背景
libcurlには可変長引数のAPIがあるが、整数の渡し方で、64bitマシンでは動くが32bitマシンでは動かず、えらい目にあった。
検証プログラム
gcc -O2 -m32 va.c
clang -O2 -m32 va.c
※%llxを使っているので、WindowsはWindows7以降のみ有効です
va.c
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void va(int x, ...){
va_list arg;
va_start(arg, x);
printf("%016llx\n",va_arg(arg,int));
printf("%016llx\n",va_arg(arg,int));
printf("%016llx\n",va_arg(arg,long));
printf("%016llx\n",va_arg(arg,long long));
printf("%016llx\n",va_arg(arg,long));
va_end(arg);
}
int main(){va(1,(long long)2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0);}
結果
Linux/FreeBSD/Darwin gcc/clang m64
- long longをintで受けるなど無茶苦茶なことをやったにもかかわらず、すべてきれいに出力された。long longに昇格されているのだろうか。
- FreeBSD(さくらのレンタルサーバー)でも同様であった。
0000000000000002
0000000000000003
0000000000000004
0000000000000005
0000000000000006
Linux/FreeBSD gcc/clang Darwin gcc m32
- intで受けた場合、上位8バイトは初期化されない。加えて、long longで送った2の上位8バイトが次のint受け取りにフレームシフトした。
- なお、clangや、mingw-w64/wineの組み合わせでも同様の結果が得られた。
f77b292000000002
f77b292000000000
f77b292000000003
0000000500000004
0000000500000006
Darwin clang m32
- ごみが0x0cになっているが原因は不明である。
0000000c00000002
0000000c00000000
0000000c00000003
0000000500000004
0000000500000006
Win64 MSVC100
- ほとんどLinuxと同じでしたが、long longを引っ張ったところでごみが出てしまった。intを8bytes境界に載せているだけなのかもしれない。
- long longにキャストして送るようにしたらゴミは出なかった。
- MSVC140ではどうなんだろう。試してませんが。。
0000000000000002
0000000000000003
0000000000000004
00007ff600000005
0000000000000006
Win32 MSVC100
- long longで受け取らないとかならずゴミが出るが、ゴミのパターンはLinuxと異なる。
- なお最初の1は引数に依存しないようだ。
0000000100000002
00ea20c800000000
00ea20c800000003
0000000500000004
00ea20c800000006
結論
可変長引数のAPIを使う場合は普段以上に型に気をつけましょう…。