2012-03-23 6 views
7

Così di recente stavo pensando di strcpy e di nuovo a K & R dove si mostra l'applicazione comePerché vs C++ 2010 compilatore produrre un codice assembly diverso per funzione simile

while (*dst++ = *src++) ; 

Tuttavia ho erroneamente trascritto come:

while (*dst = *src) 
{ 
    src++; //technically could be ++src on these lines 
    dst++; 
} 

In ogni caso questo mi ha fatto pensare se il compilatore avrebbe effettivamente prodotto un codice diverso per questi due. Il mio pensiero iniziale è che dovrebbero essere quasi identici, dal momento che src e dst vengono incrementati ma mai usati. Ho pensato che il compilatore avrebbe saputo di non tentare di conservarli acutamente come "variabili" nel codice macchina prodotto.

Utilizzo di windows7 con VS 2010 C++ SP1 in modalità di rilascio a 32 bit (/ O2), ho ottenuto il codice di disassemblaggio per entrambe le incarnazioni precedenti. Per evitare che la funzione stessa faccia riferimento direttamente all'input e sia in linea, ho creato una dll con ognuna delle funzioni. Ho omesso il prologo e l'epilogo dell'ASM prodotto.

while (*dst++ = *src++) 
6EBB1003 8B 55 08    mov   edx,dword ptr [src]  
6EBB1006 8B 45 0C    mov   eax,dword ptr [dst]  
6EBB1009 2B D0    sub   edx,eax    //prepare edx so that edx + eax always points to src  
6EBB100B EB 03    jmp   docopy+10h (6EBB1010h) 
6EBB100D 8D 49 00    lea   ecx,[ecx]    //looks like align padding, never hit this line 
6EBB1010 8A 0C 02    mov   cl,byte ptr [edx+eax] //ptr [edx+ eax] points to char in src :loop begin 
6EBB1013 88 08    mov   byte ptr [eax],cl  //copy char to dst 
6EBB1015 40     inc   eax     //inc src ptr 
6EBB1016 84 C9    test  cl,cl     // check for 0 (null terminator) 
6EBB1018 75 F6    jne   docopy+10h (6EBB1010h) //if not goto :loop begin 
     ; 

Sopra ho annotato il codice, essenzialmente un singolo ciclo, solo 1 controllo per null e 1 copia di memoria.

Vediamo ora la mia versione errore:

while (*dst = *src) 
6EBB1003 8B 55 08    mov   edx,dword ptr [src] 
6EBB1006 8A 0A    mov   cl,byte ptr [edx] 
6EBB1008 8B 45 0C    mov   eax,dword ptr [dst] 
6EBB100B 88 08    mov   byte ptr [eax],cl  //copy 0th char to dst 
6EBB100D 84 C9    test  cl,cl     //check for 0 
6EBB100F 74 0D    je   docopy+1Eh (6EBB101Eh) // return if we encounter null terminator 
6EBB1011 2B D0    sub   edx,eax 
6EBB1013 8A 4C 02 01   mov   cl,byte ptr [edx+eax+1] //get +1th char :loop begin 
    { 
     src++; 
     dst++; 
6EBB1017 40     inc   eax     
6EBB1018 88 08    mov   byte ptr [eax],cl  //copy above char to dst 
6EBB101A 84 C9    test  cl,cl     //check for 0 
6EBB101C 75 F5    jne   docopy+13h (6EBB1013h) // if not goto :loop begin 
    } 

Nella mia versione, vedo che le prime copie del char 0a alla destinazione, poi i controlli per nulla, e poi finalmente entra nel ciclo in cui esso controlla per null di nuovo. Quindi il ciclo rimane in gran parte lo stesso, ma ora gestisce lo 0 ° carattere prima del ciclo. Questo ovviamente sarà subottimale rispetto al primo caso.

Mi chiedo se qualcuno sa perché al compilatore viene impedito di creare lo stesso codice (o quasi uguale) del primo esempio. Si tratta di un problema specifico del compilatore di ms o forse delle impostazioni del mio compilatore/linker?


