我很久以前在一个论坛里偶然发现了一个有趣的问题,我想知道答案.
考虑以下C函数:
#include <stdbool.h>
bool f1()
{
int var1 = 1000;
int var2 = 2000;
int var3 = var1 + var2;
return (var3 == 0) ? true : false;
}
Run Code Online (Sandbox Code Playgroud)
这应该总是返回false
自var3 == 3000
.该main
函数如下所示:
#include <stdio.h>
#include <stdbool.h>
int main()
{
printf( f1() == true ? "true\n" : "false\n");
if( f1() )
{
printf("executed\n");
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
由于f1()
应该总是返回false
,人们会期望程序只能在屏幕上打印一个false.但是在编译并运行它之后,还会显示执行:
$ gcc main.c f1.c -o test
$ ./test
false
executed
Run Code Online (Sandbox Code Playgroud)
这是为什么?这段代码是否有某种未定义的行为?
注意:我编译了它gcc (Ubuntu 4.9.2-10ubuntu13) 4.9.2
.
Lun*_*din 391
如其他答案所述,问题是您使用gcc
没有编译器选项集.如果你这样做,它默认为所谓的"gnu90",这是1990年旧的撤销C90标准的非标准实现.
在旧的C90标准中,C语言存在一个主要缺陷:如果在使用函数之前未声明原型,则默认为int func ()
(其中( )
表示"接受任何参数").这会更改函数的调用约定func
,但不会更改实际的函数定义.由于大小bool
和int
不同,您的代码在调用函数时会调用未定义的行为.
随着C99标准的发布,这种危险的无意义行为在1999年得到了修复.隐含的功能声明被禁止.
不幸的是,GCC版本5.xx仍然默认使用旧的C标准.可能没有理由为什么你想要将代码编译为标准C以外的任何东西.所以你必须明确告诉GCC它应该将你的代码编译为现代C代码,而不是25年以上的非标准GNU垃圾.
通过始终将程序编译为以下内容来解决问题:
gcc -std=c11 -pedantic-errors -Wall -Wextra
Run Code Online (Sandbox Code Playgroud)
-std=c11
告诉它根据(当前)C标准(非正式地称为C11)进行编译的半心半意.-pedantic-errors
告诉它全心全意地执行上述操作,并在编写违反C标准的错误代码时给出编译器错误.-Wall
意味着给我一些可能有好处的额外警告.-Wextra
意味着给我一些额外的警告,可能会有好处.dbu*_*ush 139
你没有f1()
在main.c中声明一个原型,所以它被隐式定义为int f1()
,这意味着它是一个函数,它接受一个未知数量的参数并返回一个int
.
如果int
且bool
具有不同的大小,则会导致未定义的行为.例如,在我的机器上,int
是4个字节,bool
是一个字节.由于函数被定义为返回bool
,因此它返回时会在堆栈上放置一个字节.但是,由于它被隐式声明int
从main.c 返回,因此调用函数将尝试从堆栈中读取4个字节.
gcc中的默认编译器选项不会告诉您它正在执行此操作.但如果你编译-Wall -Wextra
,你会得到这个:
main.c: In function ‘main’:
main.c:6: warning: implicit declaration of function ‘f1’
Run Code Online (Sandbox Code Playgroud)
要解决此问题,请f1
在main.c之前添加声明main
:
bool f1(void);
Run Code Online (Sandbox Code Playgroud)
请注意,参数列表显式设置为void
,它告诉编译器函数不带参数,而不是空参数列表,这意味着参数数量未知.f1
还应更改f1.c中的定义以反映这一点.
Owe*_*wen 35
我认为看看Lundin的优秀答案中提到的大小不匹配实际发生的地方很有意思.
如果使用编译--save-temps
,您将获得可以查看的汇编文件.下面是其中的一部分f1()
做了== 0
比较,并返回其值:
cmpl $0, -4(%rbp)
sete %al
Run Code Online (Sandbox Code Playgroud)
回归部分是sete %al
.在C的x86调用约定中,返回值为4个字节或更小(包括int
和bool
)通过寄存器返回%eax
.%al
是最低的字节%eax
.因此,上面的3个字节%eax
处于不受控制的状态.
现在main()
:
call f1
testl %eax, %eax
je .L2
Run Code Online (Sandbox Code Playgroud)
这种检查是否整体的%eax
是零,因为它认为它是测试一个int.
添加显式函数声明更改main()
为:
call f1
testb %al, %al
je .L2
Run Code Online (Sandbox Code Playgroud)
这就是我们想要的.
jda*_*nay 27
请使用如下命令编译:
gcc -Wall -Wextra -Werror -std=gnu99 -o main.exe main.c
Run Code Online (Sandbox Code Playgroud)
输出:
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
Run Code Online (Sandbox Code Playgroud)
有了这样的信息,您应该知道如何纠正它.
编辑:在阅读(现已删除)注释后,我尝试编译没有标志的代码.好吧,这导致链接器错误没有编译器警告而不是编译器错误.而那些链接器错误更难以理解,所以即使-std-gnu99
没有必要,请尽量使用至少-Wall -Werror
它会为你节省很多痛苦.