繁体   English   中英

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

[英]This C function should always return false, but it doesn’t

我很久以前在一个论坛里偶然发现了一个有趣的问题,我想知道答案。

考虑以下C函数:

在f1.c

#include <stdbool.h>

bool f1()
{
    int var1 = 1000;
    int var2 = 2000;
    int var3 = var1 + var2;
    return (var3 == 0) ? true : false;
}

由于var3 == 3000因此应始终返回false main功能如下:

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 (Ubuntu 4.9.2-10ubuntu13) 4.9.2

如其他答案中所述,问题是您使用gcc而没有设置编译器选项。 如果你这样做,它默认为所谓的“gnu90”,这是1990年旧的撤销C90标准的非标准实现。

在旧的C90标准中,C语言存在一个主要缺陷:如果在使用函数之前没有声明原型,则默认为int func () (其中( )表示“接受任何参数”)。 这会更改函数func的调用约定,但不会更改实际的函数定义。 由于boolint的大小不同,因此在调用函数时,代码会调用未定义的行为。

随着C99标准的发布,这种危险的无意义行为在1999年得到了修复。 隐含的功能声明被禁止。

不幸的是,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’

为了解决这个问题,添加一个声明f1 main.c中,前main

bool f1(void);

请注意,参数列表显式设置为void ,它告诉编译器函数不带参数,而不是空参数列表,这意味着参数数量未知。 f1.c中的定义f1也应该改变以反映这一点。

我认为看看Lundin的优秀答案中提到的大小不匹配实际发生的地方很有意思。

如果使用--save-temps编译,您将获得可以查看的汇编文件。 这是f1()执行== 0比较并返回其值的部分:

cmpl    $0, -4(%rbp)
sete    %al

返回部分是sete %al 在C的x86调用约定中,返回值为4个字节或更小(包括intbool )通过寄存器%eax返回。 %al%eax的最低字节。 因此, %eax的高3字节处于不受控制的状态。

现在在main()

call    f1
testl   %eax, %eax
je  .L2

这会检查整个 %eax是否为零,因为它认为它正在测试一个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不是必需的,请尝试至少使用-Wall -Werror它会为你节省很多痛苦。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM