divl
単一の命令を使用して除算とモジュラスの両方を取得するために、gccのインラインアセンブリを考え出そうとしていました。残念ながら、私は組み立てがあまり得意ではありません。誰かがこれについて私を助けてくれませんか?ありがとうございました。
5 に答える
あなたはこのようなものを探しています:
__asm__("divl %2\n"
: "=d" (remainder), "=a" (quotient)
: "g" (modulus), "d" (high), "a" (low));
私は他のコメント提供者に同意しますが、通常はGCCがこれを行い、可能な場合はインラインアセンブリを避ける必要がありますが、この構成が必要になる場合もあります。
たとえば、上位ワードがモジュラスよりも小さい場合は、このように除算を実行しても安全です。ただし、GCCはこれを実現するのに十分なほど賢くはありません。一般的な場合、64ビットの数値を32ビットの数値で除算するとオーバーフローが発生する可能性があるため、ライブラリルーチンを呼び出して余分な作業を行います。(64ビットISAの場合は128ビット/ 64ビットに置き換えてください。)
これを自分で最適化しようとしないでください。GCCはすでにこれを行っています。
volatile int some_a = 18, some_b = 7;
int main(int argc, char *argv[]) {
int a = some_a, b = some_b;
printf("%d %d\n", a / b, a % b);
return 0;
}
ランニング
gcc -S test.c -O
収量
main:
.LFB11:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl some_a(%rip), %esi
movl some_b(%rip), %ecx
movl %esi, %eax
movl %esi, %edx
sarl $31, %edx
idivl %ecx
movl %eax, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
movl $0, %eax
addq $8, %rsp
.cfi_def_cfa_offset 8
ret
残りの%edxは、printfに渡される3番目の引数でもあるため、移動されないことに注意してください。
編集:32ビットバージョンはそれほど混乱しません。-m32を渡すと、
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl some_a, %eax
movl some_b, %ecx
movl %eax, %edx
sarl $31, %edx
idivl %ecx
movl %edx, 8(%esp)
movl %eax, 4(%esp)
movl $.LC0, (%esp)
call printf
movl $0, %eax
leave
ret
幸い、これを実現するためにインラインアセンブリに頼る必要はありません。gccは、可能な場合にこれを自動的に実行します。
$ cat divmod.c
struct sdiv { unsigned long quot; unsigned long rem; };
struct sdiv divide( unsigned long num, unsigned long divisor )
{
struct sdiv x = { num / divisor, num % divisor };
return x;
}
$ gcc -O3 -std=c99 -Wall -Wextra -pedantic -S divmod.c -o -
.file "divmod.c"
.text
.p2align 4,,15
.globl divide
.type divide, @function
divide:
.LFB0:
.cfi_startproc
movq %rdi, %rax
xorl %edx, %edx
divq %rsi
ret
.cfi_endproc
.LFE0:
.size divide, .-divide
.ident "GCC: (GNU) 4.4.4 20100630 (Red Hat 4.4.4-10)"
.section .note.GNU-stack,"",@progbits
はい-divlはeaxで商を生成し、残りはedxで生成します。Intel構文を使用します。例:
mov eax, 17
mov ebx, 3
xor edx, edx
div ebx
; eax = 5
; edx = 2
これは、divlに関するLinuxカーネルコードの例です。
/*
* do_div() is NOT a C function. It wants to return
* two values (the quotient and the remainder), but
* since that doesn't work very well in C, what it
* does is:
*
* - modifies the 64-bit dividend _in_place_
* - returns the 32-bit remainder
*
* This ends up being the most efficient "calling
* convention" on x86.
*/
#define do_div(n, base) \
({ \
unsigned long __upper, __low, __high, __mod, __base; \
__base = (base); \
if (__builtin_constant_p(__base) && is_power_of_2(__base)) { \
__mod = n & (__base - 1); \
n >>= ilog2(__base); \
} else { \
asm("" : "=a" (__low), "=d" (__high) : "A" (n));\
__upper = __high; \
if (__high) { \
__upper = __high % (__base); \
__high = __high / (__base); \
} \
asm("divl %2" : "=a" (__low), "=d" (__mod) \
: "rm" (__base), "0" (__low), "1" (__upper)); \
asm("" : "=A" (n) : "a" (__low), "d" (__high)); \
} \
__mod; \
})