ポインタとかの話してたらprintf読んでた

C言語リファクタ

以下なgistのソースコードリファクタをしよう

gist

とりあえずこんなことしとけ

  • 使える時はライブラリを使う
  • マジックナンバー消す

リファクタ結果

B1への教え方は?

  • Asciiなマジックナンバーを文字にするのは教育上よくない?
    • charがintで処理されることが分かりにくくなる?
  • リファクタしまくると、B1的に読みにくい?
    • ライブラリ使いまくり(mallocとか)
    • メモリ空間利用の管理とか
  • ポインタはどうやって教える?

ポインタ変数の宣言時代入の動作

宣言時の'*‘の意味

[code]
int *a, b; // (int型のアドレス)を入れる変数aとint型の変数bの宣言
int *c, *d; // (int型のアドレス)を入れる変数c,dの宣言
(int *) a, b; // エラー(キャストに見える)
[/code]

  • 宣言時の’*‘は派生型
    • ‘ポインタ完全制覇’を読もう
  • 代入時の’*‘は間接参照演算子
    • ‘ポインタ完全制覇’を読もう

どういうこと?

[code]
int num = 100;
int *a = num; // これはダメ
int *b = # // これはオッケー
[/code]
なぜ?

  • 宣言時の’*‘はint型のアドレスを入れる変数という意味
    • ってことは、宣言時はアドレスを入れないといけないよね
    • 詳しくは’ポインタ完全制覇’を読もう
  • 値参照時の’*‘はアドレスの先の値を取得するという意味
    • つまり、*a = 100というのは…
    • aというアドレスの先にある値に100を代入する

ポインタ宣言いろいろ

[code]
int **c; // 2重ポインタ
// c == 0xffffea
// *c == 0xfffffa
// **c == 100

int *d[10]; // アドレスを格納する配列
// d == {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}
// d[1] == 0x01

int *(e[10]); // アドレスを格納する配列
// e == {0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09}
// e[1] == 0x01

int (*f)[10]; // int型の配列を示す(int型のアドレス)を格納する変数d
// f == 0xffffe0
// *f == {0,1,2,3,4,5,6,7,8,9}
[/code]

配列と文字列の代入のされ方の違い

次のコードはどのように実行されるんだろう?

