配列名はポインターですか?


203

配列の名前はCのポインターですか?そうでない場合、配列の名前とポインタ変数の違いは何ですか?


4
いいえ。ただし、配列は同じです&array [0]

36
@pst:&array[0]配列ではなくポインタを生成します;)
jalf

28
@Nava(およびpst):配列&array [0]は実際には同じではありません。適例sizeof(array)sizeof(&array [0])は異なる結果を与えます。
Thomas Padron-McCarthy

1
@Thomasは同意しますが、ポインターに関しては、arrayおよび&array [0]を逆参照すると、同じ値のarray [0] .ie * array == array [0]を生成します。これらの2つのポインターが同じであることを誰もが意味していませんが、この特定のケース(最初の要素を指す)では、配列の名前も使用できます。
Nava Carmon、

1
これらも理解に役立つ可能性があります。stackoverflow.com / questions
Dinah

回答:


255

配列は配列であり、ポインターはポインターですが、ほとんどの場合、配列名はポインターに変換されます。よく使われる用語は、ポインタに腐敗することです。

ここに配列があります:

int a[7];

a 次のように、7つの整数用のスペースが含まれており、割り当てを使用してそれらの1つに値を配置できます。

a[3] = 9;

ここにポインタがあります:

int *p;

p整数用のスペースは含まれていませんが、整数用のスペースを指すことができます。たとえば、a最初の場所など、配列内のいずれかの場所を指すように設定できます。

p = &a[0];

混乱する可能性があるのは、次のように書くこともできるということです。

p = a;

これは、配列の内容をポインターにコピーしませ(それが何であれ)。代わりに、配列名は最初の要素へのポインターに変換されます。したがって、その割り当ては前の割り当てと同じです。apa

これでp、配列と同じように使用できます。

p[3] = 17;

これが機能する理由は、Cの配列逆参照演算子[ ]がポインターで定義されているためです。x[y]つまり、ポインタxから始め、ポインタが指すyものの前に要素を進め、そこにあるものをすべて取ります。ポインタ算術構文を使用して、x[y]と書くこともできます*(x+y)

これがのような通常の配列で機能するためには、最初にin aの名前aa[3](の最初の要素へのa)ポインタに変換する必要があります。次に、3つの要素を前に進め、そこにあるものをすべて取ります。つまり、配列の3番目の要素を取得します。(最初の要素には0と番号が付けられているため、これは配列の4番目の要素です。)

したがって、要約すると、Cプログラムの配列名は(ほとんどの場合)ポインターに変換されます。1つの例外はsizeof、配列に対して演算子を使用する場合です。aこのコンテキストでsizeof aがポインタに変換された場合、実際の配列ではなくポインタのサイズが得られます。これはかなり役に立たないため、その場合aは配列自体を意味します。


5
同様の自動変換は関数ポインタに適用される-の両方functionpointer()(*functionpointer)()奇妙なことに、同じことを意味します。
Carl Norum、

3
配列とポインタが同じかどうかは尋ねませんでしたが、配列の名前がポインタである場合
Ricardo Amores

32
配列名はポインターではありません。これは、配列型の変数の識別子であり、要素型のポインターへの暗黙の変換を持っています。
Pavel Minaev、2009年

29
また、とは別に、sizeof()配列->ポインターの減衰がない他のコンテキストは演算子です&-上記の例で&aは、7の配列intへのポインターであり、singleへのポインターではありませんint。つまり、その型はになりますがint(*)[7]、暗黙的にに変換することはできませんint*。このように、関数は実際に特定のサイズの配列へのポインターを取得し、型システムを介して制限を強制できます。
Pavel Minaev 2009年

3
@ onmyway133、簡単な説明と詳細な引用については、ここをチェックしてください。
Carl Norum、2015

36

配列を値として使用する場合、その名前は最初の要素のアドレスを表します。
配列が値として使用されていない場合、その名前は配列全体を表します。

int arr[7];

/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */

/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */

20

配列型(配列名など)の式がより大きな式に現れ、それが&or sizeof演算子のオペランドではない場合、配列式の型は "TのN要素配列"から「Tへのポインタ」。式の値は、配列の最初の要素のアドレスです。

要するに、配列名はポインタではありませんが、ほとんどのコンテキストでは、ポインタであるかのように扱わます。

編集する

コメントで質問に答える:

sizeofを使用する場合、配列の要素のみのサイズをカウントしますか?次に、配列の「ヘッド」も長さとポインタに関する情報を使用して領域を占有します(これは、通常のポインタよりも多くの領域を使用することを意味します)?

配列を作成する場合、割り当てられる唯一のスペースは要素自体のスペースです。別のポインターまたはメタデータ用のストレージは実体化されません。与えられた

char a[10];

あなたが覚えているのは

   +---+
