【问题标题】:Returning a value from a function with no return [duplicate]从没有返回的函数返回值[重复]
【发布时间】:2021-07-02 16:11:18
【问题描述】:

我认为我发现 gcc 编译器处理函数的方式存在问题。

我不知道这是一个错误,还是我多年来一直放过的东西从不分心。 在实践中,通过声明一个函数并定义后者具有返回值,编译器将分配在函数范围内的第一个变量的值存储在 EAX 寄存器中,然后将其依次存储在变量中。示例:

#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;
}

这是输出:

5

这是带有 intel 语法的 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

如您所见,它在变量c 中保存了参数ab 的总和,但随后将我保存在变量res 包含它们总和的eax 寄存器中,好像函数返回值。

这是因为函数是用返回值定义的吗?

【问题讨论】:

  • 您希望发生什么?
  • 因为函数没有返回值,所以我省略了“return”,我希望变量“c”的内容会丢失,因此在 nesusno mod 中 eax 的内容(eax = a + b) 存储在“res”中。我再说一遍,我省略了“return”,neinte“return c”或“return a + b”
  • 好的,我明白了,谢谢
  • 确保始终使用-Wall,您会从编译器收到消息:“控制到达非无效函数的末尾”。我认为这是警告而不是错误的唯一原因是标准要么不想强制编译器进行所需的分析以检测到这一点,要么可能不想指定所需的实际分析.
  • @ErikEidt:在 C 语言中,只要调用者不使用返回值,行为就已经明确。这是为了与 void 之前的 pre-ANSI C 向后兼容,并且存在原型,因此存在从非 void 函数末尾脱落的现有代码。即使对于 C99/C11 也没有取缔它。在 ISO C++ 中,它 未定义的行为,以便执行从非 void 函数的末尾脱落,因此即使没有 -Wall,g++ 也会发出警告,并为此省略 code-gen执行路径(甚至不是 ret,只是在 asm 中从字面上掉下来!)godbolt.org/z/e54qnKr7q

标签: c assembly gcc undefined-behavior


【解决方案1】:

您所做的是通过未能从函数返回值然后尝试使用该返回值来触发 undefined behavior

这在C standard 的第 6.9.1p12 节中有记录:

如果到达终止函数的},并且调用者使用了函数调用的值,则行为未定义。

如您所见,未定义行为的一种表现方式是程序看起来正常工作。但是,如果您添加了一些不相关的代码或使用不同的优化设置进行编译,则无法保证它会继续工作。

【讨论】:

  • 事实上,这个only 主要与gcc -O0 一起工作(实际上它相当一致,并且被代码高尔夫C 答案滥用。Return value in unused parameter)。它通常不适用于其他编译器,并且绝对不能启用优化。
  • 它很可能也适用于其他编译器,但肯定不适用于优化。由于可观察到的行为没有变化,因此优化时可能根本不包含代码。
  • 使用聪明的编译器,代码可能会编译成优化后的启动代码,因为这个特定的示例根本不会做任何事情。
  • @PeterCordes:过去,低级编译器通常会指定如果函数调用后没有任何其他计算,则其返回值将放置在某些寄存器中(取决于类型),并且从函数的末尾掉下来会返回给调用者的任何值在适当的寄存器中,但是任何计算都可能以未指定的方式影响这些寄存器。这样的规范,累积起来,会将函数返回的行为定义为除了返回无意义的值之外没有奇怪的副作用。
【解决方案2】:

eax 是用于返回值的寄存器,在本例中是因为它应该返回int。因此,调用者会得到该寄存器中发生的任何内容。但是,您至少应该得到一个警告,即没有返回语句。

因为您的函数非常小,并且编译器决定使用eax 寄存器进行计算,所以它似乎可以工作。

如果开启优化或者提供更复杂的功能,结果会大不一样。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-27
    • 1970-01-01
    • 2015-05-25
    • 2011-06-06
    相关资源
    最近更新 更多