[code]
char a[6] = {‘a’,‘i’,‘u’,‘e’,‘o’,'\0’};
char *b = "aiueo\0";
[/code]

アセンブラを読んでみる

[code]
40047f: c6 45 f0 61 movb $0x61,-0x10(%rbp) // 0x61 == ‘a’
400483: c6 45 f1 69 movb $0x69,-0xf(%rbp) // 0x69 == ‘i’
400487: c6 45 f2 75 movb $0x75,-0xe(%rbp) // 0x75 == ‘u’
40048b: c6 45 f3 65 movb $0x65,-0xd(%rbp) // 0x65 == ‘e’
40048f: c6 45 f4 6f movb $0x6f,-0xc(%rbp) // 0x6f == ‘o’
400493: c6 45 f5 00 movb $0x0,-0xb(%rbp) // 0x00 == ‘\0’
400497: 48 c7 45 f8 a8 05 40 movq $0x4005a8,-0x8(%rbp) // 0x04005a8という数字をコピー

— 0x04005a8 番地を見てみる —

4005a8: 61
4005a9: 69
4005aa: 75
4005ab: 65
4005ac: 6f

[/code]

  • char a[6] = {'a','i','u','e','o','\0'};
    • 1文字ずつ配列の中に格納する
  • char *b = "aiueo\0";
    • “aiueo\0″という文字列を作成
    • その文字列の最初のアドレスをbに格納

だから、printfではこんなことできるよ

[code]
printf("&aiueo: %pn", &"aiueo\0"); //=> 0x04005a8
[/code]

バイナリを見てみよう

[code]
0003020 00 00 00 00 00 00 00 00 61 69 75 65 6f 00 00 26 >……..aiueo..&<
0003040 61 69 75 65 6f 3a 20 25 70 0a 00 00 01 1b 03 3b >aiueo: %p……;<
[/code]

  • 最初のaiueoはオッケー
  • 次の&aiueo: %pってもしかして?
    • printf("&aiueo: %p\n", &"aiueo\0");の第1引数?
    • ってことは…?
    • 第1引数にはポインタを指定できる?

printfの第1引数にポインタを投げよう

manを見てみようか

[code]
$ man 3 printf
int printf(const char *format, …);
[/code]

おお、char *を指定できる

試してみよう

[code]
char *aaa = "0123456789abcdefg�";
char *test = "%pn";

printf(test, *aaa); //=> 0x30 == ‘0’
printf(test, aaa); //=> 0x400668
printf(test, &aaa); //=> 0x7fff08b9be00

printf(test, "0123456789abcdefg\0"); //=> 0x400638
printf(test, &"0123456789abcdefg\0"); //=> 0x400668
[/code]

というわけで、printfの第1引数にポインタを指定することはできる

printfの謎挙動を調べる

謎挙動って?

[code]
printf("%pn");

— 実行結果 —
0x7fffe55cee68
[/code]

フォーマット指定子に対してパラメータを与えていないのになぜかどこかのアドレスが表示される

printfを読んでみる

以前glibcをmakeするために引っ張ってきたglibc-2.19があるのでそこに移動してみる
すると、stdio-common/というすごくそれっぽい名前のディレクトリを発見
そこでlsしてみるとprintf.cを発見

早速中を読んでみる
[code]
$ vim -R stdio-common/printf.c
#include <libioP.h>
#include <stdarg.h>
#include <stdio.h>

#undef printf

/* Write formatted output to stdout from the format string FORMAT. */
/* VARARGS1 */
int
__printf (const char *format, …)
{
va_list arg;
int done;

va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);

return done;
}

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
/* This is for libg++. */
ldbl_strong_alias (__printf, _IO_printf);
[/code]

どうやら実体はvfprintfという関数らしい

vfprintf

stdio-common/vfprintf.cがあるので読んでみる

ざっと通して読んでいると以下な記述を発見
[code]
/* This table maps a character into a number representing a
class. In each step there is a destination label for each
class. */
static const uint8_t jump_table[] =
{
/* ' ' */ 1, 0, 0, /* ‘#’ */ 4,
0, /* ‘%’ */ 14, 0, /* “'*/ 6,
0, 0, /* ‘*’ */ 7, /* ‘+’ */ 2,
0, /* ‘-’ */ 3, /* ‘.’ */ 9, 0,
/* ‘0’ */ 5, /* ‘1’ */ 8, /* ‘2’ */ 8, /* ‘3’ */ 8,
/* ‘4’ */ 8, /* ‘5’ */ 8, /* ‘6’ */ 8, /* ‘7’ */ 8,
/* ‘8’ */ 8, /* ‘9’ */ 8, 0, 0,
0, 0, 0, 0,
0, /* ‘A’ */ 26, 0, /* ‘C’ */ 25,
0, /* ‘E’ */ 19, /* F */ 19, /* ‘G’ */ 19,
0, /* ‘I’ */ 29, 0, 0,
/* ‘L’ */ 12, 0, 0, 0,
0, 0, 0, /* ‘S’ */ 21,
0, 0, 0, 0,
/* ‘X’ */ 18, 0, /* ‘Z’ */ 13, 0,
0, 0, 0, 0,
0, /* ‘a’ */ 26, 0, /* ‘c’ */ 20,
/* ’d’ */ 15, /* ‘e’ */ 19, /* ‘f’ */ 19, /* ‘g’ */ 19,
/* ‘h’ */ 10, /* ‘i’ */ 15, /* ‘j’ */ 28, 0,
/* ‘l’ */ 11, /* ’m’ */ 24, /* ‘n’ */ 23, /* ‘o’ */ 17,
/* ‘p’ */ 22, /* ‘q’ */ 12, 0, /* ’s' */ 21,
/* ’t' */ 27, /* ‘u’ */ 16, 0, 0,
/* ‘x’ */ 18, 0, /* ‘z’ */ 13
};
[/code]

