連載
» 2017年06月15日 05時00分 公開

printf()のソースコードで、ソースコードリーディングのコツを身に付けるmain()関数の前には何があるのか(5)(2/3 ページ)

[坂井弘亮,著]

フォーマット文字列の処理を見る

 ディレクトリ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.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。