ポインタ減衰への配列とは何ですか?配列ポインターとの関係はありますか?
std::decay
C ++ 14からは、単項+を超えて配列を減衰させる、それほど曖昧ではない方法になります。
+a
と+b
C ++で法的です、それはCで違法である(C11 6.5.3.3/1「単項のオペランド+
または-
オペレータがなければなりません算術型 ")
ポインタ減衰への配列とは何ですか?配列ポインターとの関係はありますか?
std::decay
C ++ 14からは、単項+を超えて配列を減衰させる、それほど曖昧ではない方法になります。
+a
と+b
C ++で法的です、それはCで違法である(C11 6.5.3.3/1「単項のオペランド+
または-
オペレータがなければなりません算術型 ")
回答:
配列はポインタに「減衰」すると言われています。として宣言されたC ++配列はint numbers [5]
再ポイントできませんnumbers = 0x5a5aff23
。つまり、とは言えません。さらに重要なのは、用語「崩壊」はタイプと次元の喪失を意味します。ディメンション情報(カウント5)を失うことでnumbers
崩壊し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はコンパイル時に既知である必要があります。
T a[]
は、と同じですT *a
。by_pointerは、ポインター値が修飾されることを除いて、同じことを渡しますconst
。(配列の最初の要素へのポインターではなく)配列へのポインターを渡したい場合、構文はT (*array)[U]
です。
a
の配列でありchar
、その後、a
型でありchar[N]
、且つに減衰しますchar*
。しかし&a
タイプのものでありchar(*)[N]
、かつますない崩壊。
U
変更がある場合、2か所で変更することを覚えておく必要がない場合、またはサイレントバグのリスクがある場合...自律性!
配列は基本的にC / C ++のポインターと同じですが、完全ではありません。配列を変換したら:
const int a[] = { 2, 3, 5, 7, 11 };
ポインタへ(キャストなしで機能するため、場合によっては予期せず発生する可能性があります):
const int* p = a;
sizeof
配列内の要素をカウントする演算子の機能が失われます。
assert( sizeof(p) != sizeof(a) ); // sizes are not equal
この失われた能力は「減衰」と呼ばれます。
これは標準が言うことです(C99 6.3.2.1/3-その他のオペランド-左辺値、配列、および関数指定子):
sizeof演算子または単項&演算子のオペランドである場合、または配列の初期化に使用される文字列リテラルである場合を除いて、型 '' array of type ''の式は、型 '' pointer to type ''は、配列オブジェクトの初期要素を指し、左辺値ではありません。
これは、ほとんどの場合、配列名が式で使用されると、自動的に配列の最初の項目へのポインターに変換されることを意味します。
関数名は同じように機能しますが、関数ポインターの使用ははるかに少なく、より特殊化された方法で行われるため、配列名からポインターへの自動変換ほど混乱は生じません。
C ++標準(4.2配列からポインターへの変換)では、変換要件が緩和されます(強調は次のとおりです)。
「NTの配列」または「Tの未知の境界の配列」タイプの左辺値または右辺値は、「T へのポインタ」タイプの右辺値に変換できます。
そのため、変換はCで常に行われるように行われる必要はありません(これにより、関数をオーバーロードしたり、テンプレートを配列型に一致させたりできます)。
これが、Cで関数のプロトタイプ/定義で配列パラメーターを使用しないようにする理由でもあります(私の意見では、一般的な合意があるかどうかはわかりません)。それらは混乱を引き起こし、とにかくフィクションです-ポインターパラメーターを使用すると、混乱が完全になくなるわけではありませんが、少なくともパラメーター宣言は嘘をつきません。
char x[] = "Hello";
。6つの要素の配列は"Hello"
減衰しません。代わりx
にサイズ6
を取得し、その要素はの要素から初期化され"Hello"
ます。
「ディケイ」とは、配列型からポインタ型への式の暗黙的な変換を指します。ほとんどのコンテキストでは、コンパイラーが配列式を検出すると、式のタイプを「TのN要素配列」から「Tへのポインター」に変換し、式の値を配列の最初の要素のアドレスに設定します。 。この規則の例外は、配列がsizeof
or &
演算子のオペランドである場合、または配列が宣言で初期化子として使用されている文字列リテラルである場合です。
次のコードを想定します。
char a[80];
strcpy(a, "This is a test");
式a
のタイプは「80要素配列のchar」であり、式「This is a test」はタイプ「16要素配列のchar」です(Cでは、C ++文字列リテラルはconst charの配列です)。ただし、への呼び出しではstrcpy()
、どちらの式もsizeof
orのオペランドではない&
ため、それらの型は暗黙的に「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_element
とptr_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への単純なポインタです。
"This is a test" is of type "16-element array of char"
"15-element array of char"
Cの配列には値がありません。
オブジェクトの値が予期されているがオブジェクトが配列である場合は常に、代わりにそのタイプの最初の要素のアドレスが使用されます pointer to (type of array elements)
ます。
関数では、すべてのパラメーターが値で渡されます(配列も例外ではありません)。関数で配列を渡すと、その配列は「ポインタに減衰」します(sic)。配列を他のものと比較すると、再び「ポインタに減衰」します(sic)。...
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演算子、または&演算子(「アドレス」演算子)のオペランド、または文字配列の初期化に使用される文字列リテラルとしてのみです。
配列が回転してポイントされているときです;-)
実際、配列をどこかに渡したいが、代わりにポインタが渡された場合(地獄が配列全体を渡すのは誰のためか)、貧しい配列はポインタに腐っていると人々は言う。
a + 1
。
配列減衰とは、配列がパラメーターとして関数に渡された場合、ポインターと同じように扱われる(「減衰する」)ことを意味します。
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のようなセキュアCRT関数のC ++バージョンに使用します。同様のトリックを使用して、配列内の要素の数を確実に取得できます。
関数の引数として配列を渡すには4つの方法があると思うと、とても大胆になるかもしれません。また、これはあなたの熟読のための短いが実用的なコードです。
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
using namespace std;
// test data
// notice native array init with no copy aka "="
// not possible in C
const char* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };
// ONE
// simple, dangerous and useless
template<typename T>
void as_pointer(const T* array) {
// a pointer
assert(array != nullptr);
} ;
// TWO
// for above const T array[] means the same
// but and also , minimum array size indication might be given too
// this also does not stop the array decay into T *
// thus size information is lost
template<typename T>
void by_value_no_size(const T array[0xFF]) {
// decayed to a pointer
assert( array != nullptr );
}
// THREE
// size information is preserved
// but pointer is asked for
template<typename T, size_t N>
void pointer_to_array(const T (*array)[N])
{
// dealing with native pointer
assert( array != nullptr );
}
// FOUR
// no C equivalent
// array by reference
// size is preserved
template<typename T, size_t N>
void reference_to_array(const T (&array)[N])
{
// array is not a pointer here
// it is (almost) a container
// most of the std:: lib algorithms
// do work on array reference, for example
// range for requires std::begin() and std::end()
// on the type passed as range to iterate over
for (auto && elem : array )
{
cout << endl << elem ;
}
}
int main()
{
// ONE
as_pointer(specimen);
// TWO
by_value_no_size(specimen);
// THREE
pointer_to_array(&specimen);
// FOUR
reference_to_array( specimen ) ;
}
また、これはC ++とCの優位性を示していると思うかもしれません。少なくとも参照によって配列を渡すことの参照(しゃれが意図されています)において。
もちろん、ヒープ割り当て、例外、std :: libのない非常に厳密なプロジェクトがあります。C ++のネイティブ配列処理は、ミッションクリティカルな言語機能であると言われるかもしれません。
int a[10]; int b(void);
、その後、+a
int型のポインタで、+b
関数ポインタです。参照を受け入れるテンプレートに渡したい場合に便利です。