GCC为什么以及如何编译一个缺少return语句的函数?

zysjyyx4  于 2022-11-13  发布在  其他
关注(0)|答案(8)|浏览(197)

请考虑:

#include <stdio.h>

char toUpper(char);

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

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

toUpper 函数中,返回类型为 char,但在 toUpper() 中没有任何“return”,并使用gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4),Fedora 14编译源代码。
当然,警告是发出了:“警告:控件到达非void函数的末尾”,但是,运行良好。
在用gcc编译的过程中,代码中发生了什么?

ylamdve6

ylamdve61#

当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调用约定中,这就是返回值应该在的寄存器!所以...你很幸运。
但请**注意警告。**他们在那里是有原因的!

lp0sw83n

lp0sw83n2#

它取决于Application Binary Interface以及用于计算的寄存器。
例如,在x86上,第一个函数参数和返回值存储在EAX中,因此gcc很可能也使用它来存储计算结果。

dz6r00yl

dz6r00yl3#

本质上,c被推入稍后应该用返回值填充的位置;因为它不会被return覆盖,所以它将作为返回值结束。
请注意,依赖于此(在C语言或任何其他语言中,这不是一个显式的语言特性,如Perl)是一个坏主意™。

r1zhe5dt

r1zhe5dt4#

有一点需要了解,那就是省略return语句很少是一个可诊断的错误。

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

只要你不使用参数42调用它,那么包含这个函数的程序就是完全有效的C,不会调用任何未定义的行为,尽管如果你调用f(42)并随后试图使用返回值,它 * 会 * 调用UB。
因此,虽然编译器可以为缺少的返回语句提供警告启发式方法,但在没有误报或漏报的情况下是不可能做到的。这是不可能解决停机问题的结果。

dauxcl2d

dauxcl2d5#

我不能告诉你你的平台的细节,因为我不知道它,但有一个一般的答案,你看到的行为。
当某个函数被编译时,编译器会使用一个约定来返回该数据。它可以是一个机器寄存器,或者是一个定义的内存位置,比如通过堆栈或其他方式(尽管通常使用机器寄存器)。编译后的代码在执行函数的工作时也会使用该位置(寄存器或其他)。
如果函数没有返回任何值,那么编译器就不会生成用返回值显式填充该位置的代码。但是,就像我前面所说的,它可能会在执行函数时使用该位置。当您编写读取返回值(ch2 = toUpper(ch);)的代码时,编译器将编写使用其关于如何从常规位置检索返回值约定的代码。就调用程序代码而言,它只会从该位置读取该值,即使没有显式写入任何内容,也会得到一个值。
现在看看Ray的例子。编译器使用EAX寄存器来存储大写操作的结果。碰巧的是,这可能是返回值被写入的位置。在调用端,ch 2被加载了EAX中的值--因此是一个幻像返回。这只适用于x86范围的处理器。如在其它体系结构上一样,编译器可以使用完全不同的方案来决定应当如何组织约定。
然而,好的编译器会根据一组局部条件、代码知识、规则和启发式来进行优化。因此,需要注意的一点是,这只是运气的作用。编译器可以进行优化,但不做这件事或其他任何事情--你不应该回答这种行为。

gv8xihay

gv8xihay6#

您应该记住,这类代码可能会崩溃,具体取决于编译器。例如,Clang在此类函数的末尾生成一条 ud2 指令,您的应用将在运行时崩溃。

anhgbhbe

anhgbhbe7#

这里没有局部变量,所以函数结束时栈顶的值是参数c,函数退出时栈顶的值是返回值,所以不管c是什么,都是返回值.

lndjwyie

lndjwyie8#

我试过一个小程序:

#include <stdio.h>

int f1() {
}

int main() {
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  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;
}

相关问题