2010-11-11 7 views
12

C'è un'istruzione di assemblaggio ADC. Ho trovato questo significa "Aggiungi con carry". Ma io non so cosa che significa. O come scrivere questa istruzione in C++. E so che non è lo stesso di ADD. Quindi fare una semplice sommatoria non è corretto.Assembly ADC (Aggiungi con carry) a C++

INFO:
Compilato in Windows. Sto usando un'installazione di Windows a 32 bit. Il mio processore è Core 2 Duo di Intel.

+1

quale processore? – Chubsdad

+0

@Chubsdad: ho fatto del mio meglio per aggiungere alcune informazioni. Spero sia abbastanza –

+2

Quale compilatore? Per accedere al flag carry, devi incorporare il codice assembler nel tuo codice C++. Il modo in cui lo fai dipende dal compilatore che stai utilizzando. – TonyK

risposta

18

ADC è lo stesso di ADD ma aggiunge 1 in più se è impostato il flag di trasporto del processore.

+0

OK! Grazie. Ma ora, devo sapere se la bandiera è impostata. È possibile in C++? –

+1

Non con standard C++, è necessario utilizzare un blocco di codice "asm". Non ricordo la sintassi esatta, ma perderai la portabilità del codice. – Simone

+2

L'idea di ADC non è di conoscere il flag di carry, ma di fare un ADD prima di ADC, quindi il carry sarà impostato quando l'ADD trabocca – stefaanv

6

Da here (rotto) o here

Tuttavia, processore Intel ha un'istruzione speciale chiamato ADC. Questo comando si comporta allo stesso modo del comando di aggiunta . L'unica cosa in più è che aggiunge anche il valore carry flag lungo. Quindi, questo può essere molto utile per aggiungere interi di grandi dimensioni. Supponiamo che desideri aggiungere a 32 bit con registri a 16 bit. Come possiamo farlo? Bene, diciamo che il primo numero intero è tenuto sulla coppia di registri DX: AXe il secondo è su BX: CX. Questo è il modo :

add ax, cx 
adc dx, bx 

Ah, quindi prima, il 16 bit inferiore è aggiunto da add ax, cx. Quindi il 16 bit più alto è aggiunto utilizzando adc anziché aggiungere. È perché: se ci sono overflow, il bit di trasporto viene aggiunto automaticamente a il 16 bit più alto. Quindi, nessun ingombrante controllo . Questo metodo può essere esteso da a 64 bit e così via ... Si noti che: se l'aggiunta di numero intero a 32 bit sostituisce anche con il numero più alto a 16 bit, il risultato non è corretto e il flag di trasporto è impostato , per esempio Aggiungendo 5 miliardi a 5 miliardi.

Tutto da qui in poi, ricorda che cade praticamente nella zona di comportamento definito dall'implementazione.

Ecco un piccolo campione che lavora per VS 2010 (a 32 bit, WinXP)

Caveat:. $ 7.4/1- "è condizionatamente-supportata La dichiarazione asm, il suo significato è definito dall'implementazione [Nota: in genere viene utilizzato per passare informazioni attraverso la realizzazione di un assemblatore. -end nota]"

int main(){ 
    bool carry = false; 
    int x = 0xffffffff + 0xffffffff; 
    __asm { 
     jc setcarry 
setcarry: 
     mov carry, 1 
    } 
} 
+0

Nella mia risposta non riesco a bloccare la citazione della parte "Caveat ....". A volte questa formazione semplicemente non si comporta bene. – Chubsdad

+0

0xffffffff è o -1 o UINT_MAX, che viene memorizzato in un int. Forse 'x' dovrebbe essere un int unsigned, o gli addendi dovrebbero essere INT_MAX (0x7fffffff). Se consideriamo che i summit siano dello stesso tipo del risultato (cioè numero intero con segno), allora il flag OVERFLOW non è impostato - il risultato è -2 (0xfffffffe). – jww

+0

