47512


例えば一番最初に覚えたprintf(const char *format,
...);なんですが、引数はchar型を指すポインタと可変個の引数を指定しています。普段何気なく使っている時の知識と、ポインタを勉強して自分なりに解釈すると、formatはポインタなので第一引数に指定する変換指定子は、第二引数に指定した値のアドレスを持っているという理解になるのですが、、、、  
ただ、アドレスを持っているとして、例えば4byte記憶域を使用するintの整数を1byteのchar型でなぜ中身を正確に読み解けるのでしょうか？


CC BY-SA 4.0
2018-08-14T12:44:11.067
47510
2018-08-14T13:46:57.963
28645
question
c ポインタ
標準ライブラリの関数の引数の定義の読み解き方がわからない。


この辺をちゃんと説明しようと思うとC言語のABI(Application-Binary
Interface、よく聞く用語の「API」とかとは別物)を理解してもらわないといけないのですが、できるだけ一般論でまとめてみます。

まず、この解釈:

**_formatはポインタなので第一引数に指定する変換指定子は、第二引数に指定した値のアドレスを持っている_**

は、おかしいですね。実際に`printf`を呼ぶ場合の式は、

[code]

    printf("%s(%d)\n", buf, n);
    
[/code]

みたいな形になることはご存知だと思いますが、引数`format`がポインタとして渡される値は、文字列`"%s(%d)\n"`の先頭アドレスになります。第二引数以降は`format`には全然関わりません。

従って、「4byte記憶域を使用…」も全く関係ないことになります。`char`配列であるC文字列の先頭アドレスを受け取るのですから、`char
*`で全く問題ないわです。

* * *

ただ、あなたが直感で「第二引数に指定した値のアドレス」も`printf`に渡してやらないと処理できないように感じたのは間違いではありません。ただ、それは全然別の形で渡されます。

通常`...`に対応する実引数は、「スタック」と呼ばれる場所に積み上げられます。`printf`の内部の処理では、`format`文字列に`%`ではじまる書式文字列を見つけるたびに「スタック」の中の「次の場所」を探しに行くわけです。

  * `%s`を見つけたから、「スタック」の中から次の場所に入っている`char *`型の値を取り出そう。

  * `%d`を見つけたから、「スタック」の中から次の場所に入っている`int`型の値を取り出そう。

  * …

(C言語の仕様的にはどっか並べて置ける場所ならいわゆる「スタック」でなくても良いんですが、概念的には同じことです。)

大雑把に書くと`printf`の定義は、こんな感じになってます。

[code]

    int printf(const char *format, ...)
    {
       va_list arg;
       va_start(arg, format);
    
       //...
    }
    
[/code]

`va_start(arg,
format);`と言うのが、引数`format`の次から初めて`...`の「次の場所」にアクセスするための何か(実態はポインタです、普通)が`arg`に入れられるので、それを使って順に(必要になるたびに)`...`の部分に入れられた値を取り出せます。`va_arg(arg,
char*)`と書くと『次の場所に入っている`char *`型の値』を、`va_arg(arg,
int)`と書くと『次の場所に入っている`int`型の値』を取り出せます。

(`va_arg`はマクロなんで、第２引数に「型」が書けるんですが、この型が実際の引数の型に会ってなかったりすると、最悪暴走したりクラッシュしたりします。もちろん実引数が2個なのに3個目以上をアクセスするとやっぱりダメ。)

* * *

さて、あなたがご質問に書かれていることより少し余計なことを書いてしまったかもしれません。

`printf(const char *format, ...);`は

**第1引数の型は`const char *`であり、第2引数以降は別の要因で決まる不定個の引数を指定できる**

ことを表しています。

もちろん「別の要因」と言うのは`printf`の場合、第1引数の`format`に渡した書式文字列の中身です。


CC BY-SA 4.0
2018-08-14T13:45:53.753
47512
2018-08-14T13:45:53.753
13972
47510
answer


[呼出規約](https://ja.wikipedia.org/wiki/%E5%91%BC%E5%87%BA%E8%A6%8F%E7%B4%84)と言って、関数の呼び出す側がどのように引数を渡すのか＝呼び出された関数が渡された引数をどのように解釈するのか、ということがプラットフォーム毎に定められています。

通常、プロトタイプ宣言が存在すれば、双方はその宣言に従って引数の受け渡しをすれば問題ありません。実はC言語ではプロトタイプ宣言を行っていない関数の呼び出しが認められています。そのような場合にもどのように引数を受け渡しするのかが呼出規約に定められています。同様に質問のケース、可変引数関数の場合においても可変引数部分についての引数の受け渡し方法が呼出規約に定められています。

詳しくは前述のようにプラットフォーム毎に異なるわけですが、多くの場合、`int`以下のサイズの引数は呼び出し側によって`int`に拡張されてから渡されます。ですので呼び出された`printf`としては`char`を受け取るとしても`int`に拡張されていることをふまえて引数を読み取ります。


CC BY-SA 4.0
2018-08-14T13:46:57.963
47513
2018-08-14T13:46:57.963
4236
47510
answer