GCC 为什么以及如何编译缺少返回语句的函数?

2024-11-11 08:27:00
admin
原创
200
摘要:问题描述:考虑:#include <stdio.h> char toUpper(char); int main(void) { char ch, ch2; printf("lowercase input: "); ch = getchar(); ...

问题描述:

考虑:

#include <stdio.h>

char toUpper(char);

int main(void)
{
    char ch, ch2;
    printf("lowercase input: ");
    ch = getchar();
    ch2 = toUpper(ch);
    printf("%c ==> %c
", ch, ch2);
    
    return 0;
}

char toUpper(char c)
{
    if(c>='a' && c<='z')
        c = c - 32;
}

在toUpper函数中,返回类型为char ,但toUpper()中没有任何“返回” 。并使用 gcc (GCC) 4.5.1 20100924 (Red Hat 4.5.1-4), Fedora 14编译源代码。

当然,会发出一个警告:“警告:控制到达非 void 函数的末尾”,但是,运行良好。

使用 gcc 编译时该代码发生了什么?


解决方案 1:

当 C 程序被编译成汇编语言时,你的 toUpper 函数最终可能是这样的:

_toUpper:
LFB4:
        pushq   %rbp
LCFI3:
        movq    %rsp, %rbp
LCFI4:
        movb    %dil, -4(%rbp)
        cmpb    $96, -4(%rbp)
        jle     L8
        cmpb    $122, -4(%rbp)
        jg      L8
        movzbl  -4(%rbp), %eax
        subl    $32, %eax
        movb    %al, -4(%rbp)
L8:
        leave
        ret

32 的减法是在 %eax 寄存器中执行的。在 x86 调用约定中,返回值应该位于该寄存器中!所以……你很幸运。

但请注意警告。它们的存在是有原因的!

解决方案 2:

这取决于应用程序二进制接口以及用于计算的寄存器。

例如在 x86 上,第一个函数参数和返回值存储在中EAX,因此 gcc 很可能也使用它来存储计算结果。

解决方案 3:

您应该记住,此类代码可能会因编译器的不同而崩溃。例如,Clang会在此类函数末尾生成ud2指令,您的应用将在运行时崩溃。

解决方案 4:

需要理解的一件重要的事情是,省略 return 语句很少会造成可诊断的错误。考虑这个函数:

int f(int x)
{
    if (x!=42) return x*x;
}

只要您从未使用 42 作为参数调用它,包含此函数的程序就是完全有效的 C,并且不会调用任何未定义的行为,尽管如果您调用并随后尝试使用返回值,它调用 UB 。f(42)

因此,虽然编译器可以为缺失的返回语句提供警告启发式方法,但这样做不可能不产生误报或漏报。这是无法解决停机问题的结果。

解决方案 5:

本质上,c被推入稍后应填充返回值的位置;由于它不会被使用所覆盖return,因此它最终成为返回的值。

请注意,依赖这一点(在 C 语言中,或者在任何其他没有明确语言特性的语言中,比如 Perl)是一个糟糕的想法™。在极端情况下。

解决方案 6:

由于我不知道您的平台的具体情况,所以我无法告诉您,但是对于您所看到的行为有一个一般性的答案。

当编译具有返回值的某个函数时,编译器将使用惯例来返回该数据。它可以是机器寄存器,也可以是定义的内存位置,例如通过堆栈或其他方式(尽管通常使用机器寄存器)。编译后的代码在执行函数工作时也可能使用该位置(寄存器或其他位置)。

如果函数不返回任何内容,则编译器不会生成明确用返回值填充该位置的代码。但是,就像我上面所说的那样,它可能会在函数期间使用该位置。当您编写读取返回值的代码时(ch2 = toUpper(ch);),编译器将编写使用其约定的代码,了解如何从常规位置检索该返回值。就调用者代码而言,它只会从该位置读取该值,即使那里没有明确写入任何内容。因此你得到了一个值。

现在看看Ray 的例子。编译器使用 EAX 寄存器来存储大写操作的结果。碰巧的是,这可能是写入返回值的位置。在调用方,ch2 加载了 EAX 中的值 - 因此是幻影返回。这仅适用于 x86 系列处理器,因为在其他架构上,编译器可能会使用完全不同的方案来决定如何组织约定。

但是,好的编译器会尝试根据一组本地条件、代码知识、规则和启发式方法进行优化。因此,需要注意的一件重要事情是,这仅仅是运气好而已。编译器可以进行优化而不这样做或做其他事情 - 您不应该依赖这种行为。

解决方案 7:

没有局部变量,因此函数结束时堆栈顶部的值将是参数 c。退出时堆栈顶部的值是返回值。因此,c 所含的内容就是返回值。

解决方案 8:

我尝试过一个小程序:

#include <stdio.h>

int f1() {
}

int main() {
    printf("TEST: <%d>
",  f1());
    printf("TEST: <%d>
",  f1());
    printf("TEST: <%d>
",  f1());
    printf("TEST: <%d>
",  f1());
    printf("TEST: <%d>
",  f1());
}

结果:

测试:<1>

测试:<10>

测试:<11>

测试:<11>

测试:<11>

我使用了MinGW -GCC 编译器,所以可能会有差异。

您可以随意尝试,例如,char 函数。只要您不使用结果值,它仍然可以正常工作。

#include <stdio.h>

char f1() {
}

int main() {
    f1();
}

但我仍然建议设置 void 函数或提供一些返回值。

您的函数似乎需要返回:

char toUpper(char c)
{
    if(c>='a'&&c<='z')
        c = c - 32;
    return c;
}
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用