#include <stdio.h> #include <stdarg.h> static long sum(int n, ...) { long total = 0; va_list ap; va_start(ap, n); int i; for (i=0; i < n; i++) { total += va_arg(ap, long); } va_end(ap); return total; } int main(int argc, char *argv[]) { printf("a) %ld\n", sum(10, -1L, -2L, -3L, -4L, -5L, -6L, -7L, -8L, -9L, -10L)); printf("b) %ld\n", sum(5, 1, 2, 3, 4, 5)); printf("c) %ld\n", sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); return 0; }このコードを64bitのLinux上でビルドします。
$ gcc -m64 -Wall -g -o sample sample.c実行すると以下の結果が得られます
$ ./sample a) -55 b) 15 c) -21474836425
c)の結果が55でないのはlong(64bit)の引数にint(32bit)と解釈される1..10を渡してるからでしょ?
ちゃんと 1L とか (long)1 とかすれば?と思いますよね。ご尤もその通りです。
でも b) はなんで正しい結果が得られてるのに c)はダメなの?というのが今回の本題。
調べたところ、関数の呼び出し規約
がx86_64の場合、
- 整数・ポインタ引数 : RDI, RSI, RDX, RCX, R8, R9 の6個のレジスタ
- 浮動小数点引数 : XMM0 ~ XMM7 の8個のレジスタ
が使用され、これ以上の引数はスタックに積まれて渡されます。
よって b) のケースでは 6個の引数なので全てレジスタ(64bit)に格納されてセーフ
c) のケースでは 6 ~ 10 がスタックに32bitずつしかコピーされずにアウトという理由でした。
objdumpで逆アセしてみると 6~10までが movlで32bitしかコピーされていないことが判ります。
$ objdump -S sample : printf("c) %ld\n", sum(10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); 4006fd: c7 44 24 20 0a 00 00 movl $0xa,0x20(%rsp) 400704: 00 400705: c7 44 24 18 09 00 00 movl $0x9,0x18(%rsp) 40070c: 00 40070d: c7 44 24 10 08 00 00 movl $0x8,0x10(%rsp) 400714: 00 400715: c7 44 24 08 07 00 00 movl $0x7,0x8(%rsp) 40071c: 00 40071d: c7 04 24 06 00 00 00 movl $0x6,(%rsp) 400724: 41 b9 05 00 00 00 mov $0x5,%r9d 40072a: 41 b8 04 00 00 00 mov $0x4,%r8d 400730: b9 03 00 00 00 mov $0x3,%ecx 400735: ba 02 00 00 00 mov $0x2,%edx 40073a: be 01 00 00 00 mov $0x1,%esi 40073f: bf 0a 00 00 00 mov $0xa,%edi 400744: b8 00 00 00 00 mov $0x0,%eax 400749: e8 e2 fd ff ff callq 400530 <sum> :
キチンとサフィックスLを付けると以下のようにmovqで64bitコピーされ正しい結果が得られます。
printf("c) %ld\n", sum(10, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L, 10L)); 400760: 48 c7 44 24 20 0a 00 movq $0xa,0x20(%rsp) 400767: 00 00 400769: 48 c7 44 24 18 09 00 movq $0x9,0x18(%rsp) 400770: 00 00 400772: 48 c7 44 24 10 08 00 movq $0x8,0x10(%rsp) 400779: 00 00 40077b: 48 c7 44 24 08 07 00 movq $0x7,0x8(%rsp) 400782: 00 00 400784: 48 c7 04 24 06 00 00 movq $0x6,(%rsp) 40078b: 00 40078c: 41 b9 05 00 00 00 mov $0x5,%r9d 400792: 41 b8 04 00 00 00 mov $0x4,%r8d 400798: b9 03 00 00 00 mov $0x3,%ecx 40079d: ba 02 00 00 00 mov $0x2,%edx 4007a2: be 01 00 00 00 mov $0x1,%esi 4007a7: bf 0a 00 00 00 mov $0xa,%edi 4007ac: b8 00 00 00 00 mov $0x0,%eax 4007b1: e8 7a fd ff ff callq 400530 <sum> :寧ろ、c) より b) が正しい結果を得られる事の方が恐ろしいですね。
(printfなど標準関数を除いて)可変引数は警告出ないので気を付けましょう。
そんなにたくさんの引数を使うことは希でしょうが・・・