web-dev-qa-db-ja.com

アレイ減衰とは何ですか?

アレイの減衰とは何ですか?配列ポインターとの関係はありますか?

348
Vamsi

配列はポインターに「減衰」すると言われています。 int numbers [5]として宣言されたC++配列は、再ポイントできません。つまり、numbers = 0x5a5aff23とは言えません。さらに重要なことは、崩壊という用語は型と次元の喪失を意味します。 numbersは、ディメンション情報(カウント5)を失うことでint*に減衰し、タイプはint [5]ではなくなります。ここで 減衰が起こらない場合 を探してください。

値で配列を渡す場合、実際に行っているのはポインターのコピーです-配列の最初の要素へのポインターがパラメーターにコピーされます(その型は配列要素の型のポインターでもある必要があります)。これは、アレイの減衰する性質のために機能します。減衰すると、sizeofは本質的にポインターになるため、完全な配列のサイズを提供しなくなります。これが、(他の理由の中でも)参照またはポインタで渡すことが好ましい理由です。

配列を渡す3つの方法1

void by_value(const T* array)   // const T array[] means the same
void by_pointer(const T (*array)[U])
void by_reference(const T (&array)[U])

最後の2つは適切なsizeof情報を提供しますが、最初の1つは、配列引数が減衰してパラメータに割り当てられたため、情報を提供しません。

1定数Uはコンパイル時に認識される必要があります。

253
phoebus

配列は基本的にC/C++のポインターと同じですが、完全ではありません。配列を変換したら:

const int a[] = { 2, 3, 5, 7, 11 };

ポインターに変換します(キャストせずに動作するため、場合によっては予期せず発生する可能性があります)。

const int* p = a;

配列内の要素をカウントするsizeof演算子の機能が失われます。

assert( sizeof(p) != sizeof(a) );  // sizes are not equal

この失われた能力は「崩壊」と呼ばれます。

詳細については、これを確認してください 配列の減衰に関する記事

91
system PAUSE

標準の内容は次のとおりです(C99 6.3.2.1/3-その他のオペランド-左辺値、配列、関数指定子):

Sizeof演算子または単項&演算子のオペランドである場合、または配列の初期化に使用される文字列リテラルである場合を除き、型 '' array of type ''を持つ式は、型 '' pointer toを持つ式に変換されますtype ''は、配列オブジェクトの初期要素を指し、左辺値ではありません。

これは、式で配列名が使用されると、配列の最初の項目へのポインターに自動的に変換されることを意味します。

関数名は同様の方法で機能しますが、関数ポインターの使用ははるかに少なく、はるかに特殊な方法で使用されるため、配列名からポインターへの自動変換ほど混乱は生じません。

C++標準(4.2配列からポインターへの変換)は、変換要件を(強調鉱山)に緩めます:

「N Tの配列」または「Tの未知の境界の配列」型の左辺値または右辺値canは、「Tへのポインター」型の右辺値に変換されます。

そのため、変換はhaveがCで行われるように((-===-))起こりません(これにより、関数がオーバーロードするか、テンプレートが配列型に一致します)。

これが、Cで関数プロトタイプ/定義で配列パラメーターを使用することを避けるべき理由でもあります(私の意見では、一般的な合意があるかどうかはわかりません)。とにかく混乱を引き起こし、フィクションです-ポインターパラメーターを使用すると、混乱は完全になくなるわけではありませんが、少なくともパラメーター宣言は嘘ではありません。

43
Michael Burr

「減衰」とは、式を配列型からポインター型に暗黙的に変換することです。ほとんどのコンテキストでは、コンパイラは配列式を認識すると、式の型を「Nの要素の配列」から「Tへのポインタ」に変換し、式の値を配列の最初の要素のアドレスに設定します。この規則の例外は、配列がsizeofまたは&演算子のオペランドである場合、または配列が宣言で初期化子として使用される文字列リテラルである場合です。

次のコードを想定します。

char a[80];
strcpy(a, "This is a test");

aは「80要素のchar配列」型であり、式「This is a test」は「16要素のchar配列」型です(C、C++では文字列リテラルはconstの配列です) char)。ただし、strcpy()の呼び出しでは、どちらの式もsizeofまたは&のオペランドではないため、それらの型は暗黙的に「charへのポインター」に変換され、値はそれぞれの最初の要素のアドレス。 strcpy()が受け取るものは、そのプロトタイプに見られるように、配列ではなくポインタです。

char *strcpy(char *dest, const char *src);

これは、配列ポインターとは異なります。例えば:

char a[80];
char *ptr_to_first_element = a;
char (*ptr_to_array)[80] = &a;

ptr_to_first_elementptr_to_arrayの両方は同じ;を持っています。 aのベースアドレス。ただし、以下に示すように、これらは異なるタイプであり、異なる方法で処理されます。

