ポインタとかの話してたらprintf読んでた
C言語リファクタ
以下な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_arg
でfspec
を受け取りfspec
の中身次第でptr
へ渡される値が変わるptr
に引数のポインタを渡しているっぽい?
ptr
の値がNULLの場合とそうでないときで分岐- パラメータがNULLをさしていると
(nil)
と表示?
- パラメータがNULLをさしていると
ptrにNULL投げてみるか
[code]
printf("%pn", NULL); //=> (nil) と表示される
[/code]
- これは
else
な部分に入ったみたい- だいたいの予想はあってる
と、ここまで書いて力つき…
続きはatton君のブログで