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

main.c中

#include <stdio.h>
#include <stdbool.h>

int main()
{
    printf( f1() == true ? "truen" : "falsen");
    if( f1() )
    {
        printf("executedn");
    }
    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标准。 隐式函数声明被禁止。

不幸的是,默认情况下,版本5.xx以前的GCC仍然使用旧的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之前为main添加一个f1的声明:

    bool f1(void);
    

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


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

    如果使用--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是否为零,因为它认为它正在测试一个int。

    添加一个显式的函数声明将main()更改为:

    call    f1
    testb   %al, %al
    je  .L2
    

    这是我们想要的。

    链接地址: http://www.djcxy.com/p/14023.html

    上一篇: This C function should always return false, but it doesn’t

    下一篇: when the memory is cleared on stack for a local function?