ANSI C中不推荐使用的函数声明的未定义行为


ANSI C标准定义了函数原型的概念, 该原型是指示输入参数类型的函数声明的子集。 引入原型是为了消除通用函数声明所具有的缺点。


因此,在函数原型的括号中指定参数类型的列表是强制性的,否则编译器会将这样的表达式识别为过时的函数声明,这可能导致本文所述的模棱两可的情况。


过时的原型


函数声明函数的返回类型及其标识符引入指定的范围 。 请注意,不是所有的函数声明都可以被视为原型,而是只有具有输入参数类型列表的那些才可以被视为原型。


因此,下面代码的第一个表达式是声明,而不是函数原型。 以下表达式可以正确地视为原型,因为它指定了其参数的类型:


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

弃用的定义


让我们直接进入1972年(C语言发布的那年),并记住当时的程序员如何定义其函数。 让我提醒您, 函数定义将其签名与相应的可执行块(正文)联系起来。 此代码演示了K&R样式中add函数的定义:


 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而未指定参数类型列表,因此您可以使用任何参数来调用此函数。 该程序编译无误,并显示以下结果:


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

还要注意,即使使用-Wall标志, -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!"); } 

通过在do_something()函数的定义和声明中插入void关键字来更正上面的代码,否则该程序将编译而不会出现错误。 在此示例中, main()函数也用参数中的void令牌定义,尽管这不是必需的。


结论


本文的写作受到了斯蒂芬·普拉特(Stephen Prat)的书“ C编程语言。讲座和练习。第六版”的启发,特别是第五章的“带参数的函数”部分。

Source: https://habr.com/ru/post/zh-CN456682/


All Articles