C语言 返回值和垃圾值之间的关系

btxsgosb  于 11个月前  发布在  其他
关注(0)|答案(3)|浏览(81)

如果return语句在一个C语言中产生返回值的函数中不产生返回值,会发生什么?编译器在这种情况下会使用垃圾值吗?这被称为未定义行为吗?
例如:

#include <stdio.h>

int func() {

    
}

int main() {
    int result = func();
    printf("Result: %d\n", result);
    return 0;
}

字符串
此代码的输出:结果:0
我认为这是一个未定义的行为。

goucqfw6

goucqfw61#

C 2018 6.9.1 12表示:
除非另外指定,否则如果到达终止函数的},并且调用方使用了函数调用的值,则行为未定义。
对于main函数是“另有规定”,因为C 2018 5.1.2.2.3 1中有专门的规定,对于问题中的func函数,没有另行规定,在执行func时,程序控制到达终止func},那么在调用方main中,返回值func。因此,此程序的行为不由C标准定义。
在C语言中,如果函数产生返回值,而return语句没有产生返回值,会发生什么情况?
如果被调用函数和调用函数是在单独的源文件中编译的,并且在目标模块之间没有额外的通信(如链接时优化信息),则编译器在编译调用函数时无法看到被调用函数没有返回值。因此,编译器必须生成在函数返回值时有效的指令。在这种情况下,调用例程期望函数返回一个值,因此其中的指令将从应该返回值的位置(通常是指定用于该目的的处理器寄存器)获取该值。
在问题中的情况下,对于同一源文件中的函数,编译器可以看到有未定义的行为。在关闭优化的情况下进行编译时,编译器可以如上所述进行操作。在打开优化的情况下,一个好的编译器会看到代码有未定义的行为。然后会发生什么取决于编译器的设计。可能的行为包括:
1.编译器会报告问题并停止编译。
1.编译器会删除一些原本会生成的代码。例如,它可能会删除调用func的代码,但保留printf
1.编译器会消除路径上具有未定义行为的所有程式码。
1.编译器会将路径上具有未定义行为的程式码,取代为产生陷阱的指令。
在第三种情况下,它可能会删除main中的所有内容,但保留return。这实际上并不完全是全部内容,因为return是保留的。我见过一个编译器实际上删除了main中的所有内容,只留下一个入口点,没有任何东西可以阻止代码继续进入链接到它之后的内存中的任何内容。
第二种情况可能会有一些有趣的效果。如果没有未定义的行为,正常的序列会是:
1.调用func
1.将值从用于返回该值的寄存器(例如eax)复制到用于将该值传递给printf的寄存器(例如esi)。
1.调用printf
如果代码中存在未定义的行为,编译器可能会删除1和2,但保留3。在这种情况下,程序将打印esi中的值,而不是eax中的值。也就是说,它不会打印用于返回值的寄存器中的值;它将输出其他寄存器中的值,这一点在调试时要知道,这一点很重要--一种可能表现出未定义行为的方法是,将某些事情视为它们的值来自意外的地方。

nkoocmlb

nkoocmlb2#

这其实是由于编译器!
让我们检查一下这段代码:

#include <stdio.h>

int func1(){
        return 155;
}

int func(){
        int ff = func1();
}

int main(){
        int result = func();
        printf("result %d \n", result);
        return 0;
}

字符串
试试这个,它给出了result 155,这很棒,不是吗?
这里到底发生了什么?
答:当你离开一个没有任何返回值的函数时,这意味着你没有改变寄存器R 0的值。(一种内存,一种比C更低级的语言,称为汇编语言),它负责将返回值传递给调用者函数(这被称为call stack)编译器检查这个寄存器,把它赋值给调用函数的变量。你必须挖掘更多的东西,才能真正知道编译程序时发生了什么。
我希望这能让你更清楚地理解未定义行为背后的原因。

b4qexyjb

b4qexyjb3#

在编写C标准时,一个指导原则是,如果存在一些实现,其中有时可能难以以与顺序程序执行一致的方式处理某些特定的极端情况,则这些极端情况应该被描述为UB,假设编译器的客户希望他们的行为“以一种具有环境特征的文档化方式”,而不考虑标准是否要求他们这样做 *。考虑这样一个函数:

uint16_t test1(uint32_t x);

void test(uint32_t x)
{
  uint16_t temp = test1(x);
  if (temp < 65536)
    test1(temp);
}

字符串
在ARM这样的32位平台上,平台ABI可能会指定声明为uint16_t类型的函数必须始终返回R 0,R 0的值在0-65535之间,但如果test1()福尔斯不在末尾,编译器生成test()的代码可能会识别出这一点,因为指定了test1()为了始终返回值0-65535,R 0可以不加修改地复制到另一个32位寄存器,并且该值可以不加修改地传递回test,即使它超过65535。
该标准没有试图指定可能发生或不可能发生的事情,而是放弃了管辖权,因为编译器作者及其客户将比委员会更了解他们的目标平台和应用程序需求。
请注意,从来没有任何意图邀请编译器编写者使用这样的概念,即程序将永远不会接收标准放弃管辖权的输入,作为进一步推断的邀请,例如,将序列视为

x=test(y);
if (y < 65536) doSomething();


如果y超过65536,则无条件调用doSomething(),而不管代码是否实际对x中存储的值执行任何操作。

相关问题