这个C函数应该总是返回false,但是不是
我很久以前在一个论坛上偶然发现了一个有趣的问题,我想知道答案。
考虑下面的C函数:
在f1.c
#include <stdbool.h> bool f1() { int var1 = 1000; int var2 = 2000; int var3 = var1 + var2; return (var3 == 0) ? true : false; }
这应该总是返回false因为var3 == 3000 。 mainfunction如下所示:
main.c中
#include <stdio.h> #include <stdbool.h> int main() { printf( f1() == true ? "true\n" : "false\n"); if( f1() ) { printf("executed\n"); } return 0; }
由于f1()应该总是返回false ,所以程序只能在屏幕上打印一个false 。 但编译并运行后,还会显示执行的内容:
$ gcc main.c f1.c -o test $ ./test false executed
这是为什么? 这段代码是否有某种未定义的行为?
注:我用gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2编译它。
正如在其他答案中指出的那样,问题是你使用gcc而没有设置编译器选项。 如果你这样做的话,它默认的是所谓的“gnu90”,这是从1990年起旧的,撤销的C90标准的非标准实现。
在旧的C90标准中,C语言有一个主要的缺陷:如果你在使用函数之前没有声明一个原型,它将默认为int func () (其中( )表示“接受任何参数”)。 这改变了函数func的调用约定,但不会改变实际的函数定义。 由于bool和int的大小不同,所以在调用函数时,代码会调用未定义的行为。
这个危险的废话行为在1999年得到了修正,随着C99标准的发布。 隐式函数声明被禁止。
不幸的是,GCC版本5.xx默认仍然使用旧的C标准。 你可能没有理由把你的代码编译为标准的C,所以你必须明确地告诉GCC它应该把你的代码编译成现代的C代码,而不是25年前的非标准的GNU废话。
通过总是编译你的程序来解决这个问题:
gcc -std=c11 -pedantic-errors -Wall -Wextra
-
-std=c11告诉它根据(当前)C标准(非正式地被称为C11)做一个半心半意的编译。 -
-pedantic-errors告诉它全心全意地执行上述操作,并在编写违反C标准的错误代码时给编译器提供错误。 -
-Wall意味着给我一些额外的警告,可能是好的。 -
-Wextra意味着给我一些其他额外的警告,可能是好的。
你没有在main.c中声明f1()的原型,所以它被隐含地定义为int f1() ,这意味着它是一个函数,它接收未知数量的参数并返回一个int 。
如果int和bool大小不同,则会导致未定义的行为 。 例如,在我的机器上, int是4个字节,而bool是一个字节。 由于该函数被定义为返回bool ,所以它在返回时将一个字节放在堆栈上。 但是,因为它被隐式声明为从main.c返回int ,所以调用函数将尝试从栈中读取4个字节。
gcc中的默认编译器选项不会告诉你它是这样做的。 但是如果你用-Wall -Wextra编译,你会得到这个:
main.c: In function 'main': main.c:6: warning: implicit declaration of function 'f1'
为了解决这个问题,在main.c之前添加一个f1的声明:
bool f1(void);
请注意,参数列表显式设置为void ,它告诉编译器该函数不带任何参数,而不是空参数列表,这意味着未知数量的参数。 f1.c中的定义f1也应该改变以反映这一点。
我觉得很有意思的是,看到伦丁出色的回答中提到的大小不匹配的情况。
如果使用--save-temps编译,您将获得可以查看的汇编文件。 这里是f1()进行== 0比较的部分,并返回它的值:
cmpl $0, -4(%rbp) sete %al
返回部分是sete %al 。 在C的x86调用约定中,通过寄存器%eax返回4个字节或更小(包括int和bool )的返回值。 %al是%eax的最低字节。 所以, %eax的高3字节处于非受控状态。
现在在main() :
call f1 testl %eax, %eax je .L2
这将检查整个 %eax是否为零,因为它认为它正在testing一个int。
添加一个明确的函数声明将main()更改为:
call f1 testb %al, %al je .L2
这是我们想要的。
请用这样的命令编译:
gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c
输出:
main.c: In function 'main': main.c:14:5: error: implicit declaration of function 'f1' [-Werror=impl icit-function-declaration] printf( f1() == true ? "true\n" : "false\n"); ^ cc1.exe: all warnings being treated as errors
有了这样的信息,你应该知道该怎么做来纠正它。
编辑:阅读(现在删除)的评论后,我试图编译你的代码没有标志。 那么,这导致我链接错误,没有编译器警告,而不是编译器错误。 而且这些链接器错误是比较难理解的,所以即使-std-gnu99没有必要,请尽量至less使用-Wall -Werror它会为你节省很多的痛苦。