سلوك غير معرف مع تعريفات الدالة المهملة في ANSI C


يعرّف معيار ANSI C مفهوم النموذج الأولي للوظيفة ، وهو مجموعة فرعية من إعلان الوظيفة الذي يشير إلى أنواع معلمات الإدخال. تم تقديم نماذج أولية من أجل القضاء على العيوب التي تمتلكها إعلانات الوظائف العامة.


وبالتالي ، يعد تحديد قائمة بأنواع المعلمات في أقواس نموذج أولي وظيفة إلزاميًا ، وإلا فسيتم التعرف على مثل هذا التعبير من قبل المترجم كإعلان وظيفة قديم ، مما قد يؤدي إلى مواقف غامضة موصوفة في هذه المقالة.


النماذج القديمة


يقدم تعريف الوظيفة نوع الإرجاع للدالة ومعرفها في النطاق المحدد. لاحظ أنه لا يمكن اعتبار كل إعلانات الوظائف نماذج أولية ، ولكن فقط تلك التي تحتوي على قائمة بأنواع معلمات الإدخال.


وبالتالي ، فإن أول تعبير عن الكود أدناه هو إعلان ، ولكن ليس نموذجًا أوليًا للوظيفة. يمكن اعتبار التعبير التالي بحق نموذجًا أوليًا ، لأنه يحدد أنواع معلماته:


/* #1 (   "foo") */ void foo(); /* #2 (  "bar") */ void bar(int count, const char *word); 

التعاريف المهملة


دعنا نذهب مباشرة إلى عام 1972 (السنة التي صدرت فيها لغة C) ونتذكر كيف حدد المبرمجون في ذلك الوقت وظائفهم. اسمحوا لي أن أذكرك بأن تعريف دالة يربط توقيعها مع الكتلة القابلة للتنفيذ المقابلة (النص الأساسي). يوضح هذا الرمز تعريف وظيفة add بنمط K&R:


 void add(right, left, result) int right; int left; int *result; { *result = right + left; } 

كما لاحظت ، تحدد الأقواس في هذا الإدخال الوظيفة ، ولكنها لا تحتوي على أي أنواع من معلمات الإدخال. إعلانات الوظيفة "الكلاسيكية" الموضحة في القسم السابق لها نفس الخاصية.


مواقف غامضة


من المحتمل أنه في حالة عدم ملاحظة بناء الجملة الجديد للنماذج الأولية وتعريفات الوظائف التي يقدمها معيار ANSI C ، فقد تنشأ مواقف غامضة صعبة. النظر في مثال:


 #include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /*    "print_number" */ void print_number(); int main(void) { /*  */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /*    */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); } 

سنقوم بتحليل هذا البرنامج. يتم print_number وظيفة print_number الصحيحة print_number دون تحديد قائمة بأنواع المعلمات ، بحيث يمكنك استدعاء هذه الوظيفة بأي وسيطات. البرنامج مترجم دون أخطاء وطبع النتيجة التالية:


 $ gcc illegal.c -o illegal -Wall $ ./illegal  : [13.359000]  : [9238.464360]  : [18437.000000]  : [0.000000]  : [0.000000]  : [0.000000] 

لاحظ أيضًا أنه حتى مع العلم gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 ، فإن برنامج التحويل البرمجي لـ -Wall gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0 لم يولد أي تحذيرات (لكنه سيكون مرغوبًا للغاية).


إصلاح هذا البرنامج لن يكون أمرًا صعبًا ، ما print_number سوى إضافة double number بين أقواس إعلان الدالة print_number في السطر السابع ، وبعد ذلك سيشير أي مترجم يتبع المعيار إلى أخطاء في الوظيفة main() :


 $ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ 

وظائف دون معلمات


وألاحظ أيضًا أن تحديد الكلمة الأساسية void بين قوسين من النماذج الأولية وتعريفات الوظائف التي لا تأخذ معلمات أمر مرغوب فيه للغاية (ولكن ليس ضروريًا). إذا لم يتم ملاحظة هذه القاعدة ، فلن يتمكن المحول البرمجي من التحقق من مراسلات أنواع الوسائط التي تم تمريرها عندما يتم استدعاء الوظيفة بأنواع صالحة من التعريف.


 #include <stdio.h> /*    "do_something" */ void do_something(); int main(void) { /*  "do_something"       */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); } 

يجب إصلاح الشفرة أعلاه عن طريق إدخال الكلمة الأساسية void في تعريف وإعلان وظيفة do_something() ، وإلا سيتم تجميع هذا البرنامج دون أخطاء. في هذا المثال ، يتم تعريف الدالة main() أيضًا void الفارغ في المعلمات ، على الرغم من أن هذا ليس ضروريًا.


استنتاج


كانت كتابة هذا المقال مستوحاة من كتاب ستيفن برات "لغة البرمجة C. محاضرات وتمارين. الطبعة السادسة" ، وتحديداً قسم "وظائف مع الحجج" من الفصل الخامس.

Source: https://habr.com/ru/post/ar456682/


All Articles