这个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它会为你节省很多的痛苦。