2011-09-02 23 views
9
#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没有 “返回”()。并用gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4),fedora-14编译源代码。GCC为什么以及如何编译一个缺少return语句的函数?

当然,发出警告:“警告:控制到达非void函数结束”,但是,运作良好。

使用gcc进行编译时,代码中发生了什么? 我想在这种情况下得到一个坚实的答案。 谢谢:)

+5

闻起来像未定义行为。 – ThiefMaster

+2

@ThiefMaster:它**是** UB。他很幸运,通常放置返回值的寄存器恰好也用于减法。 –

+0

谢谢大家:) – harrison

回答

19

发生了什么事对你来说,当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调用约定中,这是返回值预期为的寄存器!所以......你很幸运。

但请讲究警告。他们在那里是有原因的!

+0

+1显示asm –

+1

这正是我所猜测的:使用eax,包含操作的结果,eax是返回值。但是,男孩,当你习惯了英特尔风格时,很难阅读GNU风格的汇编。 –

+0

现在我知道这个问题是什么。我看到了警告,但我只是想知道。谢谢:) – harrison

7

它取决于Application Binary Interface和哪些寄存器用于计算。

E.g.在x86上,第一个函数参数和返回值存储在EAX中,所以gcc最有可能使用它来存储计算结果。

+0

我读了ABI和调用约定。谢谢:) – harrison

1

我不能告诉你,你的平台的细节,因为我不知道,但有一种普遍的答案,你看到的行为。

当编译了一个具有返回的函数时,编译器将使用关于如何返回该数据的约定。它可能是一个机器寄存器,或者是一个定义的存储器位置,例如通过堆栈或其他(尽管通常使用机器寄存器)。编译后的代码也可以在执行该功能时使用该位置(注册或以其他方式)。

如果函数不返回任何内容,那么编译器不会生成代码,明确填充带有返回值的位置。不过就像我上面所说的那样,它可能会在该功能中使用该位置。当您编写读取返回值(ch2 = toUpper(ch);)的代码时,编译器将编写使用其惯例的代码,以检索从传统位置返回的返回值。就调用者代码而言,即使没有明确写入,它也会从该位置读取该值。因此你会得到一个价值。

现在看看@ Ray的示例,编译器使用的EAX寄存器,存储所述上壳体的操作的结果。它恰好如此,这可能是返回值写入的位置。在调用端,ch2加载了EAX中的值 - 因此是幻像返回。这只适用于x86系列处理器,因为在其他架构上,编译器可以使用完全不同的方案来决定应该如何组织约定

然而好的编译器会尝试根据本地条件集,知识代码,规则和启发式。所以重要的一点是,这只是运气而已。编译器可以优化而不是做这个或者什么 - 你不应该回应这个行为。

+0

-1听起来像是一个中国的幸运饼干...更具体的 – Quamis

+0

你究竟想让我更具体一些吗? –

+0

retracted my -1 :) – Quamis

2

本质上,c被推入到应该稍后用返回值填充的位置;因为它没有被return的使用覆盖,所以它返回的值结束。

请注意,依赖于此(使用C语言或任何其他语言(这不是Perl的显式语言功能))是一个糟糕的想法™。在极端。

+0

答案是%eax寄存器。我知道了 – harrison

2

一个很重要的理解是缺少一个return语句的诊断错误。考虑一下这个功能:

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

只要你从来没有与42的参数调用它,包含此功能的程序是完全合法的C,不调用任何不确定的行为,尽管它调用如果您拨打f(42)并随后尝试使用返回值,则为UB。因此,虽然编译器可能会提供缺少返回语句的警告启发式,但如果没有误报或漏报,就不可能这样做。这是不可能解决暂停问题的结果。

+2

+1很高兴澄清这一点。 Java和Ada的关于缺少返回值的编译时错误只要检测到存在_exists_通过函数的某个路径而不会返回语句就可以退出,而不是确保这样的路径。 –

0

没有局部变量,所以函数结尾的堆栈顶部的值将是参数c。退出时堆栈顶部的值是返回值。所以无论C持有多少,这就是回报价值。

+0

谢谢你简单的指示 – harrison

0

您应该记住,这样的代码可能会因编译器而崩溃。例如,clang会在这样的函数结束时生成ud2指令,并且您的应用程序将在运行时崩溃。

0

我已经尝试了小PROGRAMM:

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

结果:

TEST:< 1>

TEST:< 10>

TEST:< 11>

TEST:< 11>

TEST:< 11>

我用的mingw32-gcc编译器,所以有可能是个体差异。

你可以玩耍并尝试一个char函数。 只要你不使用结果值,它将仍然工作正常。

#include <stdio.h> 
char f1() { 
} 
int main() { 
    f1(); 
} 

但我stil会建议设置void函数或给一些返回值。

你的功能似乎需要一个回报:

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