どうやらC言語のフォーマット指定子のテーブルっぽい

今回は%pについて読みたい
どうやら22っぽい

そのすぐ下の辺りにマクロが記述されていた
どうやら、%pなフォーマット指定子はform_pointerらしい
ちょうど配列の22番目が%p
[code]
static JUMP_TABLE_TYPE step0_jumps[30] =
{
REF (form_unknown),
REF (flag_space), /* for ' ' */
REF (flag_plus), /* for ‘+’ */
REF (flag_minus), /* for ‘-’ */
REF (flag_hash), /* for ‘<hash>’ */
REF (flag_zero), /* for ‘0’ */
REF (flag_quote), /* for “' */
REF (width_asterics), /* for ‘*’ */
REF (width), /* for ‘1’…‘9′ */
REF (precision), /* for ‘.’ */
REF (mod_half), /* for ‘h’ */
REF (mod_long), /* for ‘l’ */
REF (mod_longlong), /* for ‘L’, ‘q’ */
REF (mod_size_t), /* for ‘z’, ‘Z’ */
REF (form_percent), /* for ‘%’ */
REF (form_integer), /* for ’d’, ‘i’ */
REF (form_unsigned), /* for ‘u’ */
REF (form_octal), /* for ‘o’ */
REF (form_hexa), /* for ‘X’, ‘x’ */
REF (form_float), /* for ‘E’, ‘e’, ‘F’, ‘f’, ‘G’, ‘g’ */
REF (form_character), /* for ‘c’ */
REF (form_string), /* for ’s', ‘S’ */
REF (form_pointer), /* for ‘p’ */
REF (form_number), /* for ‘n’ */
REF (form_strerror), /* for ’m' */
REF (form_wcharacter), /* for ‘C’ */
REF (form_floathex), /* for ‘A’, ‘a’ */
REF (mod_ptrdiff_t), /* for ’t' */
REF (mod_intmax_t), /* for ‘j’ */
REF (flag_i18n), /* for ‘I’ */
};
[/code]

それじゃあform_pointerを追いかけよう

form_pointerで検索してみるとgotoで飛ぶ先っぽい記述を発見

[code]
#define process_arg(fspec)
LABEL (form_pointer):
/* Generic pointer. */
{
const void *ptr;
if (fspec == NULL)
ptr = va_arg (ap, void *);
else
ptr = args_value[fspec->data_arg].pa_pointer;
if (ptr != NULL)
{
/* If the pointer is not NULL, write it as a %#x spec. */
base = 16;
number.word = (unsigned long int) ptr;
is_negative = 0;
alt = 1;
group = 0;
spec = L_(‘x’);
goto LABEL (number);
}
else
{
/* Write "(nil)" for a nil pointer. */
string = (CHAR_T *) L_("(nil)");
/* Make sure the full string "(nil)" is printed. */
if (prec < 5)
prec = 5;
is_long = 0; /* This is no wide-char string. */
goto LABEL (print_string);
}
}
/* NOTREACHED */
[/code]

内容

  • process_argというマクロで呼び出される壮大なワンライナー
  • process_argfspecを受け取り
    • fspecの中身次第でptrへ渡される値が変わる
    • ptrに引数のポインタを渡しているっぽい?
  • ptrの値がNULLの場合とそうでないときで分岐
    • パラメータがNULLをさしていると(nil)と表示?

ptrにNULL投げてみるか

[code]
printf("%pn", NULL); //=> (nil) と表示される
[/code]

  • これはelseな部分に入ったみたい
    • だいたいの予想はあってる

と、ここまで書いて力つき…

続きはatton君のブログ