LoginSignup
4
1

More than 3 years have passed since last update.

scanfの内部のソースを読む

Posted at

はじめに

本稿ではglibc-2.21を使用して標準ライブラリであるscanfの中身のソースコードを解読してみます。
標準ライブラリのソースコードの解読方法は、『ハロー“Hello, World” OSと標準ライブラリのシゴトとしくみ(2015年9月11日発行)』を参考にしています。
glibc-2.21は下記からダウンロードできます。
https://github.com/shichao-an/glibc-2.21

scanfの中身のソースを読んでみる

ダウンロードしたソースコードから下記のコマンドでscanfとつくファイルを検索すると、下記の結果が得られると思います。
find . -name "*scanf*"
result.png

scanf2.c,scanf.11cなどのファイルが表示されると思いますが、scanf[数字].cと書かれたコードは全てscanfのテストコードとなっています。scanfの関数自体はscanf.cで定義されています。

それではさっそくscanf.cのソースコードを覗いてみましょう。
scanf.cの中身は下記のようになっています。

scanf.c

#include <stdarg.h>
#include <stdio.h>
#include <libioP.h>
/* Read formatted input from stdin according to the format string FORMAT.  */
/* VARARGS1 */
int
__scanf (const char *format, ...)
{
  va_list arg;
  int done;
  va_start (arg, format);
  done = _IO_vfscanf (stdin, format, arg, NULL);
  va_end (arg);
  return done;
}
ldbl_strong_alias (__scanf, scanf);

上記のコードにscanfという関数が出てくるのがわかります。
ここで、ldbl_strong_aliasは
scanf()をscanf()と定義するマクロです。
具体的に、ldbl_strong_aliasのマクロの定義は以下となります。

include/libc-symbols.h
#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
...
#include/libc-symbols.h
/* Define ALIASNAME as a strong alias for NAME. */
# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((alias (#name)));

次に、__scanfという関数の中身を見ると、 _IO_vfscanf という関数が登場している
のがわかります。そこで、githubのglibc-2.21のレポシドリ上で _IO_vfscanfで検索するとlibio/vscanf.c内で定義されていることが分かります。

libio/vscanf.c
#include "libioP.h"
#include "stdio.h"
#undef vscanf
int _IO_vscanf (format, args)
     const char *format;
     _IO_va_list args;
{
  return _IO_vfscanf (_IO_stdin, format, args, NULL);
}
ldbl_weak_alias (_IO_vscanf, vscanf)

同様に、ここで出てきた_IO_vfscanfも検索をして追っていくと、stdio-
common/vfscanf.cの中で、ldbl_strong_aliasで_IO_vfscanf_internalにエイリアス
されていることが分かります。

stdio-common/vfscanf.c
int ___vfscanf (FILE *s, const char *format, va_list argptr)
{
  return _IO_vfscanf_internal (s, format, argptr, NULL);
}
ldbl_strong_alias (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_hidden_def (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_strong_alias (___vfscanf, __vfscanf)
ldbl_hidden_def (___vfscanf, __vfscanf)
ldbl_weak_alias (___vfscanf, vfscanf)

さらに、_IO_vfscanf_internalの定義を追うと、_IO_vfscanf_internalは同じ
vfscanf.c上で定義されていることが分かります。

stdio-common/vfscanf.c
int _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,int *errp)

定義の中身は膨大で全貌を見通すのがかなり大変そうです。
しかし、拾い読みをしてみると、 input_error ()という関数が登場する行があります。

stdio-common/vfscanf.c
  /* Non-ASCII, may be a multibyte.  */
  int len = __mbrlen (f, strlen (f), &state);
  if (len > 0)
    {
{
  c = inchar ();
  if (__glibc_unlikely (c == EOF))
    input_error ();
  else if (c != (unsigned char) *f++)
    {
      ungetc_not_eof (c, s);
      conv_error ();
    }
}
      do
      while (--len > 0);
      continue;
    }
}
      if (!isascii ((unsigned char) *f))

どうやらこの箇所で入力の読み込み→入力結果がエラーかどうか判断する例外処理を行っているようです。
この箇所をよく見ると、inchar ()で入力の読み込み処理を行っているようです。
実際、inchar()の定義を追いかけると、vfscanf.c上で下記のように定義されています。

stdio-common/vfscanf.c
# define inchar() (c == WEOF ? ((errno = inchar_errno), WEOF)       \
: ((c = _IO_getwc_unlocked (s)),       \
    (void) (c != WEOF
    ? ++read_in       \
    : (size_t) (inchar_errno = errno)), c))

ここで、WOEFはワイド文字のストリームの終わりを示すマクロなので、どうやら_IO_getwc_unlocked で入力を読み込み、それがストリームの終わりであるかどうかの判断を行っていることが分かります。
_IO_getwc_unlocked の定義は以下の通りです。

libio/libio.h
# define _IO_getwc_unlocked(_fp) \
   || ((_fp)->_wide_data->_IO_read_ptr \
       >= (_fp)->_wide_data->_IO_read_end), 0) \
  (_IO_BE ((_fp)->_wide_data == NULL \
   ? __wuflow (_fp) : (_IO_wint_t) *(_fp)->_wide_data->_IO_read_ptr++)

この処理を見ると_IO_read_ptrというポインタに文字データを収納していることがわかります。
ここでIO_read_ptrは同じソース上のlibio/libio.h内で定義されており、文字を読み込むためのポインタを表してます。

libio/libio.h
struct _IO_FILE {
    int _flags;     /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
    /* The following pointers correspond to the C++ streambuf protocol. */
    /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
    char* _IO_read_ptr; /* Current read pointer */
    char* _IO_read_end; /* End of get area. */
    char* _IO_read_base;    /* Start of putback+get area. */
    char* _IO_write_base;   /* Start of put area. */
    char* _IO_write_ptr;    /* Current put pointer. */
    char* _IO_write_end;    /* End of put area. */
    char* _IO_buf_base; /* Start of reserve area. */
    char* _IO_buf_end;  /* End of reserve area. */

よって、キーボードから入力された文字はこのポインタに文字列を収納していることがわかります。

おわりに

今回は、scanfの中身にある関数の定義をソースコードから追っていくことでscanfの動作を調べてみました。
このようにソースコードの定義を追っていくことで関数の挙動を分析することを静的解析といいます。
また機会があればscanf以外のC言語標準ライブラリの中身も分析していきたいです。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1