这个C函数应该总是返回false,但它不会

本文翻译自: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 . 如果intbool的大小不同,则会导致未定义的行为 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. 由于boolint的大小不同,因此在调用函数时,代码会调用未定义的行为。

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个字节或更小(包括intbool )通过寄存器%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. 这就是我们想要的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值