2010-03-08 13 views
5

di recente, sto cercando in codici di assemblaggio per #define, const e enum:aiuto per capire le differenze tra #define, const e enum in C e C++ a livello di montaggio

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

codici assembly (per la linea 6 e 7 codici c) generato da 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) 

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

codici assembly (per la linea 4 e 6 codici c) generato da 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) 

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

codici assembly (per la linea 7 e 8 in c codici) generati da 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) 

ho trovato che l'uso #define e enum, l'asse i codici mbly sono gli stessi. Il compilatore usa 3 istruzioni per eseguire la moltiplicazione. Tuttavia, quando si utilizza const, viene utilizzata l'istruzione imul. Qualcuno sa il motivo dietro a ciò?

+0

Quale versione di gcc e quali flag di ottimizzazione? –

+0

gcc (GCC) 4.4.2 20091222 (Red Hat 4.4.2-20) Non ho specificato il flag di ottimizzazione (predefinito). – martin

risposta

7

La differenza è che con #define o enum il valore 3 non ha bisogno di esistere come un valore esplicito nel codice e quindi il compilatore ha deciso di utilizzare due aggiungere istruzioni piuttosto che allocare spazio per la costante 3. Il add reg,reg l'istruzione è di 2 byte per istruzione, quindi i 6 byte di istruzioni e 0 byte per le costanti moltiplicano per 3, questo è il codice più piccolo di imul più una costante di 4 byte. Oltre al modo in cui vengono utilizzate le istruzioni di aggiunta, si risolve in una traduzione letterale di * 2 * 3, quindi questa potrebbe non essere un'ottimizzazione della dimensione, potrebbe essere l'output del compilatore predefinito ogni volta che si moltiplica per 2 o per 3. (aggiungere di solito è un'istruzione più rapida di quella moltiplicata).

#define e enum non dichiarare un'istanza, forniscono solo un modo per assegnare un nome simbolico al valore 3, in modo che il compilatore abbia la possibilità di creare un codice più piccolo.

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 

Ma quando si dichiara const int pi = 3, si indica al compilatore di allocare spazio per un valore intero e inizializzarlo con 3. Tale utilizza 4 byte, ma la costante è ora disponibile per utilizzare come operando per l'istruzione 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 

A proposito, questo è chiaramente codice non ottimizzato.Poiché il valore a viene mai usato, quindi, se l'ottimizzazione è stato attivato, il compilatore sarebbe solo eseguire

xor eax, eax ; return 0 

In tutti i 3 casi.

Addendum:

ho provato questo con MSVC e in modalità debug ho la stessa uscita per tutti e 3 i casi, MSVC utilizza sempre IMUL da un letterale 6. Anche nel caso 3 quando si crea il const int = 3 doesn' in realtà lo riferimento nell'imul.

Non penso che questo test ti dica davvero qualcosa su const vs define vs enum perché questo è un codice non ottimizzato.

+3

+1; Riguardo l'ultimo paragrafo; questo è dove C++ differisce da C nella semantica 'const'. C++ si comporterà come se pi fosse un letterale a meno che non si prenda il suo indirizzo, quindi dovrei credere di ottenere lo stesso codice in tutti e tre i casi per la compilazione C++. – Clifford

+0

@Clifford: quando accendo le ottimizzazioni complete sul mio compilatore, il programma si trasforma in 'return 0' poiché a non viene mai usato. –

+0

ma perché aggiungere può essere utilizzato, imul non può? mi piacerebbe sapere il motivo dietro questo? come quale tipo di istruzioni possono essere usate quando si usa #define ed enum, quali istruzioni non possono o ciò dipende dal compilatore che ho usato? se è vero, dipende dal meccanismo del compilatore? – martin

0

La parola chiave const si limita a indicare che il particolare file ad esso acceduto non è autorizzato a modificarlo, ma altri moduli possono modificare o definire il valore. Pertanto, turni e multipli non sono consentiti, poiché il valore non è noto in anticipo. I valori # define vengono semplicemente sostituiti con il valore letterale dopo la pre-elaborazione, in modo che il compilatore possa analizzarlo in fase di compilazione. Però non sono del tutto sicuro delle enumerazioni.

+0

La prima istruzione è vera per gli argomenti della funzione 'const', ma non per le variabili locali come in questo esempio; per definizione un modulo o una funzione esterna non può modificarlo. – Clifford

+0

secondo la tua spiegazione, quando si usa const, l'istruzione imul non può essere usata, ma perché viene usata? – martin

+0

Avrei dovuto dire turni e * aggiunge *, non cambia e moltiplica.Ad esempio, per moltiplicare per 13, puoi suddividere il 13 in (x << 3) + (x << 1) + x, ma per avere una soluzione per ogni possibile caso in un ex est occorrerebbe la piena moltiplicazione del software routine, e l'hardware per farlo è più veloce e già disponibile. –

0

Nell'ultimo caso, il compilatore considera pi una variabile piuttosto che una costante letterale. Potrebbe essere possibile per il compilatore ottimizzarlo con diverse opzioni del compilatore.

[modifica] Nota l'intero frammento come scritto può essere ottimizzato poiché a è assegnato ma non utilizzato; dichiarare a come volatile per evitare che ciò si verifichi.

La semantica di const in C++ differisce da quella in C in modo sottile, ho il sospetto che si otterrebbe un risultato diverso con la compilazione C++.

0

Quando viene compilato come C++, codice identico viene generato a quello prodotto quando compilato con C, almeno 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) 

Lo stesso codice viene emesso se pi è definito come:

#define pi 3 
+0

Mi chiedo perché il compilatore non usi 'lea eax, [eax * 2 + eax]' per moltiplicare per 3 passi. – Skizz

0

Sembra che tu non abbia effettivamente attivato l'ottimizzatore, anche quando pensavi di averlo fatto. Ho compilato questo:

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 di Apple 4.2 (piuttosto vecchio del compilatore utilizzato); Posso riprodurre l'assemblaggio Tu dici che hai quando uso nessuna ottimizzazione (il default), ma ad ogni livello di ottimizzazione maggiore, io ottiene il codice identico per tutti e tre, e il codice identico se compilato come C o C++:

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

In base ai commenti sulla risposta di John Knoeller, sembra che non ci si sia resi conto che le opzioni della riga di comando di GCC sono case sensitive. Capitale O opzioni (-O1, -O2, -O3, -Os) attiva l'ottimizzazione; le opzioni in lettere minuscole o (-o whatever) specificano il file di output. Il tuo costrutto -o2 -othing ignora in silenzio la parte -o2 e scrive su thing.