2010-03-08 12 views
5

recientemente, estoy buscando en códigos de montaje para #define, const y enumeración:ayuda para entender las diferencias entre #define, const y enum en C y C++ en el nivel de conjunto

códigos 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 } 

códigos de montaje (para la línea 6 y 7 en los códigos c) generado por 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ódigos C (enum):

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

códigos de montaje (para la línea 4 y 6 en los códigos c) generado por 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ódigos 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 } 

códigos de montaje (para la línea 7 y 8 en c códigos) generados por 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 encontraron que el uso #define y enum, la asse Los códigos de Mbly son iguales. El compilador usa 3 instrucciones de agregar para realizar la multiplicación. Sin embargo, cuando se usa const, se usa la instrucción imul. ¿Alguien sabe la razón detrás de eso?

+0

¿Qué versión de gcc y qué indicadores de optimización? –

+0

gcc (GCC) 4.4.2 20091222 (Red Hat 4.4.2-20) No especifiqué el indicador de optimización (predeterminado). – martin

Respuesta

7

La diferencia es que con #define o enum el valor 3 no tiene por qué existir como un valor explícito en el código por lo que el compilador ha decidido utilizar dos añadir instrucciones en lugar de asignar espacio para la constante 3. El add reg,reg la instrucción es de 2 bytes por instrucción, entonces eso es 6 bytes de instrucciones y 0 bytes para que las constantes se multipliquen por 3, es un código más pequeño que imul más una constante de 4 bytes. Además de la forma en que se usan las instrucciones add, funciona con una traducción bastante literal de * 2 * 3, por lo que puede no ser una optimización de tamaño, puede ser la salida predeterminada del compilador siempre que multiplique por 2 o por 3. (agregue generalmente es una instrucción más rápida que multiplicar).

#define y enum no declaran una instancia, solo proporcionan una forma de dar un nombre simbólico al valor 3, por lo que el compilador tiene la opción de hacer código más pequeño.

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 

Pero cuando se declara const int pi = 3, le dice al compilador para asignar espacio para un valor entero e inicializar con 3. Que utiliza 4 bytes, pero la constante es ahora disponible para su uso como un operando para la instrucción 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 

Por cierto, este código claramente no está optimizado.Debido a que el valor a nunca se utiliza, por lo que si la optimización se enciende, el compilador sólo hay que ejecutar

xor eax, eax ; return 0 

En los 3 casos.

Adición:

He intentado esto con MSVC y en modo de depuración tengo la misma salida para los 3 casos, siempre utiliza MSVC imul por un literal 6. Incluso en el caso 3 cuando crea el const int = 3 que doesn' en realidad lo referencia en el imul.

No creo que esta prueba realmente te diga nada acerca de const vs define vs enum porque este es un código no optimizado.

+3

+1; En cuanto al último párrafo; aquí es donde C++ difiere de C en la semántica 'const'. C++ se comportará como si pi fuera un literal a menos que tome su dirección, por lo que debería creer que obtenga el mismo código en los tres casos para la compilación de C++. – Clifford

+0

@Clifford: cuando activo optimizaciones completas en mi compilador, el programa se convierte en 'return 0' ya que a nunca se usa. –

+0

, pero ¿por qué agregar se puede utilizar, imul no? Me gustaría saber el motivo detrás de eso? como qué tipo de instrucciones se pueden usar al usar #define & enum, ¿qué instrucciones no pueden o esto depende del compilador que utilicé? si es verdad, ¿depende del mecanismo del compilador? – martin

0

La palabra clave const simplemente indica que el archivo particular que tiene acceso a ella no puede modificarla, pero otros módulos pueden modificar o definir el valor. Por lo tanto, los turnos y las multiplicaciones no están permitidos, ya que el valor no se conoce de antemano. Los valores # defined'ed simplemente se reemplazan con el valor literal después del preprocesamiento, por lo que el compilador puede analizarlo en tiempo de compilación. Aunque no estoy del todo seguro sobre enums.

+0

Su primera afirmación es verdadera para los argumentos de la función 'const', pero no para las variables locales como en este ejemplo; por definición, un módulo externo o función no puede modificarlo. – Clifford

+0

de acuerdo con su explicación, cuando se usa const, la instrucción imul no se puede usar, pero ¿por qué se usa? – martin

+0

Debería haber dicho cambios y * agregar *, no cambios y multiplicaciones.Por ejemplo, para multiplicar por 13, puede dividir el 13 en (x << 3) + (x << 1) + x, pero para tener una solución para cada caso posible en un extern int se requeriría la multiplicación completa del software rutina, y el hardware para hacerlo es más rápido y ya está disponible. –

0

En el último caso, el compilador trata a pi como una variable en lugar de una constante literal. Es posible que el compilador optimice eso con diferentes opciones de compilación.

[editar] Tenga en cuenta que todo el fragmento tal como está escrito se puede optimizar ya que a está asignado pero no se utiliza; declare a como volatile para evitar que eso ocurra.

La semántica de const en C++ difiere de manera sutil en C, sospecho que obtendría un resultado diferente con la compilación de C++.

0

Cuando se compila como C++, código idénticos se genera a la producida cuando se compila con C, al menos con 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) 

El mismo código se emite si pi se define como:

#define pi 3 
+0

Me pregunto por qué el compilador no usa 'lea eax, [eax * 2 + eax]' para multiplicar por 3 pasos. – Skizz

0

Parece que en realidad no enciende el optimizador, incluso cuando creía que lo hacía. Recopilé esto:

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; 
} 

... con gcc 4.2 de Apple (bastante más antiguo que el compilador que usaste); Puedo reproducir el montaje se dice que recibió cuando utilizo ninguna optimización (por defecto), pero en cualquier nivel de optimización superior, que tienen el código idéntico para los tres, y el código de forma idéntica tanto si compilado como C o C++:

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

Según los comentarios sobre la respuesta de John Knoeller, parece que no se dio cuenta de que las opciones de línea de comando de GCC son , que distingue entre mayúsculas y minúsculas. Capital O opciones (-O1, -O2, -O3, -Os) activar la optimización; las opciones minúsculas o (-o whatever) especifican el archivo de salida. Su construcción -o2 -othing ignora silenciosamente la parte -o2 y escribe en thing.

Cuestiones relacionadas