#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など標準関数を除いて)可変引数は警告出ないので気を付けましょう。
そんなにたくさんの引数を使うことは希でしょうが・・・