装配ADC(随附进位)到C++

Mar*_*aux 16 c++ x86 assembly

有汇编指令ADC.我发现这意味着"随身携带".但我不知道意味着什么.或者如何用C++编写这个指令.我知道它不一样ADD.所以做一个简单的求和是不正确的.

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

Sim*_*one 24

ADC与ADD相同,但如果处理器的进位标志置位则增加1.

  • ADC的想法不是知道进位标志,而是要在ADC之前进行ADD,因此当ADD溢出时进位将被设置 (2认同)

Chu*_*dad 8

这里(破碎)或这里

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

add  ax, cx
adc  dx, bx
Run Code Online (Sandbox Code Playgroud)

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

从这里开始的一切,请记住,它几乎落入了实现定义行为的区域.

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

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

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

  • 这段代码很荒谬;**您不能依赖于“asm”块之外的 C 语句是否设置了“CF”**。它可能恰好在调试模式下工作,但在启用优化的情况下这不会有用。另外,根据“CF”,使用“setc进位”将进位设置为0或1。 (3认同)

Spa*_*rky 6

可以在C和C++中模拟ADC行为.下面的示例添加了两个数字(存储为unsigned数组,因为它们太大而无法放入单个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;
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助.

  • 如果`second == ~0U && carry == 1`,此代码将无法设置进位.例如:32bits无符号,即`second [i] == 0xFFFFFFFF && carry == 1`.在这种情况下`first [i] == result [i]`即使溢出(进位)已经发生. (5认同)

Pet*_*des 6

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
Run Code Online (Sandbox Code Playgroud)

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

您有时可以手持编译器来发出adc或以其他方式使用add习语uint64_t sum = a+b;/ 的执行carry = sum < a;。但是对于当前的编译器,扩展它以从而adc不是获得结转add是不可能的;c+d+carry_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;
}
Run Code Online (Sandbox Code Playgroud)

使用-O2x86-64 ( Godbolt ) 的clang 主干编译如下:

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
Run Code Online (Sandbox Code Playgroud)

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

第一个 _ExtInt arg 在 R8:RCX:RDX:RSI 中,第二个在 R9 中具有低 qword,其余在内存中。

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

  • 当前版本的 ExtInt 只是展开所有内容,我看到 4000 多行汇编代码,其中包含 2048*2048 乘法,没有任何循环。在 (1&lt;&lt;20) 位处,编译器资源管理器由于超时而终止了该进程。 (2认同)

Řrř*_*ola 5

在 x86-64 中,ADD指令将两个 64 位整数相加:add rax, rbxdoes rax = rax + rbx
\n当存在无符号溢出时(= 当结果不适合 64 位时),它还将进位标志设置为 1,否则它将进位标志设置为 0。

\n

在C++中,你可以像这样模拟ADD:

\n
uint64_t a, b;\nbool carry;\n\na += b;\ncarry = (a < b);  // a+b can\'t be smaller than b: there must have been an overflow\n
Run Code Online (Sandbox Code Playgroud)\n

ADC指令与 ADD 类似,但将进位标志添加到结果中:
\nadc rax, rbx会执行此操作rax = rax + rbx + carry_flag
\n如果存在无符号溢出,它还会设置进位标志。

\n

在 C++ 中:

\n
uint64_t tmp = b + carry;\na += tmp;\ncarry = (tmp < carry) + (a < tmp);  // only one overflow can happen\n
Run Code Online (Sandbox Code Playgroud)\n

ADD 和 ADC 指令可用于添加大整数(n 个“数字”)。
\n对最低有效数字使用 ADD,然后使用 ADC ( n \xe2\x80\x93 1) 次将其余数字相加。
\n这是 \xe2\x80\x9c教科书加法算法\xe2\x80\x9d。

\n

例如,将 256 位大整数与四个 64 位“数字”相加:

\n
mov rax, [rsi]        ; load the least significant source digit\nmov rbx, [rsi + 8]    ; ...\nmov rcx, [rsi + 16]\nmov rdx, [rsi + 24]\nadd [rdi], rax        ; add it to the least significant destination digit\nadc [rdi + 8], rbx    ; ... propagate carry up\nadc [rdi + 16], rcx\nadc [rdi + 24], rdx\n
Run Code Online (Sandbox Code Playgroud)\n

最近版本的clang编译器可以识别大整数加法并使用 ADD/ADC 来实现它

\n
constexpr uint64_t n = 4;\nuint64_t dst[n], src[n];\n\n// Add src to dst.\nuint64_t carry = 0;\nfor (int i = 0; i < n; i++) {\n  uint64_t tmp = src[i] + carry;\n  dst[i] += tmp;\n  carry = (tmp < carry) + (dst[i] < tmp);\n}\n
Run Code Online (Sandbox Code Playgroud)\n