这个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 == 3000mainfunction如下所示:

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的调用约定,但不会改变实际的函数定义。 由于boolint的大小不同,所以在调用函数时,代码会调用未定义的行为。

这个危险的废话行为在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

如果intbool大小不同,则会导致未定义的行为 。 例如,在我的机器上, 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个字节或更小(包括intbool )的返回值。 %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它会为你节省很多的痛苦。