这个 C 函数应该总是返回 false,但事实并非如此

问题描述 投票:0回答:4

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

考虑以下 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
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。但编译运行后,还显示execute

$ gcc main.c f1.c -o test
$ ./test
false
executed

这是为什么呢?这段代码是否有某种未定义的行为?

注意:我是用

gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
编译的。

c gcc
4个回答
413
投票

如其他答案中所述,问题是您使用

gcc
时未设置编译器选项。如果这样做,它默认为所谓的“gnu90”,这是 1990 年撤回的旧 C90 标准的非标准实现。

在旧的 C90 标准中,C 语言有一个重大缺陷:如果在使用函数之前没有声明原型,它将默认为

int func ()
(其中
( )
表示“接受任何参数”)。这改变了函数
func
的调用约定,但并没有改变实际的函数定义。由于
bool
int
的大小不同,因此您的代码在调用函数时会调用未定义的行为。

随着 1999 年 C99 标准的发布,这种危险的胡言乱语行为得到了修复。隐式函数声明被禁止。

不幸的是,GCC 直到版本 5.x.x 仍然默认使用旧的 C 标准。你可能没有理由想要将你的代码编译为标准 C 以外的任何代码。因此,你必须明确告诉 GCC 它应该将你的代码编译为现代 C 代码,而不是一些 25 岁以上的非标准 GNU 废话.

通过始终将程序编译为以下方式来解决问题:

gcc -std=c11 -pedantic-errors -Wall -Wextra
  • -std=c11
    告诉它半心半意地尝试根据(当前)C 标准(非正式地称为 C11)进行编译。
  • -pedantic-errors
    告诉它全心全意地执行上述操作,并在您编写违反 C 标准的错误代码时给出编译器错误。
  • -Wall
    意味着给我一些可能会更好的额外警告。
  • -Wextra
    意味着给我一些其他可能会更好的额外警告。

148
投票

布什8===> SO是一场狗屎秀。感谢您的搭车。


39
投票

我认为看看 Lundin 的出色答案中提到的尺寸不匹配实际发生在哪里很有趣。

如果使用

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

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

返回的部分是

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

现在在

main()

call    f1
testl   %eax, %eax
je  .L2

这会检查 %eax

whole
是否为零,因为它认为它正在测试 int。

添加显式函数声明将

main()
更改为:

call    f1
testb   %al, %al
je  .L2

这就是我们想要的。


27
投票

请使用如下命令进行编译:

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
它会为您省去很多麻烦。

© www.soinside.com 2019 - 2024. All rights reserved.