2009-02-10 16 views
27

, Inspirado por la pregunta Difference in initalizing and zeroing an array in c/c++ ?, decidí examinar realmente el ensamblado de, en mi caso, una versión de lanzamiento optimizada para Windows Mobile Professional (procesador ARM, del compilador de optimización de Microsoft). Lo que encontré fue algo sorprendente, y me pregunto si alguien puede arrojar algo de luz sobre mis preguntas al respecto.Montaje extraño de array 0-initialization

Estos dos ejemplos son examinados:

byte a[10] = { 0 }; 

byte b[10]; 
memset(b, 0, sizeof(b)); 

Se utilizan en la misma función, por lo que la pila se ve así:

[ ] // padding byte to reach DWORD boundary 
[ ] // padding byte to reach DWORD boundary 
[ ] // b[9] (last element of b) 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] // b[0] = sp + 12 (stack pointer + 12 bytes) 
[ ] // padding byte to reach DWORD boundary 
[ ] // padding byte to reach DWORD boundary 
[ ] // a[9] (last element of a) 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] 
[ ] // a[0] = sp (stack pointer, at bottom) 

El ensamblado generado con mis comentarios:

; byte a[10] = { 0 }; 

01: mov r3, #0  // r3 = 0 
02: mov r2, #9  // 3rd arg to memset: 9 bytes, note that sizeof(a) = 10 
03: mov r1, #0  // 2nd arg to memset: 0-initializer 
04: add r0, sp, #1 // 1st arg to memset: &a[1] = a + 1, since only 9 bytes will be set 
05: strb r3, [sp]  // a[0] = r3 = 0, sets the first element of a 
06: bl memset  // continue in memset 

; byte b[10]; 
; memset(b, 0, sizeof(b)); 

07: mov r2, #0xA  // 3rd arg to memset: 10 bytes, sizeof(b) 
08: mov r1, #0  // 2nd arg to memset: 0-initializer 
09: add r0, sp, #0xC // 1st arg to memset: sp + 12 bytes (the 10 elements 
         // of a + 2 padding bytes for alignment) = &b[0] 
10: bl memset  // continue in memset 

Ahora, hay dos cosas que me confunden:

  1. ¿Qué sentido tienen las líneas 02 y 05? ¿Por qué no simplemente dar & a [0] y 10 bytes a memset?
  2. ¿Por qué los bytes de relleno de 0 no se inicializan? ¿Eso es solo para rellenar las estructuras?

Edit: era demasiado curioso para no probar el caso struct:

struct Padded 
{ 
    DWORD x; 
    byte y; 
}; 

El ensamblador para 0-inicializarlo:

; Padded p1 = { 0 }; 

01: mov r3, #0 
02: str r3, [sp] 
03: mov r3, #0 
04: str r3, [sp, #4] 

; Padded p2; 
; memset(&p2, 0, sizeof(p2)); 

05: mov r3, #0 
06: str r3, [sp] 
07: andcs r4, r0, #0xFF 
08: str r3, [sp, #4] 

Aquí vemos en la línea 04 que un acolchado de hecho ocurre, ya que se usa str (a diferencia de strb). ¿Derecha?

+1

Ni idea, pero gran pregunta –

+0

Bueno, después de leer los comentarios a continuación, parece que msvc simplemente no es muy consistente acerca de la reducción a cero de la memoria. –

Respuesta

13

El motivo de las líneas 2 y 5 se debe a que ha especificado un 0 en el inicializador de la matriz. El compilador inicializará todas las constantes y luego rellenará el resto utilizando memset. Si tuviera que poner dos ceros en su inicializador, lo vería strw (palabra en lugar de byte) y luego memset 8 bytes.

En cuanto al relleno, solo se usa para alinear los accesos a la memoria; los datos no deben usarse bajo circunstancias normales, por lo que configurarlos es un desperdicio.

Editar: Para el registro, puedo estar equivocado sobre la suposición de strw anterior. El 99% de mi experiencia ARM es revertir el código generado por GCC/LLVM en el iPhone, por lo que mi suposición puede no trasladarse a MSVC.

11

Ambos bits de código no tienen errores. Las dos líneas mencionadas no son inteligentes, pero estás probando que este compilador está emitiendo un código que no es óptimo.

Los bytes de relleno usualmente solo se inicializan si eso simplifica el ensamblaje o acelera el código. Por ejemplo, si tiene relleno entre dos miembros llenos a cero, a menudo también es más fácil llenar el relleno a cero. Además, si tiene relleno al final y su memset() está optimizado para escrituras de varios bytes, puede ser más rápido sobrescribir ese relleno también.

+2

En realidad, este código muy bien podría ser óptimo. La forma en que las instrucciones se canalizan en ARM podría hacer que sea más eficiente rastrear y luego bifurcar. Dicho esto, la diferencia de rendimiento probablemente sea insignificante, y estás usando 4 bytes adicionales, así que quién sabe. –

+3

Poco probable. Tiene accesos de memoria no alineados (un byte y 9 bytes - ARM a menudo tiene un bus de 16 bits. Eso significa leer/modificar/escribir!). Además, tienes presión de registro adicional: también necesitas R3. – MSalters

8

Algunas pruebas rápidas indican que el compilador x86 de Microsoft genera un ensamblaje diferente si la lista de inicializadores está vacía, en comparación con cuando contiene un cero. Quizás su compilador ARM también lo haga. ¿Qué pasa si haces esto?

byte a[10] = { }; 

Aquí está la lista de montaje llegué (con opciones /EHsc /FAs /O2 sobre Visual Studio 2008). Tenga en cuenta que la inclusión de un cero en la lista de inicialización hace que el compilador utilizar los accesos a memoria no alineada para inicializar la matriz, mientras que la versión lista de inicialización vacía y la versión memset() tanto el uso de memoria alineado accesos:

; unsigned char a[10] = { }; 

xor eax, eax 
mov DWORD PTR _a$[esp+40], eax 
mov DWORD PTR _a$[esp+44], eax 
mov WORD PTR _a$[esp+48], ax 

; unsigned char b[10] = { 0 }; 

mov BYTE PTR _b$[esp+40], al 
mov DWORD PTR _b$[esp+41], eax 
mov DWORD PTR _b$[esp+45], eax 
mov BYTE PTR _b$[esp+49], al 

; unsigned char c[10]; 
; memset(c, 0, sizeof(c)); 

mov DWORD PTR _c$[esp+40], eax 
mov DWORD PTR _c$[esp+44], eax 
mov WORD PTR _c$[esp+48], ax 
+1

wooh !! ¿Por qué demonios hace eso? : P al menos esperaría que la inicialización 0 explícita copie primero el valor en al, a todos los bytes en eax. es como si la optimización estuviera a medio hacer para la inicialización explícita usando 0. –