配列型の式は、次の場合を除き、配列オブジェクトの最初の要素へのポインターに暗黙的に変換されます。
- 単項演算子のオペランド
&
。
- のオペランド
sizeof
; また
- 配列オブジェクトを初期化するために使用される初期化子の文字列リテラル。
3 番目のケースの例は次のとおりです。
char arr[6] = "hello";
"hello"
タイプの配列式です(ターミネータchar[6]
の 5 + 1 )。'\0'
アドレスには変換されません。of の完全な 6 バイト値が"hello"
配列 object にコピーされますarr
。
一方、これでは:
char *ptr = "hello";
配列式"hello"
は へのポインタに「減衰」し、'h'
そのポインタ値はポインタ オブジェクト の初期化に使用されますptr
。(実際には のはずですがconst char *ptr
、それは副次的な問題です。)
関数型 (関数名など) の式は、次の場合を除き、関数へのポインターに暗黙的に変換されます。
- 単項演算子のオペランド
&
。また
sizeof
(のオペランドsizeof function_name
は不正であり、ポインターのサイズではありません)。
それでおしまい。
どちらの場合も、ポインターオブジェクトは作成されません。式は、アドレスとも呼ばれるポインター値に変換 (「減衰」) されます。
(これらの両方の場合の「変換」は、キャスト演算子によって指定されるような通常の型変換ではありません。オペランドの値を取得して、それを使用して結果の値を計算することはありません。int
変換float
. むしろ、配列または関数型の式は、コンパイル時にポインタ型の式に「変換」されます. 私の意見では、「調整された」という言葉は「変換された」よりも明確でした.)
配列インデックス演算子[]
と関数呼び出し "operator" の両方に()
ポインターが必要であることに注意してください。のような通常の関数呼び出しfunc(42)
では、関数名は関数func
へのポインターに「減衰」し、呼び出しで使用されます。(この変換は、関数呼び出しが正しいことを行う限り、生成されたコードで実際に実行する必要はありません。)
関数のルールには、いくつかの奇妙な結果があります。ほとんどの場合、式func
は関数へのポインタに変換されますfunc
。では&func
、func
はポインターに変換されませんが&
、関数のアドレス、つまりポインター値を生成します。では*func
、func
暗黙的にポインターに変換され、それを*
逆参照して関数自体が生成され、(ほとんどのコンテキストで) ポインターに変換されます。では****func
、これが繰り返し発生します。
(C11 標準のドラフトでは、配列には別の例外があると述べています。つまり、配列が new_Alignof
演算子のオペランドである場合です。これはドラフトのエラーであり、最終的に公開された C11 標準で修正されています。_Alignof
括弧内にのみ適用できます。式ではなく型名です。)
配列のアドレスとその最初のメンバーのアドレス:
int arr[10];
&arr; /* address of entire array */
&arr[0]; /* address of first element */
メモリアドレスは同じですが、型が異なります。前者は配列オブジェクト全体のアドレスであり、型int(*)[10]
(10int
の配列へのポインター) です。後者はタイプint*
です。この 2 つの型には互換性がなく (たとえばint*
、オブジェクトに値を合法的に代入することはできません)、ポインター演算は異なる動作をします。int(*)[10]
配列または関数型の宣言された関数パラメーターは、コンパイル時に (変換されずに) ポインター パラメーターに調整されるという別の規則があります。例えば:
void func(int arr[]);
とまったく同等です
void func(int *arr);
これらの規則 (配列式の変換と配列パラメーターの調整) が組み合わさって、C における配列とポインターの関係に関して多くの混乱が生じます。
comp.lang.c FAQのセクション 6 は、詳細を説明する優れた仕事をしています。
これの決定的なソースは、ISO C 標準です。 N1570 (1.6 MB PDF) は、2011 年規格の最新のドラフトです。これらの変換は、セクション 6.3.2.1 の段落 3 (配列) および 4 (関数) で指定されています。そのドラフトには、_Alignof
実際には適用されない への誤った参照があります。
ちなみに、printf
あなたの例の呼び出しは厳密に間違っています:
int fruits[10];
printf("Address IN constant pointer is %p\n",fruits);
printf("Address OF constant pointer is %p\n",&fruits);
format には typeの%p
引数が必要void*
です。ほとんどの実装の場合と同様に、タイプint*
andのポインターがandint(*)[10]
と同じ表現を持ち、同じ方法で引数として渡される場合、動作する可能性がありますが、保証されません。ポインタを次void*
のように明示的に変換する必要があります。void*
int fruits[10];
printf("Address IN constant pointer is %p\n", (void*)fruits);
printf("Address OF constant pointer is %p\n", (void*)&fruits);
では、なぜこのように行われるのでしょうか。問題は、ある意味で配列が C の二流市民であることです。配列を関数呼び出しの引数として値で渡すことはできず、関数の結果として返すこともできません。配列を有効にするには、さまざまな長さの配列を操作できる必要があります。for 、 for 、 forなど (これらはすべて別個の型です) の個別のstrlen
関数は、非常に扱いにくくなります。その代わりに、配列はその要素へのポインターを介してアクセスおよび操作され、ポインター演算はそれらの要素をトラバースする方法を提供します。char[1]
char[2]
char[3]
配列式が (ほとんどのコンテキストで) ポインターに減衰しない場合、結果に対してできることはあまりありません。また、C は、配列とポインターを必ずしも区別していなかった初期の言語 (BCPL と B) から派生したものです。
他の言語は配列を第一級の型として扱うことができますが、そうするためには「C の精神」ではない追加の機能が必要であり、C は依然として比較的低レベルの言語です。
関数をこのように扱う理由についてはよくわかりません。関数型の値がないことは事実ですが、言語は関数呼び出しの接頭辞として (関数へのポインターではなく) 関数を必要とする可能性があり*
、間接呼び出しには明示的な演算子が必要です: (*funcptr)(arg)
. を省略することができるの*
は便利ですが、途方もないものではありません。これはおそらく、歴史的な慣性と配列の扱いとの一貫性の組み合わせによるものです。