UP | HOME

printfにおける可変引数の実例 - 2023年度 システムプログラミング

printf における可変引数の実例

プログラム中で,

1: main()
2: {
3:   char string[] = "ABC";
4:   myprintf("%d,%s", 10, string);
5: }

としたとき,

1: myprintf(char *fmt, ...)
2: {
3:    ...
4: }

として宣言された myprintf 内にジャンプしたときのスタックの様子は, 以下のようになるでしょう.ただし,アドレスは,かならずしもこの値にはなりません.

アドレス ラベル(変数名) 中身 説明
4976 新$sp→      
: : : : :
5000 fmt 6004 char* fmtが指す文字列の先頭アドレス
5004   10 int 第2引数
5008   6000 char* stringが指す文字列の先頭アドレス
5012   未使用   第4引数があれば,ここに入る
: : : : :
6000 string ABC\0   string の中身
6004 無名 %d,%    
6008 無名 s\0    
: : : : :

第2引数を取り出すには?

 1: char *p = ((char*)&fmt) + ROUNDUP_SIZEOF(fmt);
 2: 
 3: switch (*fmt){
 4: case 'd':
 5:   print_int(*(int*)p);
 6:   p = p + ROUNDUP_SIZEOF(int);
 7:   break;
 8: case 's':
 9:   print_string(*(char**)p);
10:   p = p + ROUNDUP_SIZEOF(char*);
11:   break;
12: case 'c':
13:   print_char(*(char*)p);
14:   p = p + ROUNDUP_SIZEOF(char);
15:   break;

ここで,ROUNDUP_SIZEOF… は fmt の sizeof を 4の倍数に切り上げる操作をする.ここの場合は, 4のままなので,

 1: char *p = ((char*)&fmt) + 4;
 2: 
 3: switch (*fmt){
 4: case 'd':
 5:   print_int(*(int*)p);
 6:   p = p + 4;
 7:   break;
 8: case 's':
 9:   print_string(*(char**)p);
10:   p = p + 4;
11:   break;
12: case 'c':
13:   print_char(*(char*)p);
14:   p = p + 4;
15:   break;

のように考えることができます.

fmt 中に % を見付ける度に p5004, 5008, ... と,次の引数が入っているアドレスになることが分かります.

p は,ポインタですが, p が指すアドレスに入っている値は, それぞれ % の後の文字によって型が違ってきます.

この例では, %d の場合は, int 型(10) なので, pint を指すアドレスつまり int* ということになります.

従って,10を得るためには, pint* にキャストし, そのポインタが指す中身を取り出さなければなりません.

print_int(*(int*)p);

中の *(int*)p は,それを表しています. 一方,次の引数 %s に対応する 5008 番地にある 6000 という値は, char ではなく, char* です.従って, p (つまり5008) は, char* を指すポインタ (char**) となるため, (char*)p ではなく, (char**)p と書く必要があります.

print_string(*(char**)p);

中の *(char**)p は,それを表しています.

Author: Yoshinari Nomura

Emacs 27.1 (Org mode 9.3)