質問: 例えば一番最初に覚えたprintf(const char *format, ...);なんですが、引数はchar型を指すポインタと可変個の引数を指定しています。普段何気なく使っている時の知識と、ポインタを勉強して自分なりに解釈すると、formatはポインタなので第一引数に指定する変換指定子は、第二引数に指定した値のアドレスを持っているという理解になるのですが、、、、 ただ、アドレスを持っているとして、例えば4byte記憶域を使用するintの整数を1byteのchar型でなぜ中身を正確に読み解けるのでしょうか?
回答: この辺をちゃんと説明しようと思うとC言語のABI(Application-Binary Interface、よく聞く用語の「API」とかとは別物)を理解してもらわないといけないのですが、できるだけ一般論でまとめてみます。 まず、この解釈: ***formatはポインタなので第一引数に指定する変換指定子は、第二引数に指定した値のアドレスを持っている*** は、おかしいですね。実際に`printf`を呼ぶ場合の式は、 ``` printf("%s(%d)\n", buf, n); ``` みたいな形になることはご存知だと思いますが、引数`format`がポインタとして渡される値は、文字列`"%s(%d)\n"`の先頭アドレスになります。第二引数以降は`format`には全然関わりません。 従って、「4byte記憶域を使用...」も全く関係ないことになります。`char`配列であるC文字列の先頭アドレスを受け取るのですから、`char *`で全く問題ないわです。 ただ、あなたが直感で「第二引数に指定した値のアドレス」も`printf`に渡してやらないと処理できないように感じたのは間違いではありません。ただ、それは全然別の形で渡されます。 通常`...`に対応する実引数は、「スタック」と呼ばれる場所に積み上げられます。`printf`の内部の処理では、`format`文字列に`%`ではじまる書式文字列を見つけるたびに「スタック」の中の「次の場所」を探しに行くわけです。 * `%s`を見つけたから、「スタック」の中から次の場所に入っている`char *`型の値を取り出そう。 * `%d`を見つけたから、「スタック」の中から次の場所に入っている`int`型の値を取り出そう。 * ... (C言語の仕様的にはどっか並べて置ける場所ならいわゆる「スタック」でなくても良いんですが、概念的には同じことです。) 大雑把に書くと`printf`の定義は、こんな感じになってます。 ``` int printf(const char *format, ...) { va_list arg; va_start(arg, format); //... } ``` `va_start(arg, format);`と言うのが、引数`format`の次から初めて`...`の「次の場所」にアクセスするための何か(実態はポインタです、普通)が`arg`に入れられるので、それを使って順に(必要になるたびに)`...`の部分に入れられた値を取り出せます。`va_arg(arg, char*)`と書くと『次の場所に入っている`char *`型の値』を、`va_arg(arg, int)`と書くと『次の場所に入っている`int`型の値』を取り出せます。 (`va_arg`はマクロなんで、第2引数に「型」が書けるんですが、この型が実際の引数の型に会ってなかったりすると、最悪暴走したりクラッシュしたりします。もちろん実引数が2個なのに3個目以上をアクセスするとやっぱりダメ。) さて、あなたがご質問に書かれていることより少し余計なことを書いてしまったかもしれません。 `printf(const char *format, ...);`は **第1引数の型は`const char *`であり、第2引数以降は別の要因で決まる不定個の引数を指定できる** ことを表しています。 もちろん「別の要因」と言うのは`printf`の場合、第1引数の`format`に渡した書式文字列の中身です。