qui è il codice completo, 2 file (1 funzione sostituisce l'altro).

// in first dll project 
__declspec(dllexport) void docopy(const char* src, char* dst) 
{ 
    while (*dst++ = *src++); 
} 

__declspec(dllexport) void docopy(const char* src, char* dst) 
{ 
    while (*dst = *src) 
    { 
     ++src; 
     ++dst; 
    } 
} 


//seprate main.cpp file calls docopy 
void docopy(const char* src, char* dst); 
char* source ="source"; 
char destination[100]; 
int main() 
{ 

    docopy(source, destination); 
} 
+0

Potresti pubblicare per favore gli interi pezzi di codice C con cui hai iniziato? Potrebbe essere diverso a causa delle dichiarazioni di src & dst, ma non sarei in grado di saperlo. I preamboli degli assemblatori che hai rimosso sono identici? Non è necessario incollarli se lo sono. – gbulmer

+0

In entrambi i casi questo è l'intero codice, tranne che per l'epilogo e il prologo. La dichiarazione di entrambe le funzioni è __declspec (dllexport) void docopy (char * src, char * dst) – skimon

+0

Questo è uno stile di codifica errato, perché molti lettori vedranno il segno "=" nell'espressione come errore di battitura per "==". –

risposta

10

Perché nel primo esempio, il post-incremento avviene sempre, anche se src inizia puntando a un carattere nullo. Nella stessa situazione di partenza, il secondo esempio non incrementerebbe i puntatori.

+0

Ah, capisco cosa intendi e sono incline ad accettare la tua risposta. Il compilatore non dovrebbe essere in grado di vedere che dst e src che vengono incrementati non fa differenza qui poiché non vengono utilizzati se non nel contesto della copia e del confronto in mentre, essenzialmente, rendono la logica uguale al primo esempio? – skimon

+0

In teoria, se 'src' e' dst' sono locali e non vengono riutilizzati dopo il ciclo, allora sì, l'ottimizzatore potrebbe fare l'ipotesi. Forse un ottimizzatore più aggressivo lo farebbe. D'altra parte, l'ottimizzatore non ha la versione "altra" da confrontare. Data la seconda fonte, non c'è davvero nulla da suggerire che potrebbe aggiungere un incremento "extra" per l'efficienza. – AShelly

+0

ho pubblicato il codice completo nel mio messaggio originale. @AShelly penso che tu abbia ragione, forse il compilatore è troppo prudente per qualche ragione qui. – skimon

2

Naturalmente il compilatore ha altre opzioni. Il "copia primo byte quindi entra nel ciclo se non 0" è ciò che gcc-4.5.1 produce con -O1. Con -O2 e -O3, produce

.LFB0: 
    .cfi_startproc 
    jmp  .L6    // jump to copy 
    .p2align 4,,10 
    .p2align 3 
.L4: 
    addq $1, %rdi  // increment pointers 
    addq $1, %rsi 
.L6:      // copy 
    movzbl (%rdi), %eax // get source byte 
    testb %al, %al  // check for 0 
    movb %al, (%rsi)  // move to dest 
    jne  .L4    // loop if nonzero 
    rep 
    ret 
    .cfi_endproc 

che è molto simile a quello che produce per il loop R K &. Non è possibile dire se sia effettivamente meglio, ma sembra più bello.

A parte il salto nel circuito, le istruzioni per il loop R K & sono esattamente gli stessi, appena ordinato in modo diverso:

.LFB0: 
    .cfi_startproc 
    .p2align 4,,10 
    .p2align 3 
.L2: 
    movzbl (%rdi), %eax // get source byte 
    addq $1, %rdi  // increment source pointer 
    movb %al, (%rsi)  // move byte to dest 
    addq $1, %rsi  // increment dest pointer 
    testb %al, %al  // check for 0 
    jne  .L2    // loop if nonzero 
    rep 
    ret 
    .cfi_endproc 
+0

Interessante, se compilo a 64 bit su gcc-4.4.4, vedo lo stesso comportamento che hai descritto. Tuttavia in 32 bit (-m32 -O3) produce "copia prima byte quindi entra nel ciclo se non 0". Forse la disponibilità di più registri in 64 bit fa la differenza qui? – skimon

+0

Buona chiamata. Infatti, con -m32, 4.5.1 copia anche il primo byte al di fuori del ciclo - e usa '% eax' come offset,' movzbl 1 (% ecx,% eax),% edx; movb% dl, 1 (% ebx,% eax); addl 1,% eax' invece di incrementare i puntatori direttamente come in modalità 64 bit. Puoi controllare quale codice produce il compilatore VS per 64-bit? Se ciò si comporta in modo simile, è molto probabilmente una cosa di architettura. –

+0

appena controllato con 64 bit su VS, fa ancora il "copia primo byte quindi entra nel ciclo se non 0". – skimon

0

Il tuo secondo codice non "controllare nulla di nuovo". Nella tua seconda versione il corpo del ciclo lavora con i caratteri all'indirizzo edx+eax+1 (nota la parte +1), che corrisponde ai caratteri numero 1, 2, 3 e così via.Il codice del prologo funziona con il carattere numero 0. Ciò significa che il codice non controlla mai lo stesso personaggio due volte, come sembra credere. Non c'è "di nuovo" lì.

Il secondo codice è un bot più contorto (la prima iterazione del ciclo ne viene effettivamente estratta) poiché, come è stato già spiegato, la sua funzionalità è diversa. I valori finali dei puntatori sono diversi tra il tuo pugno e la tua seconda versione.

Problemi correlati