tags:

views:

398

answers:

4

recently, i am looking into assembly codes for #define, const and enum:

C codes(#define):

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

assembly codes(for line 6 and 7 in c codes) generated by GCC:

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 codes(enum):

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

assembly codes(for line 4 and 6 in c codes) generated by GCC:

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 codes(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  }

assembly codes(for line 7 and 8 in c codes) generated by GCC:

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)

i found that use #define and enum, the assembly codes are the same. The compiler use 3 add instructions to perform multiplication. However, when use const, imul instruction is used. Anyone knows the reason behind that?

A: 

The const keyword merely states that the particular file accessing it isn't allowed to modify it, but other modules may modify or define the value. Therefore, shifts and multiplies aren't allowed, since the value isn't known in advance. The #define'ed values are simply replaced with the literal value after preprocessing, so the compiler can analyze it at compile time. I'm not entirely sure about enums though.

Arthur Kalliokoski
Your first statement is true for `const` function arguments, but not local variables as in this example; by definition an external module or function cannot modify it.
Clifford
according to your explanation, when using const, imul instruction can not be used, but why it is used?
martin
I should have said shifts and *adds*, not shifts and multiplies. For instance, to multiply by 13, you can break the 13 down into (x << 3) + (x << 1) + x, but to have a solution for every possible case in an extern int would require the full software multiplication routine, and the hardware to do that is faster and already available.
Arthur Kalliokoski
+6  A: 

The difference is that with #define or enum the value 3 doesn't need to exist as an explicit value in the code and so the compiler has decided to use two add instructions rather than allocating space for the constant 3. The add reg,reg instruction is 2 bytes per instruction, so thats 6 bytes of instructions and 0 bytes for constants to multiply by 3, that's smaller code than imul plus a 4 byte constant. Plus the way the add instructions are used, it works out to a pretty literal translation of *2 *3, so this may not be a size optimization, it may be the default compiler output whenever you multiply by 2 or by 3. (add is usually a faster instruction than multiply).

#define and enum don't declare an instance, they only provide a way to give a symbolic name to the value 3, so the compiler has the option of making smaller code.

  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

But when you declare int const = 3, you tell the compiler to allocate space for an integer value and initialize it with 3. That uses 4 bytes, but the constant is now available to use as an operand for the imul instruction.

 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

By the way, this is clearly not optimized code. Because the value a is never used, so if optimization were turned on, the compiler would just execute

xor eax, eax  ; return 0

In all 3 cases.

Addendum:

I tried this with MSVC and in debug mode I get the same output for all 3 cases, MSVC always uses imul by a literal 6. Even in case 3 when it creates the const int = 3 it doesn't actually reference it in the imul.

I don't think this test really tells you anything about const vs define vs enum because this is non-optimized code.

John Knoeller
+1; Regarding the last paragraph; this is where C++ differs from C in `const` semantics. C++ will behave as if pi were a literal unless you take its address, so he should I believe get the same code in all three cases for C++ compilation.
Clifford
@Clifford: when I turn on full optimizations on my compiler, the program turns into `return 0` since a is never used.
John Knoeller
martin
@martin: I wouldn't worry too much about it. The results will be very different if you turn on the optimizer.
John Knoeller
Regarding assignment of `a` being optimised out, declare it `volatile` and that will prevent that. Regarding Martin's concerns; the whole point of using a compiler is not to have to worry about assembler - let the compiler do its job I say ;-).
Clifford
@John Knoeller:i tried to use optimization flags, but i get the same output. gcc -g -o3 -o progC testC.c, is this correct? i tried -o1,-o2,os, but all the outputs are same. How come....?
martin
@martin: Optimizers vary, it was only ever a possibility that it would change the code not a requirement. Did you try C++ compilation?
Clifford
@Clifford: yes, i tried. but nothing changes...
martin
@martin: I am surprised at that; oh well.
Clifford
A: 

In the last case, the compiler treats pi as a variable rather than a literal constant. It may be possible for the compiler to optimise that out with different compiler options.

[edit]Note the entire fragment as written can be optimised out since a is assigned but not used; declare a as a volatile to prevent that occurring.

The semantics of const in C++ differ from that in C in subtle ways, I suspect you'd get a different result with C++ compilation.

Clifford
A: 

When compiled as C++, identical code is generated to that produced when compiled with C, at least with 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)

The same code is emitted if pi is defined as:

#define pi 3
anon
I wonder why the compiler doesn't use `lea eax,[eax*2+eax]` for the multiply by 3 step.
Skizz