本文翻译自:This C function should always return false, but it doesn’t
I stumbled over an interesting question in a forum a long time ago and I want to know the answer. 我很久以前在一个论坛里偶然发现了一个有趣的问题,我想知道答案。
Consider the following C function: 考虑以下C函数:
f1.c 在f1.c
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
This should always return false
since var3 == 3000
. 由于var3 == 3000
因此应始终返回false
。 The main
function looks like this: main
功能如下:
main.c main.c中
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Since f1()
should always return false
, one would expect the program to print only one false to the screen. 由于f1()
应始终返回false
,因此可以预期程序只会向屏幕打印一个false 。 But after compiling and running it, executed is also displayed: 但是在编译并运行它之后,还会显示执行 :
$ gcc main.c f1.c -o test
$ ./test
false
executed
Why is that? 这是为什么? Does this code have some sort of undefined behavior? 这段代码是否有某种未定义的行为?
Note: I compiled it with gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
. 注意:我用gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
编译它gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
。
#1楼
参考:https://stackoom.com/question/2T37S/这个C函数应该总是返回false-但它不会
#2楼
You don't have a prototype declared for f1()
in main.c, so it is implicitly defined as int f1()
, meaning it is a function that takes an unknown number of arguments and returns an int
. 你没有在main.c中为f1()
声明一个原型,所以它被隐式定义为int f1()
,这意味着它是一个接受未知数量的参数并返回一个int
的函数。
If int
and bool
are of different sizes, this will result in undefined behavior . 如果int
和bool
的大小不同,则会导致未定义的行为 。 For example, on my machine, int
is 4 bytes and bool
is one byte. 例如,在我的机器上, int
是4个字节, bool
是一个字节。 Since the function is defined to return bool
, it puts one byte on the stack when it returns. 由于函数被定义为返回bool
,因此它返回时会在堆栈上放置一个字节。 However, since it's implicitly declared to return int
from main.c, the calling function will try to read 4 bytes from the stack. 但是,由于它被隐式声明从main.c返回int
,因此调用函数将尝试从堆栈中读取4个字节。
The default compilers options in gcc won't tell you that it's doing this. gcc中的默认编译器选项不会告诉您它正在执行此操作。 But if you compile with -Wall -Wextra
, you'll get this: 但是如果用-Wall -Wextra
编译,你会得到这个:
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
To fix this, add a declaration for f1
in main.c, before main
: 为了解决这个问题,添加一个声明f1
main.c中,前main
:
bool f1(void);
Note that the argument list is explicitly set to void
, which tells the compiler the function takes no arguments, as opposed to an empty parameter list which means an unknown number of arguments. 请注意,参数列表显式设置为void
,它告诉编译器函数不带参数,而不是空参数列表,这意味着参数数量未知。 The definition f1
in f1.c should also be changed to reflect this. f1.c中的定义f1
也应该改变以反映这一点。
#3楼
Please compile with a command such as this one: 请使用如下命令编译:
gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c
Output: 输出:
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
With such a message, you should know what to do to correct it. 有了这样的信息,您应该知道如何纠正它。
Edit: After reading a (now deleted) comment, I tried to compile your code without the flags. 编辑:在阅读(现已删除)注释后,我尝试编译没有标志的代码。 Well, This led me to linker errors with no compiler warnings instead of compiler errors. 好吧,这导致链接器错误没有编译器警告而不是编译器错误。 And those linker errors are more difficult to understand, so even if -std-gnu99
is not necessary, please try to allways use at least -Wall -Werror
it will save you a lot of pain in the ass. 而那些链接器错误更难以理解,所以即使-std-gnu99
不是必需的,请尝试至少使用-Wall -Werror
它会为你节省很多痛苦。
#4楼
As noted in other answers, the problem is that you use gcc
with no compiler options set. 如其他答案中所述,问题是您使用gcc
而没有设置编译器选项。 If you do this, it defaults to what is called "gnu90", which is a non-standard implementation of the old, withdrawn C90 standard from 1990. 如果你这样做,它默认为所谓的“gnu90”,这是1990年旧的撤销C90标准的非标准实现。
In the old C90 standard there was a major flaw in the C language: if you didn't declare a prototype before using a function, it would default to int func ()
(where ( )
means "accept any parameter"). 在旧的C90标准中,C语言存在一个主要缺陷:如果在使用函数之前没有声明原型,则默认为int func ()
(其中( )
表示“接受任何参数”)。 This changes the calling convention of the function func
, but it doesn't change the actual function definition. 这会更改函数func
的调用约定,但不会更改实际的函数定义。 Since the size of bool
and int
are different, your code invokes undefined behavior when the function is called. 由于bool
和int
的大小不同,因此在调用函数时,代码会调用未定义的行为。
This dangerous nonsense behavior was fixed in the year 1999, with the release of the C99 standard. 随着C99标准的发布,这种危险的无意义行为在1999年得到了修复。 Implicit function declarations were banned. 隐含的功能声明被禁止。
Unfortunately, GCC up to version 5.xx still uses the old C standard by default. 不幸的是,GCC版本5.xx仍然默认使用旧的C标准。 There is probably no reason why you should want to compile your code as anything but standard C. So you have to explicitly tell GCC that it should compile your code as modern C code, instead of some 25+ years old, non-standard GNU crap. 可能没有理由为什么你想要将代码编译为标准C以外的任何东西。所以你必须明确告诉GCC它应该将你的代码编译为现代C代码,而不是25年以上的非标准GNU垃圾。
Fix the problem by always compiling your program as: 通过始终将程序编译为以下内容来解决问题:
gcc -std=c11 -pedantic-errors -Wall -Wextra
-
-std=c11
tells it to make a half-hearted attempt to compile according the (current) C standard (informally known as C11).-std=c11
告诉它根据(当前)C标准(非正式地称为C11)进行编译的半心半意。 -
-pedantic-errors
tells it to whole-heartedly do the above, and give compiler errors when you write incorrect code which violates the C standard.-pedantic-errors
告诉它全心全意地执行上述操作,并在编写违反C标准的错误代码时给出编译器错误。 -
-Wall
means give me some extra warnings that might be good to have.-Wall
意味着给我一些额外的警告,可能会有好处。 -
-Wextra
means give me some other extra warnings that might be good to have.-Wextra
意味着给我一些额外的警告,可能会有好处。
#5楼
I think it's interesting to see where the size-mismatch mentioned in Lundin's excellent answer actually happens. 我认为看看Lundin的优秀答案中提到的大小不匹配实际发生的地方很有意思。
If you compile with --save-temps
, you will get assembly files that you can look at. 如果使用--save-temps
编译,您将获得可以查看的汇编文件。 Here's the part where f1()
does the == 0
comparison and returns its value: 这是f1()
执行== 0
比较并返回其值的部分:
cmpl $0, -4(%rbp)
sete %al
The returning part is sete %al
. 返回部分是sete %al
。 In C's x86 calling conventions, return values 4 bytes or smaller (which includes int
and bool
) are returned via register %eax
. 在C的x86调用约定中,返回值为4个字节或更小(包括int
和bool
)通过寄存器%eax
返回。 %al
is the lowest byte of %eax
. %al
是%eax
的最低字节。 So, the upper 3 bytes of %eax
are left in an uncontrolled state. 因此, %eax
的高3字节处于不受控制的状态。
Now in main()
: 现在在main()
:
call f1
testl %eax, %eax
je .L2
This checks whether the whole of %eax
is zero, because it thinks it's testing an int. 这会检查整个 %eax
是否为零,因为它认为它正在测试一个int。
Adding an explicit function declaration changes main()
to: 添加显式函数声明会将main()
更改为:
call f1
testb %al, %al
je .L2
which is what we want. 这就是我们想要的。