2009-02-10 4 views
27

Inspiriert von der Frage Difference in initalizing and zeroing an array in c/c++ ?, entschied ich mich, die Assembly von, in meinem Fall, eine optimierte Version Build für Windows Mobile Professional (ARM-Prozessor, aus dem Microsoft Optimizing Compiler) zu untersuchen. Was ich fand, war etwas überraschend, und ich frage mich, ob jemand meine Fragen in Bezug darauf klären kann.Seltsame Assembly aus Array 0-Initialisierung

Diese beiden Beispiele werden untersucht:

byte a[10] = { 0 }; 

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

Sie in der gleichen Funktion verwendet werden, so dass der Stapel sieht wie folgt aus:

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

Die generierte Assembly mit meinen Kommentaren:

; 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 

Nun, es gibt zwei Dinge, die mich verwirrt:

  1. Was ist der Sinn der Zeilen 02 und 05? Warum geben Sie nicht einfach & a [0] und 10 Bytes zu memset?
  2. Warum sind die Füllbytes von 0 nicht initialisiert? Ist das nur für das Auffüllen in Strukturen?

Edit: Ich war zu neugierig, um nicht die Struktur Fall zu testen:

struct Padded 
{ 
    DWORD x; 
    byte y; 
}; 

Der Assembler für 0-Initialisierung es:

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

Hier sehen wir in 04 Linie, dass eine Polsterung tatsächlich auftreten, da str (im Gegensatz zu strb) verwendet wird. Recht?

+1

Keine Ahnung, aber große Frage –

+0

Nun, nach dem Lesen der Kommentare unten, scheint es, dass msvc ist nur nicht sehr konsequent über Null-Speicher. –

Antwort

13

Der Grund für die Zeilen 2 und 5 liegt darin, dass Sie im Arrayinitialisierer eine 0 angegeben haben. Der Compiler wird alle Konstanten initialisieren und dann den Rest mit memset auffüllen. Wenn Sie Ihrem Initialisierer zwei Nullen geben würden, würden Sie strw (Wort statt Byte) sehen und dann 8 Bytes schreiben.

Wie für die Auffüllung wird es nur verwendet, um Speicherzugriffe auszurichten - die Daten sollten unter normalen Umständen nicht verwendet werden, so dass die Speicherung verschwenderisch ist.

Edit: Für die Aufzeichnung, kann ich falsch sein über die obige Annahme Strw. 99% meiner Erfahrung mit ARM ist die Umkehrung von Code, der von GCC/LLVM auf dem iPhone generiert wurde, sodass meine Annahme nicht auf MSVC übertragen werden kann.

11

Beide Codebits sind fehlerfrei. Die beiden erwähnten Zeilen sind nicht schlau, aber Sie beweisen nur, dass dieser Compiler suboptimalen Code ausgibt.

Padding-Bytes werden normalerweise nur initialisiert, wenn dies die Montage vereinfacht oder den Code beschleunigt. Wenn z. B. zwischen zwei mit Null gefüllten Elementen gepolstert wird, ist es oft einfacher, das Padding ebenfalls auf Null zu setzen. Wenn Sie am Ende ein Padding haben und Ihr memset() für Multi-Byte-Schreibvorgänge optimiert ist, kann es auch schneller sein, dieses Padding zu überschreiben.

+2

Eigentlich könnte dieser Code sehr gut optimal sein. Die Art und Weise, wie Anweisungen auf ARM gepipeled werden, könnte es einfacher machen, strb zu betreiben, dann abzuzweigen und zu loopen. Das heißt, der Leistungsunterschied wäre wahrscheinlich vernachlässigbar, und Sie verwenden zusätzliche 4 Bytes, also wer weiß. –

+3

Unwahrscheinlich. Sie haben nicht ausgerichtete Speicherzugriffe (ein Byte und 9 Bytes - ARM hat oft einen 16-Bit-Bus. Das bedeutet lesen/ändern/schreiben!). Außerdem haben Sie zusätzlichen Registerdruck: Sie brauchen auch R3. – MSalters

8

Einige schnelle Tests zeigen an, dass der Microsoft x86-Compiler eine andere Assembly generiert, wenn die Initialisierungsliste leer ist, im Vergleich dazu, wenn sie eine Null enthält. Vielleicht tut das auch ihr ARM-Compiler. Was passiert, wenn du das tust?

byte a[10] = { }; 

Hier ist die Assembler-Liste Ich habe (mit Optionen /EHsc /FAs /O2 auf Visual Studio 2008). Beachten Sie, dass eine Null in der Initialisiererliste einschließlich der Compiler verursacht unaligned Speicher zu verwenden, greift auf das Array zu initialisieren, während die leere Initialisiererliste-Version und die memset() Version verwenden beide ausgerichtet Speicherzugriffe:

; 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 !! Warum in aller Welt macht es das? : P zumindest würden Sie erwarten, dass die explizite 0-Initialisierung zuerst den Wert in al zu allen Bytes in eax kopiert. Es ist, als ob eine Optimierung für die explizite Initialisierung mit 0 zur Hälfte gemacht wurde. –