Cとアセンブリでmyprintf()を作成するー土台作り編ー
Cとアセンブリでprintf()関数もどきをprintf()関数を用いずに自力で実装した。
ソースコードは以下。
https://gist.github.com/totzYuta/76ef4055ea4c03778e17
出力は以下のようになる。
I am Yuta Totsuka, my age is 21
このプログラムの挙動について順を追ってメモしておく。
main部分
main部分から流れを確認していく。
int main() { myprintf("I am %s, my age is %d", "Yuta Totsuka", 21); return 0; }
プログラムが走り出したらまずmain()関数が呼ばれ、myprintf()関数を呼び出す。
このサンプルでは可変引数として
- "I am %s, my age is %d": string
- "Yuta Totsuka": string
- 21: integer
の3つの引数を与える。
このmyprintf()関数が無事実行されたら0を返してこのプログラムは終了。
myprintf部分
ということで次はmain()関数内で呼ばれたmyprintf()関数を見ていく。
まずこの関数では可変引数を受け取る。1つめはfmtをchar型のポインタで、2つ目以降は...の形で受け取る。
void myprintf(char *fmt, ...) {
このあたりはドット3つで可変引数を受け取るを参照。
以下でまず必要な変数を定義。
int i, argc = 0; char *s;
fmt++
で*fmtのポインタが指す場所をひとつずつ繰り上げていき、char *fmt
の中身がなくなるまで出力する処理を繰り返す、という仕組み。
while (*fmt) { // 出力処理 fmt++; }
続いて出力処理部分。ここでは*fmtが指すデータが'%'だった場合とそうでない場合とで出力の処理を分岐する。
while (*fmt) { if (*fmt == '%') { // 各型に対応する変数の出力処理 }else { // 文字をそのまま出力 } fmt++; }
'%'だったらその'%'自体は出力せずfmtを次のアドレスを指すよう繰り上げ、そこに入っているアルファベットに従って、switchで各型に応じた出力処理に振り分ける。
// 各型に対応する変数の出力処理 fmt++; argc++; switch (*fmt) { case '%': // Process of %% break; case 'c': // Process of %c break; case 'd': // Process of %d i = *((int*) ((char *)&fmt + argc * sizeof(void *)) ); print_int(i); break; case 'u': // Process of %u break; case 's': // Process of %s s = *((int*) ((char *)&fmt + argc * sizeof(void *)) ); print_string(s); break; break; }
'%'以外の場合はその文字をそのまま出力する。
// 文字をそのまま出力 print_char(*fmt);
そして最後にprint_charをCで実装する。
void print_char(char c) { char s[2]; s[0] = c; s[1] = '\0'; print_string(s) }
syscalls.s
printf()関数なしでコンソールに文字を出力するための関数をsyscalls.s
で定義している。
詳しくはこちらを参照。
さいごに
これでprintf()関数における最初の最初の最初の1っぽとなるようなプログラムを作成することができた。(printf()関数はめちゃくちゃ奥が深い。。。)
ということで次回からはこのプログラムを用いて機能を加えた強化版myprintf()関数を作ってみたいと思う。
疑問点
s = *((int*) ((char *)&fmt + argc * sizeof(void *)) );
ここの部分どうなってんねん!なぜCプログラム中でprint_charを作ったのか?アセンブリのsyscallのサービスとして提供されていなかっただろうか。(番号11)