帮助理解汇编级别的C和C ++中#define,const和enum之间的差异

时间:2010-03-08 10:05:21

标签: c gcc assembly x86

最近,我正在研究#define,const和enum的汇编代码:

C代码(#define):

3   #define pi 3  
4   int main(void)
5   {
6      int a,r=1;             
7      a=2*pi*r;
8      return 0;
9   }

由GCC生成的汇编代码(用于c代码中的第6行和第7行):

6   mov $0x1, -0x4(%ebp)
7   mov -0x4(%ebp), %edx
7   mov %edx, %eax
7   add %eax, %eax
7   add %edx, %eax
7   add %eax, %eax
7   mov %eax, -0x8(%ebp)

C代码(枚举):

2   int main(void)
3   {
4      int a,r=1;
5      enum{pi=3};
6      a=2*pi*r;
7      return 0;
8   }

由GCC生成的汇编代码(对于c代码中的第4行和第6行):

6   mov $0x1, -0x4(%ebp)
7   mov -0x4(%ebp), %edx
7   mov %edx, %eax
7   add %eax, %eax
7   add %edx, %eax
7   add %eax, %eax
7   mov %eax, -0x8(%ebp)

C代码(const):

4   int main(void)
5   {
6      int a,r=1;  
7      const int pi=3;           
8      a=2*pi*r;
9      return 0;
10  }

由GCC生成的汇编代码(对于c代码中的第7行和第8行):

6   movl $0x3, -0x8(%ebp)
7   movl $0x3, -0x4(%ebp)
8   mov  -0x4(%ebp), %eax
8   add  %eax, %eax
8   imul -0x8(%ebp), %eax
8   mov  %eax, 0xc(%ebp)

我发现使用#defineenum,汇编代码是相同的。编译器使用3个添加指令来执行乘法。但是,使用const时,会使用imul指令。 谁知道背后的原因?

5 个答案:

答案 0 :(得分:7)

不同之处在于,对于#defineenum,值3不需要作为代码中的显式值存在,因此编译器决定使用两个添加指令而不是分配空间对于常量3. add reg,reg指令是每个指令2个字节,因此6个字节的指令和0个字节的常量乘以3,这是比imul加上4个字节常量更小的代码。再加上使用添加指令的方式,它可以实现* 2 * 3的非常直译,因此这可能不是大小优化,只要乘以2或3,它就可以是默认的编译器输出。通常是比乘法更快的指令)。

#defineenum没有声明实例,它们只提供了为值3赋予符号名称的方法,因此编译器可以选择制作更小的代码。

  mov $0x1, -0x4(%ebp)    ; r=1
  mov -0x4(%ebp), %edx    ; edx = r
  mov %edx, %eax          ; eax = edx
  add %eax, %eax          ; *2
  add %edx, %eax          ; 
  add %eax, %eax          ; *3
  mov %eax, -0x8(%ebp)    ; a = eax

但是当你声明const int pi = 3时,你告诉编译器为一个整数值分配空间并用3初始化它。它使用4个字节,但是这个常量现在可用作{{的操作数1}}指令。

imul

顺便说一句,这显然不是优化代码。因为永远不会使用值 movl $0x3, -0x8(%ebp) ; pi = 3 movl $0x3, -0x4(%ebp) ; r = 3? (typo?) mov -0x4(%ebp), %eax ; eax = r add %eax, %eax ; *2 imul -0x8(%ebp), %eax ; *pi mov %eax, 0xc(%ebp) ; a = eax ,所以如果打开优化,编译器就会执行

a

在所有3个案例中。

附录:

我尝试使用MSVC并且在调试模式下,我得到所有3种情况的相同输出,MSVC总是使用imul的文字6.即使在情况3时它创建xor eax, eax ; return 0 它实际上没有参考它在imul。

我不认为这个测试真的告诉你有关const vs define vs enum的任何内容,因为这是非优化代码。

答案 1 :(得分:0)

const关键字仅表示不允许访问它的特定文件对其进行修改,但其他模块可能会修改或定义该值。因此,不允许移位和乘法,因为该值不是预先知道的。预定义后,#define'ed值将简单地替换为文字值,因此编译器可以在编译时对其进行分析。虽然我对enums不太确定。

答案 2 :(得分:0)

在最后一种情况下,编译器将pi视为变量而不是文字常量。编译器可能可以使用不同的编译器选项对其进行优化。

[edit]注意,由于a已分配但未使用,因此可以优化写入的整个片段;将a声明为volatile以防止发生这种情况。

C ++中const的语义与C中的语义略有不同,我怀疑你会在C ++编译中得到不同的结果。

答案 3 :(得分:0)

当编译为C ++时,生成与使用C编译时生成的代码相同的代码,至少使用GCC 4.4.1:

const int pi = 3;

...

a=2*pi*r;
-   0x40132e    <main+22>:      mov    0xc(%esp),%edx
-   0x401332    <main+26>:      mov    %edx,%eax
-   0x401334    <main+28>:      shl    %eax
-   0x401336    <main+30>:      add    %edx,%eax
-   0x401338    <main+32>:      shl    %eax
-   0x40133a    <main+34>:      mov    %eax,0x8(%esp)

如果pi定义为:

,则会发出相同的代码
#define pi 3

答案 4 :(得分:0)

看起来你实际上并没有打开优化器 - 即使你认为你做了。我编译了这个:

int literal(int r)
{
  return 2*3*r;
}
int enumeral(int r)
{
  enum { pi=3 };
  return 2*pi*r;
}
int constant(int r)
{
  const int pi=3;
  return 2*pi*r;
}

...使用Apple的gcc 4.2(比您使用的编译器更旧);当我不使用优化(默认)时,我可以重现你说你得到的程序集,但是在任何更高的优化级别,我得到所有三个相同的代码,相同的代码是否编译为C或C ++:

    movl    4(%esp), %eax
    leal    (%eax,%eax,2), %eax
    addl    %eax, %eax
    ret

基于对John Knoeller的回答的评论,您似乎没有意识到GCC的命令行选项区分大小写 Capital O选项(-O1-O2-O3-Os)启用优化;小写o选项(-o whatever)指定输出文件。您的-o2 -othing构造会默默地忽略-o2部分并写入thing