C 从没有返回的函数返回值
我认为我发现了gcc编译器处理函数的方式存在问题 我不知道这是一个错误还是一个永远不会让我分心的事情,我已经错过了多年。 实际上,通过声明函数并定义具有返回值的函数,编译器将在函数范围内分配的第一个变量的值存储在EAX寄存器中,然后依次将其存储在变量中。例如:C 从没有返回的函数返回值,c,assembly,gcc,undefined-behavior,C,Assembly,Gcc,Undefined Behavior,我认为我发现了gcc编译器处理函数的方式存在问题 我不知道这是一个错误还是一个永远不会让我分心的事情,我已经错过了多年。 实际上,通过声明函数并定义具有返回值的函数,编译器将在函数范围内分配的第一个变量的值存储在EAX寄存器中,然后依次将其存储在变量中。例如: #include<stdio.h> int add(int a, int b) { int c = a + b; ;there isn't return } int main(void) { in
#include<stdio.h>
int add(int a, int b)
{
int c = a + b;
;there isn't return
}
int main(void)
{
int res = add(3, 2);
return 0;
}
这是具有英特尔语法的x86-64程序集:
功能添加:
push rbp
mov rbp, rsp
mov DWORD PTR[rbp-0x14], edi ;store first
mov DWORD PTR[rbp-0x18], esi ;store second
mov edx, DWORD PTR[rbp-0x14]
mov eax, DWORD PTR[rbp-0x18]
add eax, esx
mov DWORD PTR[rbp-0x4], eax
nop
pop rbp
ret
主要功能:
push rbp
mov rbp, rsp
sub rsp, 0x10
mov esi, 0x2 ;first parameter
mov edi, 0x3 ;second parameter
call 0x1129 <add>
;WHAT??? eax = a + b, why store it?
mov DWORD PTR[rbp-0x4], eax
mov eax, 0x0
leave
ret
push-rbp
mov rbp,rsp
副rsp,0x10
movesi,0x2;第一参数
movedi,0x3;第二个参数
呼叫0x1129
;什么???eax=a+b,为什么要存储它?
mov DWORD PTR[rbp-0x4],eax
mov-eax,0x0
离开
ret
如您所见,它将参数a
和b
的总和保存在变量c
中,但随后它将我保存在变量res
包含其总和的eax寄存器中,就像函数返回值一样
之所以这样做是因为函数是用返回值定义的吗?您所做的是通过未能从函数返回值,然后尝试使用该返回值来触发的 本文件第6.9.1p12节对此进行了记录: 如果到达了终止函数的
}
,并且调用方使用了函数调用的值,则行为未定义
正如您所看到的,未定义行为的一种表现方式是程序看起来工作正常。但是,如果您添加了一些不相关的代码或使用不同的优化设置编译,则无法保证它会继续工作。
eax
是用于返回值的寄存器,在这种情况下,因为它应该返回int
。所以调用方得到寄存器中发生的任何内容。然而,您至少应该得到一个警告,即没有返回语句
因为您的函数非常小,并且编译器决定使用eax
寄存器进行计算,所以它似乎可以工作
如果您打开优化或提供更复杂的函数,结果会截然不同。您希望会发生什么?因为函数不返回值,所以我省略了“return”,我希望变量“c”的内容会丢失,因此在nesusno mod中,eax(eax=a+b)的内容存储在“res”中. 我重复一遍,我省略了“return”,没有“return c”或“return a+b”好的,现在我明白了,谢谢你确保总是使用
-Wall
,你会从编译器那里得到消息:“控件到达非void函数的末尾”。我认为这是一个警告而不是错误的唯一原因是标准不想强制编译器进行所需的分析来检测这一点,或者,可能不想指定所需的实际分析。@ErikEidt:在C中,只要调用方不使用返回值,这种行为就是定义良好的。这是为了从void
之前向后兼容前ANSI C,并且存在原型,因此存在从非void函数末尾脱落的现有代码。即使是C99/C11也没有宣布它为非法。在ISO C++中,执行的是一个未定义的行为,它在非空虚函数的结尾处掉下来,因此<代码> G++< /COD>将警告即使没有墙,也省略代码Gen执行该路径(甚至不是<代码> ReT <代码>,只是从ASM中的结尾处掉下来),实际上这只不过是可靠地工作在<代码> GCC—O0</代码>中。(事实上是相当一致的,并且被代码C答案滥用。)。它通常不适用于其他编译器,并且肯定不适用于启用了优化的编译器。它很可能也适用于其他编译器,但肯定不适用于优化。由于观察到的行为没有变化,因此优化时可能根本不包含代码。使用智能编译器,代码可能会编译为零g但是启动代码需要优化,因为这个特定的示例根本不会做任何事情。@PeterCordes:低级编译器通常会指定,如果函数调用后面没有任何其他计算,它的返回值将放在某些寄存器中(取决于类型),并且从函数的末尾掉下来会将适当寄存器中的任何值返回给调用方,但任何计算都可能以未指定的方式影响这些寄存器。这种累积的规范将函数返回的行为定义为除了返回无意义的值之外没有奇怪的副作用。
push rbp
mov rbp, rsp
sub rsp, 0x10
mov esi, 0x2 ;first parameter
mov edi, 0x3 ;second parameter
call 0x1129 <add>
;WHAT??? eax = a + b, why store it?
mov DWORD PTR[rbp-0x4], eax
mov eax, 0x0
leave
ret