如何在C中使用asm添加两个64位数时访问进位标志

是的,谢谢你的工作。 @PeterCordes。 __int128有效。 但是还有一件事,正如你所说的使用多精度算法的内在函数,即C中的_addcarry_u64 ,使用头文件immintrin.h我有以下代码

 #include  #include  #include  #include  unsigned char _addcarry_u64(unsigned char c_in, uint64_t src1, uint64_t src2,uint64_t *sum); int main() { unsigned char carry; uint64_t sum; long long int c1=0,c2=0; uint64_t a=0x0234BDFA12CD4379,b=0xA8DB4567ACE92B38; carry = _addcarry_u64(0,a,b,&sum); printf("sum is %lx and carry value is %un",sum,carry); return 0; } 

你能指出我的错误吗? 我正在获得对_addcarry_u64未定义引用。 一些快速谷歌没有回答问题,如果要使用任何其他头文件或它与gcc不兼容,为什么这样

最初我有这个代码用于添加两个64位数字:

 static __inline int is_digit_lessthan_ct(digit_t x, digit_t y) { // Is x > (RADIX-1)); } #define ADDC(carryIn, addend1, addend2, carryOut, sumOut) \ { digit_t tempReg = (addend1) + (int)(carryIn); \ (sumOut) = (addend2) + tempReg; \ (carryOut) = (is_digit_lessthan_ct(tempReg, (int)(carryIn)) | is_digit_lessthan_ct((sumOut), tempReg)); \ } 

现在我知道使用汇编语言可以提高这种实现的速度。 所以我试图做类似的事情但是我无法访问或返回进位。 这是我的代码:

 #include #include #include uint64_t add32(uint64_t a,uint64_t b) { uint64_t d=0,carry=0; __asm__("mov %1,%%rax\n\t" "adc %2,%%rax\n\t" "mov %%rax,%0\n\t" :"=r"(d) :"r"(a),"r"(b) :"%rax" ); return d; } int main() { uint64_t a=0xA234BDFA12CD4379,b=0xA8DB4567ACE92B38; printf("Sum = %lx \n",add32(a,b)); return 0; } 

这个加法的结果应该是14B100361BFB66EB1,其中msb中的初始1是进位。 我想保存那个随身携带的寄存器。 我试过jc,但是我得到了一些或其他的错误。 甚至setc给了我错误,可能是因为我不确定语法。 那么有人能告诉我如何将进位保存在另一个寄存器中或通过修改此代码返回它吗?

像往常一样,内联asm并非绝对必要。 https://gcc.gnu.org/wiki/DontUseInlineAsm 。 但是目前的编译器实际上对于实际的扩展精度添加很有用,所以你可能想要asm。

adc有一个英特尔内在函数: _addcarry_u64 。 但是gcc和clang可能会使代码变慢。 ,不幸的是。 在64位平台上的GNU C中,您可以使用unsigned __int128


编译器通常设法在使用carry_out = (x+y) < x的惯用语来检查添加的进位时制作相当好的代码,其中<是无符号比较。 例如:

 struct long_carry { unsigned long res; unsigned carry; }; struct long_carry add_carryout(unsigned long x, unsigned long y) { unsigned long retval = x + y; unsigned carry = (retval < x); return (struct long_carry){ retval, carry }; } 

gcc7.2 -O3发出这个 (并且clang发出类似的代码):

  mov rax, rdi # because we need return value in a different register xor edx, edx # set up for setc add rax, rsi # generate carry setc dl # save carry. ret # return with rax=sum, edx=carry (SysV ABI struct packing) 

使用内联asm,你无法做到比这更好; 这个function对于现代CPU来说已经是最佳选择。 (我想如果mov不是零延迟,那么首先执行add将缩短进行准备的延迟。但是在Intel CPU上,应该更好地立即覆盖mov-elimination结果,所以最好先移动然后添加。)


Clang甚至会使用adc来将add中的进位用作另一个进位的进位,但仅限于第一个肢体。 也许是因为: 更新: 此function被破坏carry_out = (x+y) < x在进位时不起作用。 使用carry_out = (x+y+c_in) < xy+c_in可以y+c_in为零,即使有进位也会给你(x+0) < x (假)。

请注意,clang的cmp / adc reg,0确切地实现了C的行为,这与那里的另一个adc

无论如何,当它安全时,gcc甚至不会第一次使用adc 。 (因此,对于不吸吮的代码使用unsigned __int128 ,对于甚至更宽的整数使用asm)。

 // BROKEN with carry_in=1 and y=~0U static unsigned adc_buggy(unsigned long *sum, unsigned long x, unsigned long y, unsigned carry_in) { *sum = x + y + carry_in; unsigned carry = (*sum < x); return carry; } // *x += *y void add256(unsigned long *x, unsigned long *y) { unsigned carry; carry = adc(x, x[0], y[0], 0); carry = adc(x+1, x[1], y[1], carry); carry = adc(x+2, x[2], y[2], carry); carry = adc(x+3, x[3], y[3], carry); } mov rax, qword ptr [rsi] add rax, qword ptr [rdi] mov qword ptr [rdi], rax mov rax, qword ptr [rdi + 8] mov r8, qword ptr [rdi + 16] # hoisted mov rdx, qword ptr [rsi + 8] adc rdx, rax # ok, no memory operand but still adc mov qword ptr [rdi + 8], rdx mov rcx, qword ptr [rsi + 16] # r8 was loaded earlier add rcx, r8 cmp rdx, rax # manually check the previous result for carry. /facepalm adc rcx, 0 ... 

这很糟糕,所以如果你想要扩展精度加法,你仍然需要asm。 但是为了将结转变为C变量,你不能。