printfを実装してみたというお話。

何を作ったか

C言語標準ライブラリのprintf関数を実装。
プロトタイプ宣言 int ft_printf(const char *format, ...);
第一引数 format に%dなど含んだ文字列、その後に引数を受けて、
指定された形式で文字列を標準出力へ出力します。

  • 解釈できるformatのルール
    %[フラグ][最小フィールド幅].[精度][変換指定子]
    • 対応フラグ : '-0'
    • 対応変換指定子 : 'cspdiuxX%'

github.com

実装する前に

出力はwrite関数を使い、出力した文字数を返り値として返すためにft_printfプログラム内でwrite関数の返り値を受け取るようにします。
可変長引数を扱うので、stdarg系の関数を使います。

プログラムの流れ

  • おおまかな流れは、
    formatを一文字目から順に見ていき
    '%'があれば%以降の文字列解析->第二引数以降を受け取り->指定された形式で文字を出力していく
    %がなければ文字をそのまま出力する
    例 : ft_printf("%dtokyo", 42)->write("42"), wirte("t"), wirte("o"), write("k"), write("y"), write("o")
  • 解析結果などの情報は逐次構造体にまとめて処理を進めていく
  • 図での流れと、実行例
    f:id:rakiyama0229:20211002001125p:plain
    各ファイルの役割と、処理の流れ
    実行例[ft_printf("%-10.5d", -42)]
    出力[-00042 ]
    • start.c
      • %が指定されているので解析開始、情報を構造体storeに格納
        flag = HYPHEN (フラグ)
        width = 10 (最小フィールド幅)
        prec = 5 (精度)
        spec = SPEC_D (変換指定子)
    • prepare_to_put.c
      • 引数を受け取る->-42
        接頭語を"-"、出力する本体を"42"と考える(こう分けると今後の処理が楽)
        ちなみに%pならprefix="0x"、必要ない時はprefix=""にしとく
      • 出力する文字列の情報を構造体に格納
        body = 2 (本体の文字数 : "42"の文字数)
        zero = 3 (0の文字数 : 精度の5から"42"の文字数2を引いた数)
        prefix = "-"
        blank = 4 (空白の文字数 : フィールド幅の10からbody・zero・prefixの文字数を引いた数)
    • put.c
      put_flag_hyphen()によりprefix, zero, body, blankの順番で文字を出力していく。

      まとめ

      そもそもprintfで変換指定子以外使ったことがなかったので、
      フラグの指定などあらゆる使い方を試してprintfの機能を理解するところから始めました。
      enumと関数ポインタ配列の存在を42tokyoの知人に教えてもらい今回初めて使ってみました。
      enumを使いだすと、配列の利点(任意のアドレスにランダムアクセスできたり)を使えるようになり、そこから関数ポインタ配列を使ってみることになり、コードがシンプルになっていきました。
      コードがまとまっていくと、プログラムの流れもまとめやすくなり、最終的に「変換指定子別で前処理、フラグで出力方法を管理する」方向性が見えてきました。