有汇编指令ADC.我发现这意味着"随身携带".但我不知道这意味着什么.或者如何用C++编写这个指令.我知道它不一样ADD.所以做一个简单的求和是不正确的.
信息:
在Windows中编译.我正在使用32位Windows安装.我的处理器是Intel的Core 2 Duo.
但是,英特尔处理器有一条名为adc的特殊指令.此命令的行为与add命令类似.唯一的额外的事情是它还添加值进位标志.因此,添加大整数可能非常方便.假设您要添加带有16位寄存器的32位整数.我们怎么做?好吧,假设第一个整数保存在寄存器对DX:AX上,第二个整数保存在BX:CX上.这是如何:
Run Code Online (Sandbox Code Playgroud)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声明是有条件支持的;它的含义是实现定义的.[注意:通常它用于通过实现将信息传递给汇编程序.-end note]"
int main(){
bool carry = false;
int x = 0xffffffff + 0xffffffff;
__asm {
jc setcarry
setcarry:
mov carry, 1
}
}
Run Code Online (Sandbox Code Playgroud)
可以在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)
希望这可以帮助.
C++ 语言没有任何进位标志的概念,因此围绕ADC指令制作内部函数包装器很笨拙。然而,英特尔还是做到了: unsigned char _addcarry_u32 (unsigned char c_in, unsigned a, unsigned b, unsigned * out);. 最后我检查过,gcc 在这方面做得很差(将进位结果保存到一个整数寄存器中,而不是将它留在 CF 中),但希望英特尔自己的编译器做得更好。
另请参阅x86标记 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),这不会改变这一点。
在 x86-64 中,ADD指令将两个 64 位整数相加:add rax, rbxdoes rax = rax + rbx。
\n当存在无符号溢出时(= 当结果不适合 64 位时),它还将进位标志设置为 1,否则它将进位标志设置为 0。
在C++中,你可以像这样模拟ADD:
\nuint64_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\nRun Code Online (Sandbox Code Playgroud)\nADC指令与 ADD 类似,但将进位标志添加到结果中:
\nadc rax, rbx会执行此操作rax = rax + rbx + carry_flag。
\n如果存在无符号溢出,它还会设置进位标志。
在 C++ 中:
\nuint64_t tmp = b + carry;\na += tmp;\ncarry = (tmp < carry) + (a < tmp); // only one overflow can happen\nRun Code Online (Sandbox Code Playgroud)\nADD 和 ADC 指令可用于添加大整数(n 个“数字”)。
\n对最低有效数字使用 ADD,然后使用 ADC ( n \xe2\x80\x93 1) 次将其余数字相加。
\n这是 \xe2\x80\x9c教科书加法算法\xe2\x80\x9d。
例如,将 256 位大整数与四个 64 位“数字”相加:
\nmov 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\nRun Code Online (Sandbox Code Playgroud)\n最近版本的clang编译器可以识别大整数加法并使用 ADD/ADC 来实现它。
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}\nRun Code Online (Sandbox Code Playgroud)\n