Assembly ADC (Aggiungi con carry) a C ++

C’è un’istruzione di assembly ADC . Ho trovato questo significa “Aggiungi con carry”. Ma non so cosa significhi. O come scrivere questa istruzione in C ++. E so che non è lo stesso di ADD . Quindi fare una semplice sumtoria non è corretto.

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

L’ADC è uguale a ADD ma aggiunge un extra 1 se è impostato il flag di carry del processore.

Da qui (rotto) o qui

Tuttavia, il processore Intel ha un’istruzione speciale chiamata adc. Questo comando si comporta allo stesso modo del comando add. L’unica cosa in più è che aggiunge anche il valore carry flag. Quindi, questo può essere molto utile per aggiungere interi di grandi dimensioni. Supponiamo di voler aggiungere un numero intero a 32 bit con registri a 16 bit. Come possiamo farlo? Bene, diciamo che il primo intero è contenuto nella coppia di registri DX: AX, e il secondo è in BX: CX. Questo è come:

 add ax, cx adc dx, bx 

Ah, quindi prima, il 16 bit più basso è aggiunto da ax ax, cx. Quindi il 16 bit più alto viene aggiunto usando adc invece di add. È perché: se ci sono overflow, il carry bit viene automaticamente aggiunto nel 16 bit più alto. Quindi, nessun controllo ingombrante. Questo metodo può essere esteso a 64 bit e così via … Nota che: se l’aggiunta di numeri interi a 32 bit supera troppo anche i 16 bit più alti, il risultato non sarà corretto e verrà impostato il flag di carry, ad es. a 5 miliardi.

Tutto da qui in avanti, ricorda che cade praticamente nella zona del comportamento definito dall’implementazione.

Ecco un piccolo esempio che funziona per VS 2010 (32 bit, WinXp)

Avvertenza: $ 7,4 / 1- “La dichiarazione asm è supportata condizionalmente, il suo significato è definito dall’implementazione. [Nota: in genere viene utilizzato per passare informazioni attraverso l’implementazione a un assemblatore. -End note]”

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

Il comportamento ADC può essere simulato sia in C che in 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 ti aiuti.

C’è un bug in questo. Prova questo input:

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

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

Cambiando questa linea:

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

a questa:

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

lo risolve

Il linguaggio C ++ non ha alcun concetto di flag di carry, quindi fare un wrapper di funzioni intrinseche attorno all’istruzione ADC è goffo. 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.

Vedi anche il wiki del tag x86 per la documentazione di assemblaggio.


Il compilatore utilizzerà ADC per te quando aggiungi interi più ampi di un singolo registro, ad esempio aggiungendo int64_t nel codice a 32 bit o __int128_t nel codice a 64 bit.

 #include  #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 

uscita ASM dal explorer del compilatore Godbolt . clang’s -fverbose-asm non è molto vebose, ma gcc 5.3 / 6.1 spreca due istruzioni mov modo che sia meno leggibile.

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