最近,我正在研究#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)
我发现使用#define
和enum
,汇编代码是相同的。编译器使用3个添加指令来执行乘法。但是,使用const
时,会使用imul指令。
谁知道背后的原因?
答案 0 :(得分:7)
不同之处在于,对于#define
或enum
,值3不需要作为代码中的显式值存在,因此编译器决定使用两个添加指令而不是分配空间对于常量3. add reg,reg
指令是每个指令2个字节,因此6个字节的指令和0个字节的常量乘以3,这是比imul
加上4个字节常量更小的代码。再加上使用添加指令的方式,它可以实现* 2 * 3的非常直译,因此这可能不是大小优化,只要乘以2或3,它就可以是默认的编译器输出。通常是比乘法更快的指令)。
#define
和enum
没有声明实例,它们只提供了为值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
。