トップC-Tips > 可変個引数

可変個引数[2012.08.14]

1.stdarg.h

可変個引数の関数は stdarg.h に定義されているマクロを使う。 関連部分を抜粋したものを以下に示す。

typedef char *va_list;

/* only correct for i386 */
#define va_start(ap,last) ap = ((char *)&(last)) + ((sizeof(last)+3)&~3)
#define va_arg(ap,type) (ap += (sizeof(type)+3)&~3, *(type *)(ap - ((sizeof(type)+3)&~3)))
#define va_copy(dest, src) (dest) = (src)
#define va_end(ap)

プログラム例を下に示す。

#include <stdarg.h>

void varargs(const char *format, ...) {
    va_list argptr;
    va_start(argptr, format);
    vprintf(format, argptr);
    va_end(argptr);
}

void main() {
   varargs("%d '%c' \"%s\"\n", 123, 'A', "abc");
}

参考までに、TCC でコンパイルした結果を下に示す。

00401000 55                     push    ebp
00401001 89E5                   mov     ebp,esp
00401003 81EC04000000           sub     esp,4
00401009 90                     nop
0040100A 8D450C                 lea     eax,[ebp+0Ch]
0040100D 8945FC                 mov     [ebp-4],eax
00401010 8B45FC                 mov     eax,[ebp-4]
00401013 50                     push    eax
00401014 8B4508                 mov     eax,[ebp+8]
00401017 50                     push    eax
00401018 E8BB000000             call    jmp_vprintf
0040101D 83C408                 add     esp,8
00401020 C9                     leave
00401021 C3                     ret

C言語では、関数呼び出し時の実引数の値は、右(末尾)から順にスタックにプッシュされる。 この結果、一番左(先頭)の引数がスタックの一番上にある。 varargs("%d '%c' \"%s\"\n", 123, 'A', "abc"); の場合、 文字列 "%d '%c' \"%s\"\n" のアドレスが スタックの一番上に置かれる。 このサイズは sizeof(char*) (32ビットパソコンでは4バイト)である。 その下に int型整数 123がある(32ビットパソコンでは4バイト)。 その下に 文字 'c' がある。(関数呼び出しでは、符号拡張し、int型と同じサイズとなる。)、 一番下に文字列 "abc" のアドレスがある。

vprintfの第2引数は、プリントする値の配列の先頭番地である。 この場合、このアドレスはフォーマット変数のアドレス &format より sizeof(char*) (32ビットパソコンでは4バイト)大きな値となる。

従って、この場合は、上のプログラムは次のように書ける。

しかし、移植性のために、一般には、stdarg.h のマクロを使う方がよい。

void varargs(const char *format, ...) {
    vprintf(format, (char*)&format + sizeof(char*));
}

void main() {
   varargs("%d '%c' \"%s\"\n", 123, 'A', "abc");
}
00401000 55                     push    ebp
00401001 89E5                   mov     ebp,esp
00401003 81EC00000000           sub     esp,0
00401009 90                     nop
0040100A 8D450C                 lea     eax,[ebp+0Ch]
0040100D 50                     push    eax
0040100E 8B4508                 mov     eax,[ebp+8]
00401011 50                     push    eax
00401012 E8C1000000             call    jmp_vprintf
00401017 83C408                 add     esp,8
0040101A C9                     leave
0040101B C3                     ret