a: |   | a[0]
   +---+ 
   |   | a[1]
   +---+
   |   | a[2]
   +---+
    ...
   +---+
   |   | a[9]
   +---+

発現は、 aアレイ全体を指すが、全くありません対象 a配列要素自体とは別のは したがって、sizeof a配列全体のサイズ(バイト単位)がわかります。式&aは、配列のアドレスを提供します。これは、最初の要素のアドレスと同じです。差&aとは、&a[0]結果のタイプである1 - char (*)[10]最初のケース及びchar *第二に。

奇妙なのは、個々の要素にアクセスしたいときです。式a[i]は、結果として定義され*(a + i)ます。アドレス値を指定すると、そのアドレスからの要素(バイトではなく)をaオフセットし、結果を逆参照します。i

問題は、それaがポインタやアドレスではなく、配列オブジェクト全体にあることです。コンパイラは、(例えば、配列型の発現を見るたびにこのように、C内のルールは、そのaタイプを有する、char [10]及びその発現はのオペランドではないsizeofか、単項&演算子、その式のタイプは、(「減衰」)に変換されポインタ型(char *)に、式の値は配列の最初の要素のアドレスです。したがって、式は a式と同じ型と値を有し&a[0](ひいては、式は*a式と同じタイプ及び値を有しますa[0])。

CはBと呼ばれる以前の言語に由来し、Bにしてa いた配列要素から別のポインタオブジェクトa[0]a[1]などリッチーは、Bの配列のセマンティクスを維持したいが、彼は別のポインタオブジェクトを格納して台無しにしたくありませんでした。それで彼はそれを取り除きました。代わりに、コンパイラーは必要に応じて、変換中に配列式をポインター式に変換します。

配列にはサイズに関するメタデータは格納されないことを思い出してください。その配列式がポインターに「減衰」するとすぐに、1つの要素へのポインターしかありません。その要素は、一連の要素の最初の要素でも、単一のオブジェクトでもかまいません。ポインタ自体に基づいて知る方法はありません。

配列式を関数に渡すと、関数が受け取るすべての要素は最初の要素へのポインターです-配列の大きさはわかりません(これがgets関数が非常に脅威であり、最終的にライブラリから削除された理由です)。関数が配列の要素数を知るには、センチネル値(C文字列の0ターミネーターなど)を使用するか、要素数を別のパラメーターとして渡す必要があります。


  1. アドレス値の解釈方法に影響する可能性があるのは、マシンによって異なります。

この答えについてはかなり長い間探していました。ありがとうございました!そして、あなたが知っているなら、配列式が何であるかをもう少し教えてもらえますか?sizeofを使用する場合、配列の要素のみのサイズをカウントしますか?次に、配列「ヘッド」も長さとポインタに関する情報を使用してスペースを占有します(これは、通常のポインターよりも多くのスペースを使用することを意味します)?
Andriy Dmytruk 2017

後もう一つ。長さ5の配列は、int [5]型です。それは、sizeof(array)を呼び出したときに長さがわかっている場所からです-その型から?そして、これは、長さが異なる配列は、定数の異なるタイプのようであることを意味しますか?
Andriy Dmytruk 2017

@AndriyDmytruk:sizeofは演算子であり、オペランドのバイト数(オブジェクトを表す式、または括弧内の型名)に評価されます。したがって、配列の場合sizeofは、要素の数に単一の要素のバイト数を掛けた値に評価されます。場合int4バイト幅であり、その後の5要素の配列は、int20バイトを占めます。
John Bode、

演算子も[ ]特別ではありませんか?たとえば、、次にの場合、int a[2][3];x = a[1][2];書き換えることはできますがx = *( *(a+1) + 2 );、ここでaはポインタ型に変換されませんint*a関数の引数の場合はに変換する必要がありますint*)。
Stan

2
@Stan:式にaはtypeがありint [2][3]、type に「減衰」しint (*)[3]ます。式に*(a + 1)はtype int [3]があり、「減衰」してになりint *ます。したがって、*(*(a + 1) + 2)タイプはになりintます。 a第3要素の配列を指しinta + 1の第3要素の配列を指しint*(a + 1) の第3要素の配列int*(a + 1) + 2第二の配列の第三の要素を指しint、そう*(*(a + 1) + 2) の第二の配列の第三の要素はint。それがどのようにマシンコードにマッピングされるかは完全にコンパイラ次第です。
John Bode

5

このように宣言された配列

int a[10];

メモリを10 int秒間割り当てます。変更はできませんaが、を使用してポインタ演算を実行できますa

このようなポインタは、ポインタだけにメモリを割り当てますp

int *p;

は割り当てられませんint。あなたはそれを修正することができます:

p = a;

次のように、配列の添字を使用できます。

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect

