printf()のソースコードで、ソースコードリーディングのコツを身に付ける:main()関数の前には何があるのか(5)(2/3 ページ)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。前回まで、printf()内の中身をデバッグと逆アセンブルで探ってきたが、今回はソースコードリーディングで答え合わせをしてみる。
フォーマット文字列の処理を見る
ディレクトリstdio-commonにはvfprintf.cというファイルがある。vfprintf()の本体があるのはここだろうか。
vfprintf.cを見てみると、どうやら当たりのようだ。
219:/* The function itself. */
220:int
221:vfprintf (FILE *s, const CHAR_T *format, va_list ap)
222:{
223: /* The character used as thousands separator. */
224:#ifdef COMPILE_WPRINTF
225: wchar_t thousands_sep = L'\0';
226:#else
227: const char *thousands_sep = NULL;
228:#endif
...
vfprintf()の内容は、定番の可変長引数の処理だ。
可変長引数を扱う関数を作る場合には、関数自体はprintf()のようにva_start()により可変長引数の処理の準備をして、vfprintf()のような実際の処理を呼び出すような構成になる。そして実際の処理はvfprintf()のように、頭に「v」を付加した関数名にするのが通例となっている。
vfprintf()の内部には、フォーマット文字列の処理がある。printf()の"%d"や"%s"などを解釈する処理だ。
しかし実際のソースコードはマクロの定義の山になっている。またジャンプテーブルを利用した状態遷移のような書き方になっており、残念ながら一筋縄ではいかなそうだ。
拾い読みしてみると、マクロの中ではva_arg()によって引数を得ているようだ。例えば以下は、整数値の処理と思われる部分だ。可変長で与えられた引数はva_arg()によって順に取得することができる。
550: LABEL (form_integer): \
551: /* Signed decimal integer. */ \
552: base = 10; \
553: \
554: if (is_longlong) \
555: { \
...
567: } \
568: else \
569: { \
570: long int signed_number; \
571: \
572: if (fspec == NULL) \
573: { \
574: if (is_long_num) \
575: signed_number = va_arg (ap, long int); \
576: else if (is_char) \
577: signed_number = (signed char) va_arg (ap, unsigned int); \
578: else if (!is_short) \
579: signed_number = va_arg (ap, int); \
580: else \
581: signed_number = (short int) va_arg (ap, unsigned int); \
582: } \
...
そして文字列の出力はoutchar()/outstring()というマクロで行っているように思える。
例えば"%%"による「%」の出力は、以下のように定義されているようだ。
542:#define process_arg(fspec) \
543: /* Start real work. We know about all flags and modifiers and \
544: now process the wanted format specifier. */ \
545: LABEL (form_percent): \
546: /* Write a literal "%". */ \
547: outchar (L_('%')); \
548: break; \
文字の出力を見る
文字出力の先を追いかけてみよう。
文字出力を行うoutchar()は、vfprintf.cの中で以下のように定義されている。
147:#define outchar(Ch) \
148: do \
149: { \
150: const INT_T outc = (Ch); \
151: if (PUTC (outc, s) == EOF || done == INT_MAX) \
152: { \
153: done = -1; \
154: goto all_done; \
155: } \
156: ++done; \
157: } \
158: while (0)
PUTC()で文字出力されているように思える。そしてPUTC()はやはりvfprintf.cで、以下のように定義されている。
... 102:# define PUTC(C, F) _IO_putc_unlocked (C, F) ...
さらに追いかけて探ってみよう。_IO_putc_unlockedという文字列を検索してみる。
[user@localhost glibc-2.21]$ grep _IO_putc_unlocked */* ... libio/libio.h:#define _IO_putc_unlocked(_ch, _fp) \ ...
libio/libio.hで#defineによって定義されているマクロのようだ。実際に見てみると、_IO_putc_unlocked()は以下のように定義されていた。
412:#define _IO_putc_unlocked(_ch, _fp) \ 413: (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \ 414: ? __overflow (_fp, (unsigned char) (_ch)) \ 415: : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))
つまりファイルポインタの指す先の、_IO_write_ptrというポインタの先に文字を格納しているようだ。格納できない場合には__overflow()が呼ばれているので、これはバッファあふれの際の対処だろう。
ファイルポインタの_IO_write_ptrというメンバはバッファの現在値を指すポインタだろう。つまり文字はバッファリングされて出力されるということがわかる。
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
プログラミング言語Cについて知ろう
プログラミング言語の基本となる「C」。正しい文法や作法を身に付けよう。Cには確かに学ぶだけの価値がある(編集部)
シェルコード解析に必携の「5つ道具」
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)
【 od 】コマンド――ファイルを8進数や16進数でダンプする
本連載は、Linuxのコマンドについて、基本書式からオプション、具体的な実行例までを紹介していきます。今回は、「od」コマンドです。