21

有一个 x86 汇编指令ADC。我发现这意味着“带进位添加”。这是什么意思/做什么?如何在 C++ 中实现这条指令的行为?

信息:
在 Windows 上编译。我正在使用 32 位 Windows 安装。我的处理器是 Intel 的 Core 2 Duo。

4

6 回答 6

31

ADC 与 ADD 相同,但如果设置了处理器的进位标志,则增加一个额外的 1。

于 2010-11-11T11:26:42.967 回答
10

这里(破碎)或这里

但是,Intel 处理器有一条特殊指令,称为 adc。此命令的行为类似于 add 命令。唯一额外的事情是它还添加了值进位标志。因此,这对于添加大整数可能非常方便。假设您想用 16 位寄存器添加一个 32 位整数。我们怎么能做到这一点?好吧,假设第一个整数保存在寄存器对 DX:AX 上,第二个整数保存在 BX:CX 上。这是如何:

add  ax, cx
adc  dx, bx

啊,所以首先,通过 add ax, cx 添加低 16 位。然后使用 adc 而不是 add 来添加更高的 16 位。这是因为:如果有溢出,进位位会自动加到高16位。所以,没有繁琐的检查。这种方法可以扩展到 64 位等等... 注意:如果 32 位整数加法在高 16 位也溢出,结果将不正确并设置进位标志,例如加 50 亿到50亿。

从这里开始,请记住,它几乎属于实现定义的行为区域。

这是一个适用于 VS 2010(32 位,WinXp)的小示例

警告:$7.4/1-“asm 声明是有条件支持的;它的含义是实现定义的。[注意:通常它用于通过实现将信息传递给汇编程序。-结束说明]”

int main(){
   bool carry = false;
   int x = 0xffffffff + 0xffffffff;
   __asm {
      jc setcarry
setcarry:
      mov carry, 1
   }
}
于 2010-11-11T11:31:11.963 回答
9

ADC 行为可以在 C 和 C++ 中进行仿真。以下示例将两个数字相加(存储为无符号数组,因为它们太大而无法放入单个无符号数中)。

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;

希望这可以帮助。

于 2010-11-11T12:01:40.550 回答
8

C++ 语言没有任何进位标志的概念,因此围绕ADC指令制作内部函数包装器很笨重。然而,英特尔还是做到了: unsigned char _addcarry_u32 (unsigned char c_in, unsigned a, unsigned b, unsigned * out);. 最后我检查了一下,gcc 在这方面做得很差(将进位结果保存到整数寄存器中,而不是将其留在 CF 中),但希望英特尔自己的编译器做得更好。

另请参阅标记 wiki 以获取程序集文档。


当添加比单个寄存器更宽的整数时,编译器将为您使用 ADC,例如添加int64_t32 位代码或__int128_t64 位代码。

#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

Godbolt 编译器资源管理器的 asm 输出。clang-fverbose-asm不是很冗长,但 gcc 5.3 / 6.1 浪费了两条mov指令,因此可读性较差。

您有时可以手持编译器发出一个adc或以其他方式使用add成语uint64_t sum = a+b;/的执行carry = sum < a;。但是对于当前的编译器来说,将其扩展为从 anadc而不是中执行add是不可能的;c+d+carry_in可以一直环绕,如果您安全地执行,编译器不会设法优化对每个+in执行的多个检查。c+d+carry


_ExtInt

我知道有一种方法可以获得 add/adc/.../adc 链:Clang 的新_ExtInt(width)功能提供了任何大小的固定位宽类型,最大为 16,777,215 位(博客文章)。它已于 2020 年 4 月 21 日添加到 clang 的开发版本中,因此尚未发布任何版本。

这有望在某个时候出现在 ISO C 和/或 C++ 中;N2472提案显然正在“被 ISO WG14 C 语言委员会积极考虑

typedef _ExtInt(256) wide_int;

wide_int add ( wide_int a, wide_int b) {
    return a+b;
}

-O2使用x86-64 ( Godbolt )的 clang trunk 编译如下:

add(int _ExtInt<256>, int _ExtInt<256>):
        add     rsi, r9
        adc     rdx, qword ptr [rsp + 8]
        adc     rcx, qword ptr [rsp + 16]
        mov     rax, rdi                        # return the retval pointer
        adc     r8, qword ptr [rsp + 24]        # chain of ADD / 3x ADC!

        mov     qword ptr [rdi + 8], rdx        # store results to mem
        mov     qword ptr [rdi], rsi
        mov     qword ptr [rdi + 16], rcx
        mov     qword ptr [rdi + 24], r8
        ret

显然_ExtInt是在整数寄存器中按值传递,直到调用约定用完寄存器。(至少在这个早期版本中;也许 x86-64 SysV 应该将它归类为“内存”,当它比 2 或 3 个寄存器更宽时,比如大于 16 字节的结构。虽然比结构更多,但将它放在寄存器中很可能是有用。只需将其他 args 放在首位,这样它们就不会移位。)

第一个 _ExtInt 参数在 R8:RCX:RDX:RSI 中,第二个在 R9 中有它的低位 qword,其余的在内存中。

指向返回值对象的指针作为 RDI 中隐藏的第一个参数传递;x86-64 System V 只返回最多 2 个整数寄存器 (RDX:RAX),这不会改变这一点。

于 2016-06-10T01:15:19.073 回答
4

这有一个错误。试试这个输入:

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

结果应该是 {0, 0, 1, ...} 但结果是 {0, 0, 0, ...}

更改此行:

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

对此:

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

修复它。

于 2011-01-20T03:42:02.303 回答
0
int32_t adc(uint32_t first, uint32_t second, uint32_t *carry)
    {
        uint32_t res;
        uint32_t carry_out = 0;
    
        if (!*carry)
        {
            res = first + second;
            *carry = (res < first) && (res < second);
    
            return res;
        }
    
        res = adc(first, second, &carry_out);
        if (*carry)
        {
            res++;
            carry_out |= !res;
        }
        
        *carry = carry_out;
    
        return res;
    }
于 2017-02-17T09:27:06.657 回答