2009-02-10 18 views
27

Ispirato dalla domanda Difference in initalizing and zeroing an array in c/c++ ?, ho deciso di esaminare effettivamente l'assemblaggio di, nel mio caso, una build di rilascio ottimizzata per Windows Mobile Professional (processore ARM, da Microsoft Optimizing Compiler). Ciò che ho trovato è stato piuttosto sorprendente, e mi chiedo se qualcuno possa far luce sulle mie domande a riguardo.Strange assembly dall'inizializzazione dell'array 0

Questi due esempi vengono esaminati:

byte a[10] = { 0 }; 

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

Essi sono utilizzati nella stessa funzione, così lo stack aspetto:

[ ] // 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) 

L'assembly generato con i miei commenti:

; 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 

Ora, ci sono due cose che mi confondono:

  1. Qual è il punto delle linee 02 e 05? Perché non dare semplicemente & a [0] e 10 byte a memset?
  2. Perché i byte di padding di 0 non sono inizializzati? È solo per il riempimento nelle strutture?

Edit: ero troppo curioso di non testare il caso struct:

struct Padded 
{ 
    DWORD x; 
    byte y; 
}; 

L'assembler per 0-inizializzazione:

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

Qui vediamo in linea 04 che un'imbottitura effettivamente si verificano, poiché viene utilizzato str (al contrario di strb). Destra?

+1

Non ne ho idea, ma grande domanda –

+0

beh, dopo aver letto i commenti di seguito, sembra che msvc non sia molto coerente sull'azzeramento della memoria. –

risposta

13

Il motivo per le righe 2 e 5 è perché è stato specificato 0 nell'inizializzatore di array. Il compilatore inizializzerà tutte le costanti e recupererà il resto usando memset. Se dovessi inserire due zeri nel tuo inizializzatore, lo vedresti strw (parola anziché byte) quindi memset 8 byte.

Come per il padding, è usato solo per allineare gli accessi alla memoria - i dati non dovrebbero essere usati in circostanze normali, quindi memsetting è uno spreco.

Modifica: Per la cronaca, potrei sbagliarmi riguardo la precedente ipotesi. Il 99% della mia esperienza ARM sta invertendo il codice generato da GCC/LLVM su iPhone, quindi la mia ipotesi potrebbe non essere trasferita a MSVC.

11

Entrambi i bit di codice non contengono errori. Le due linee menzionate non sono intelligenti, ma stai solo dimostrando che questo compilatore sta emettendo un codice subottimale.

I byte di riempimento vengono generalmente inizializzati solo se ciò semplifica l'assemblaggio o velocizza il codice. Ad esempio, se si dispone di un riempimento tra due membri pieni di zero, è spesso più facile riempire a zero anche il padding. Inoltre, se hai il padding alla fine e il tuo memset() è ottimizzato per le scritture multi-byte, potrebbe essere più veloce sovrascrivere anche quel padding.

+2

In realtà, questo codice molto bene potrebbe essere ottimale. Il modo in cui le istruzioni sono pipeline su ARM potrebbe facilmente renderlo più efficiente per strb quindi diramazioni e loop. Detto questo, la differenza di prestazioni sarebbe probabilmente trascurabile e stai utilizzando 4 byte in più, quindi chi lo sa. –

+3

Improbabile. Hai accesso alla memoria non allineato (un byte e 9 byte - ARM ha spesso un bus a 16 bit. Ciò significa leggere/modificare/scrivere!). Inoltre, hai una pressione extra di registro: hai bisogno anche di R3. – MSalters

8

Alcuni test rapidi indicano che il compilatore x86 di Microsoft genera assembly diversi se l'elenco di inizializzazione è vuoto, rispetto a quando contiene uno zero. Forse anche il loro compilatore ARM. Cosa succede se lo fai?

byte a[10] = { }; 

Ecco l'elenco di montaggio ho ottenuto (con opzioni /EHsc /FAs /O2 su Visual Studio 2008). Si noti che tra cui uno zero nella lista di inizializzazione fa sì che il compilatore di utilizzare la memoria non allineati accessi per inizializzare l'array, mentre la versione lista di inizializzazione vuota e la versione memset() sia la memoria allineato uso accessi:

; 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 !! perché mai lo fa? : P almeno ci si aspetta che l'inizializzazione 0 esplicita copi prima il valore in al, a tutti i byte in eax. è come se l'ottimizzazione fosse stata eseguita a metà per l'inizializzazione esplicita usando 0. –