a[i] == ptr_to_first_element[i] == (*ptr_to_array)[i] != *ptr_to_array[i] != ptr_to_array[i]

a[i]*(a+i)(配列型がポインター型に変換された場合のみ機能する)として解釈されるため、a[i]ptr_to_first_element[i]の両方が同じように機能することに注意してください。式(*ptr_to_array)[i]*(*a+i)として解釈されます。式*ptr_to_array[i]およびptr_to_array[i]は、コンテキストに応じてコンパイラの警告またはエラーを引き起こす可能性があります。 a[i]に評価することを期待している場合、彼らは間違いなく間違ったことをします。

sizeof a == sizeof *ptr_to_array == 80

繰り返しますが、配列がsizeofのオペランドである場合、ポインター型に変換されません。

sizeof *ptr_to_first_element == sizeof (char) == 1
sizeof ptr_to_first_element == sizeof (char *) == whatever the pointer size
                                                  is on your platform

ptr_to_first_elementは、charへの単純なポインターです。

25
John Bode

Cの配列には値がありません。

オブジェクトの値は予想されるが、オブジェクトが配列である場合は、代わりにその最初の要素のアドレスが使用され、pointer to (type of array elements)型が使用されます。

関数では、すべてのパラメーターは値で渡されます(配列も例外ではありません)。関数に配列を渡すと、「ポインターに減衰」します(原文)。配列を他の配列と比較すると、やはり「ポインターへの減衰」(原文); ...

void foo(int arr[]);

関数fooは配列の値を期待します。しかし、Cでは、配列には値がありません!そのため、fooは、代わりに配列の最初の要素のアドレスを取得します。

int arr[5];
int *ip = &(arr[1]);
if (arr == ip) { /* something; */ }

上記の比較では、arrには値がないため、ポインターになります。これは、intへのポインターになります。そのポインターは、変数ipと比較できます。

配列のインデックス構文では、arrが「ポインターに減衰している」ことを確認するのに慣れています

arr[42];
/* same as *(arr + 42); */
/* same as *(&(arr[0]) + 42); */

配列がポインターに減衰しないのは、sizeof演算子、&演算子( 'address of'演算子)のオペランド、または文字配列の初期化に使用される文字列リテラルとしてのみです。

12
pmg

配列が腐敗し、;-)がポイントされているときです。

実際には、配列をどこかに渡したいが、代わりにポインターが渡される場合(地獄が配列全体を渡すので)、人々は貧しい配列がポインターに崩壊したと言います。

配列の減衰とは、配列が関数にパラメーターとして渡されるときに、ポインターと同じように扱われる(「減衰する」)ことを意味します。

void do_something(int *array) {
  // We don't know how big array is here, because it's decayed to a pointer.
  printf("%i\n", sizeof(array));  // always prints 4 on a 32-bit machine
}

int main (int argc, char **argv) {
    int a[10];
    int b[20];
    int *c;
    printf("%zu\n", sizeof(a)); //prints 40 on a 32-bit machine
    printf("%zu\n", sizeof(b)); //prints 80 on a 32-bit machine
    printf("%zu\n", sizeof(c)); //prints 4 on a 32-bit machine
    do_something(a);
    do_something(b);
    do_something(c);
}

上記には2つの合併症または例外があります。

まず、CおよびC++で多次元配列を処理する場合、最初の次元のみが失われます。これは、配列がメモリ内に連続して配置されるため、コンパイラはそのメモリブロックへのオフセットを計算できるように、最初の次元以外のすべてを知っている必要があります。

void do_something(int array[][10])
{
    // We don't know how big the first dimension is.
}

int main(int argc, char *argv[]) {
    int a[5][10];
    int b[20][10];
    do_something(a);
    do_something(b);
    return 0;
}

第二に、C++では、テンプレートを使用して配列のサイズを推測できます。 Microsoftは、これを strcpy_s などのC++バージョンのSecure CRT関数に使用します。同様のトリックを使用して、確実に 配列内の要素数を取得 にすることができます。

2
Josh Kelley

tl; dr:定義した配列を使用する場合、実際には最初の要素へのポインターを使用します。

副<文>この[前述の事実の]結果として、それ故に、従って、だから◆【同】consequently; therefore <文>このような方法で、このようにして、こんなふうに、上に述べたように◆【同】in this manner <文>そのような程度まで<文> AひいてはB◆【用法】A and thus B <文>例えば◆【同】for example; as an example:

  • arr[idx]と書くと、本当に*(arr + idx)と言っているだけです。
  • 関数は、配列パラメーターを指定しても、実際にはポインターとしてのみ配列をパラメーターとして受け取ることはありません。

このルールの並べ替え例外:

  • struct内の関数に固定長配列を渡すことができます。
  • sizeof()は、ポインターのサイズではなく、配列が占めるサイズを示します。
0
einpoklum