Quel codice è ridicolo; ** non puoi dipendere dal fatto che 'CF' sia impostato o meno da un'istruzione C al di fuori del blocco' asm' **. Potrebbe succedere di funzionare in modalità di debug, ma non sarà utile con l'ottimizzazione abilitata. Inoltre, usa 'setc carry' per impostare carry su 0 o 1, in base a' CF'. –

7

il comportamento ADC può essere simulato sia in C e C++. Nell'esempio seguente vengono aggiunti due numeri (memorizzati come matrici di unsigned in quanto troppo grandi per essere inseriti in un singolo unsigned).

unsigned first[10]; 
unsigned second[10]; 
unsigned result[11]; 

.... /* first and second get defined */ 

unsigned carry = 0; 
for (i = 0; i < 10; i++) { 
    result[i] = first[i] + second[i] + carry; 
    carry = (first[i] > result[i]); 
} 
result[10] = carry; 

Spero che questo aiuti.

+5

Questo codice non riuscirà a impostare il carry se 'second == ~ 0U && carry == 1'. per esempio con 32 bit senza segno che sarebbe 'secondo [i] == 0xFFFFFFFF && carry == 1'. In questo caso, 'first [i] == result [i]' anche se è successo un overflow (carry). –

+1

Il codice di lavoro è 'unsigned tmp = second [i] + carry; risultato [i] = primo [i] + tmp; carry = (primo [i]> risultato [i]) | (secondo [i]> tmp); ' –

+0

E il fatto questo è incredibilmente lento è il motivo per cui è scritto in assemblea proprio adesso. – Joshua

4

C'è un bug in questo.Prova questo ingresso:

unsigned first[10] = {0x00000001}; 
unsigned second[10] = {0xffffffff, 0xffffffff}; 

Il risultato dovrebbe essere {0, 0, 1, ...} ma il risultato è {0, 0, 0, ...}

La modifica di questa linea:

carry = (first[i] > result[i]); 

a questo:

if (carry) 
    carry = (first[i] >= result[i]); 
else 
    carry = (first[i] > result[i]); 

lo ripara.

+2

'carry = (carry && first [i]> = result [i]) || (! Carry && first [i]> result [i])' evita la ramificazione e fa la stessa cosa, se qualcuno è interessato. – Hassedev

+2

In realtà '||' e '&&' causano diramazioni poiché valutano solo il lato destro come necessario. Ci sono più ramificazioni nella one-liner che con la dichiarazione if() di facile lettura. –

3

Il linguaggio C++ non ha alcun concetto di contrassegno carry, pertanto la creazione di un wrapper di funzione intrinseca attorno allo ADC instruction è netta. Tuttavia, Intel lo ha fatto comunque: unsigned char _addcarry_u32 (unsigned char c_in, unsigned a, unsigned b, unsigned * out);. L'ultima volta che ho controllato, gcc ha fatto un pessimo lavoro con questo (salvando il risultato di carry in un registro intero, invece di lasciarlo in CF), ma si spera che il compilatore di Intel funzioni meglio.

Vedere anche il tag wiki per la documentazione di assemblaggio.


Il compilatore utilizzerà ADC per voi quando si aggiungono numeri interi più ampi di un singolo registro, ad es. aggiungendo int64_t in codice a 32 bit o __int128_t in codice a 64 bit.

#include <stdint.h> 
#ifdef __x86_64__ 
__int128_t add128(__int128_t a, __int128_t b) { return a+b; } 
#endif 
    # clang 3.8 -O3 for x86-64, SystemV ABI. 
    # __int128_t args passed in 2 regs each, and returned in rdx:rax 
    add  rdi, rdx 
    adc  rsi, rcx 
    mov  rax, rdi 
    mov  rdx, rsi 
    ret 

asm uscita da Godbolt compiler explorer. clang's -fverbose-asm non è molto vebose, ma gcc 5.3/6.1 spreca due istruzioni mov quindi è meno leggibile.

0
unsigned long result; 
unsigned int first; 
unsigned int second; 

result = first + second; 
result += (result & 0x10000) ? 1 : 0; 
result &= 0xFFFF