2
配列は常にスタックに割り当てられるわけではありません。Yhatは、コンパイラーごとに異なる実装の詳細です。ほとんどの場合、静的配列またはグローバル配列は、スタックとは異なるメモリ領域から割り当てられます。CONST型の配列は、メモリのさらに別の領域から割り当ててもよい
マークBessey

1
私はGrumdrigが割り当て10」と言うためのものだと思いint自動ストレージduration`と秒。
軌道上での明度レース

4

配列名だけでメモリ位置が得られるため、配列名をポインタのように扱うことができます。

int a[7];

a[0] = 1976;
a[1] = 1984;

printf("memory location of a: %p", a);

printf("value at memory location %p is %d", a, *a);

そして、あなたがポインタに行うことができる他の気の利いたもの(例えば、オフセットの加算/減算)、あなたは配列にも行うことができます:

printf("value at memory location %p is %d", a + 1, *(a + 1));

言語的には、Cが配列をある種の「ポインタ」として公開しなかった場合(厳密には、これは単なるメモリの場所です。メモリ内の任意の場所を指すことはできず、プログラマが制御することもできません)。私たちは常にこれをコーディングする必要があります:

printf("value at memory location %p is %d", &a[1], a[1]);

1

この例は問題にいくつかの光を投げかけると思います:

#include <stdio.h>
int main()
{
        int a[3] = {9, 10, 11};
        int **b = &a;

        printf("a == &a: %d\n", a == b);
        return 0;
}

これはgcc 4.9.2で(2つの警告付きで)正常にコンパイルされ、以下を出力します。

a == &a: 1

おっとっと :-)

したがって、結論はノーです。配列はポインタではなく、メモリに格納されていません(読み取り専用でもありません)。たとえアドレスが&演算子を使用して取得できるため、このように見えます。しかし-おっと-その演算子は機能しません:-))いずれにせよ、あなたは警告されました:

p.c: In function main’:
pp.c:6:12: warning: initialization from incompatible pointer type
  int **b = &a;
            ^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
  printf("a == &a: %d\n", a == b);

C ++は、コンパイル時のエラーでそのような試みを拒否します。

編集:

これは私が示すつもりでした:

#include <stdio.h>
int main()
{
    int a[3] = {9, 10, 11};
    void *c = a;

    void *b = &a;
    void *d = &c;

    printf("a == &a: %d\n", a == b);
    printf("c == &c: %d\n", c == d);
    return 0;
}

にもかかわらずca同じメモリへの「ポイント」、あなたはのアドレス取得することができますcポインタを、しかし、あなたはのアドレス取得することはできませんaポインタを。


1
「正常にコンパイルされます(警告が2つあります)」。それは良くありません。を追加してgccに適切な標準Cとしてコンパイルするように指示すると、-std=c11 -pedantic-errors無効なCコードを書き込むとコンパイラエラーが発生します。のint (*)[3]変数にを割り当てようとするからですint**。これは、互いにまったく関係のない2つの型です。したがって、この例が証明するはずのものは、私にはわかりません。
ランディン2018年

ランディン、コメントありがとうございます。あなたは多くの標準があることを知っています。編集で何を意味するのかを明確にしようとしました。int **タイプは1つが良く使用する必要があり、そこにポイントではありませんvoid *。このため。
パロ

-3

配列名はポインタのように動作し、配列の最初の要素を指します。例:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

両方の印刷ステートメントは、マシンに対してまったく同じ出力を提供します。私のシステムではそれは与えました:

0x7fff6fe40bc0

-4

配列は、メモリ内の連続した要素のコレクションです。Cでは、配列の名前は最初の要素のインデックスであり、オフセットを適用すると、残りの要素にアクセスできます。「最初の要素へのインデックス」は、実際にはメモリ方向へのポインタです。

ポインター変数との違いは、配列の名前が指している場所を変更できないことです。そのため、constポインターに似ています(似ていますが、同じではありません。Markのコメントを参照してください)。しかし、ポインタ演算を使用する場合、値を取得するために配列名を逆参照する必要がないことも:

char array = "hello wordl";
char* ptr = array;

char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'

だから答えはちょっと「はい」です。


1
配列名はconstポインターと同じではありません。与えられた:int a [10]; int * p = a; sizeof(p)とsizeof(a)は同じではありません。
マークベッシー

1
他にも違いがあります。一般に、C規格で使用されている用語、特に「変換」と呼ばれる用語を使用することをお勧めします。引用:「sizeof演算子または単項&演算子のオペランドである場合、または配列の初期化に使用される文字列リテラルである場合を除いて、型が '' array of type ''の式は、型が 'の式に変換されます配列オブジェクトの初期要素を指し、左辺値ではない「タイプへのポインタ」。配列オブジェクトにレジスタストレージクラスがある場合、動作は未定義です。
Pavel Minaev、2009年

-5

配列名は、配列の最初の要素のアドレスです。つまり、配列名はconstポインタです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.