Cの配列の目的は、ポインターがその仕事を果たした可能性があるときは何ですか?


8

配列とポインターはCでは同じものではありませんが、それらは関連しており、同様に使用できます。これまでのところ、全員が同意しています。

しかし、ポインタがCに含まれていた理由はわかりません。

配列表記(たとえば、a [5]またはint a [4] = {0,1,2,3};)を削除することは言っていません。これは非常に便利で便利です。しかし、見栄えをよくするために、ポインタと同じように(同じように)同じ表記を使用することもできます。したがって、配列表記は配列を持つ理由ではなく、単に表記です!

私が目にする唯一の違いは、配列は定数ポインタであり、それらが指すメモリのサイズは変更できないことです。しかし、これはポインタを使用して、正確にそれらを一定にすることによっても達成できます(メモリは固定サイズではありませんが、これが問題であるかどうかはわかりません)。

それでは、なぜポインタだけではなく、プログラマにポインタの動作(つまり、定数、定数ではなく、固定サイズ、可変サイズなど)を決定させるのでしょうか。


6
バイトで同じことを達成できるときにcharがあるのはなぜですか?
WuHoUnited 2010年

2
しかし、より深刻な注意点として、ポインタを使用してメモリ割り当てをどのように達成するつもりですか?
WuHoUnited 2010年

5
個人的には、ポインタは(時々)逆ではなく配列であるふりをしていると考えています。では、多くの他の言語、ポインタはしていない配列のような振る舞いを持っている-あなたはどちらかへのポインタの配列タイプを使用するか、ポインタ演算(バイト、ない要素でカウント)を行うポインタを通じてアレイにアクセスしたい場合。私は、Cの発明者がポインタを微調整してより便利な配列のような動作を実現するという点で考えたかなりの金額を賭けます-彼らがすでに配列のようなポインタの動作をしていたと私は非常に疑っていますも」
Steve314、2011年

3
171で同じことを達成できるのに、なぜ0xABがあるのですか?
e-MEE、2011年

2
なぜx = a + b * 2;単純な式のシーケンスで同じことを達成できるような複合式があるのx = b; x*=2; x+=a;ですか?
Fortran、2011年

回答:


14

配列は、スタック上に作成された連続したメモリです。この構文シュガーがなければ、連続したスタックメモリを保証することはできません。可能であれば、別のポインターを割り当てて、ポインターの計算を実行できるようにする必要があります(ただし*(&foo + x)、私がそうしたくない場合を除きます)。もちろん、l値のセマンティクスに違反する可能性がありますが、少なくともかなり扱いにくく、ある種の構文上の砂糖を叫ぶことになります)。デザイン的には、単一の識別子でコレクションを参照できるので、カプセル化の一種でもあります(別の方法で別のポインターが必要になります)。そして、それらを連続して割り当て、それらを参照する別のポインターを割り当てることができたint fooForSomething, fooForSomethingElseとしても、コレクションが成長するにつれてかなりの創造性を強制するかのどちらか になるでしょう。 int foo1, foo2 ...、これは配列のように見えますが、保守が困難です。


ああ、そしてfoo1、foo2 ...の値を渡すと、特にメンバーの数を可変にしたい場合は、面倒になります。
kylben 2011年

2
自動として宣言された配列のみがスタックに置かれます。それ以外のもの(つまりstatics)は通常、環境によっては他の場所に行き着きます。
Blrfl 2011年

これは「構文上の砂糖」ではなく、はるかに深い-C型システムの重要な部分であり、悪名高い配列の崩壊です。
SKロジック

1
スタック上の配列とalloca()の違いは何
ですか

@sylvanaar、このSOの質問を参照してください: stackoverflow.com/questions/1018853/… 私は実際にそれを聞いたことがありませんが、それは危険であると私が予想する方法では、明らかに非標準で危険です。
kylben '21年

12

配列表記は便利で読みやすく、エラーが発生しにくくなります。これは、ポインタの形式を提供します。シンタックスシュガーかもしれませんが、たまには少し甘さが必要ですよね。

すべての抽象化と同様に、抽象化が提供する便宜のために少しの柔軟性をあきらめます。


1
質問で述べたように、配列表記には反対していません。しかし、同じ表記がポインターの上で機能する可能性があるので、配列自体の必要性はどこにあるのでしょうか?
Daniel Scocco、2011年

1
@DanielScocco-多分それはできました。しかし、それでもこのように作られました。話の終わり。その時点で著者の頭の中で何が起こっていたのか誰も知らないので、誰もこれに実際に答えることはできません。
2010年

10

多次元配列についてまだ誰もコメントしていないことに驚いています。

「ネストされたポインタ」(たとえばint **p)で構成されるマトリックスがある場合、各「行」(外部次元)にあるのはその行の最初の要素を指すポインタなので、値にアクセスするには2つのメモリアクセスが必要です。さらに、必要なメモリはですsizeof(*int)*n + n*m*sizeof(int)

2次元配列のシナリオint p[n][m]では、行のアドレスは検索ではなく計算されるため、要素へのアクセスに必要なメモリアクセスは1回だけです。必要なメモリはちょうどn*m*sizeof(int)です。

配列をポインターで置き換えることができないもう1つの場所は、構造体内です。

struct s {
    int[2];
    float;
}

間違いなく同じではありません

struct s {
   *int;
   float;
}

そこでは配列のサイズが重要であり、ポインタはその情報を持っていません。

つまり、一次元配列と単一ポインターはほとんど交換可能ですが、それらの類似点はそこで終わります。


アセンブラの違いを教えてください。多次元配列、構造パッキング、パディングはすべて、言語を理解して使用する必要がある人間に意味のある参照フレームを提供するための単なる方法です。
sylvanaar

@sylvanaarあなたは本気ですか?最初の構造体が2つの整数と浮動小数点数のスペースを保持し、2番目の構造体がメモリアドレスと浮動小数点数を格納するためだけにあることを確認するために、アセンブラを実際に確認する必要がありますか?
Fortran、2011年

1
私はついていません。これらすべての命令セットで何をしなければならないのですか?opは、ポインタではなく配列で何ができるかを尋ねていました。固定サイズの配列で構造体を宣言することは、ポインタではエミュレートできません。多次元配列については、もちろんあなたは、メモリのちょうどチャンクを持つことができ、セルのインデックスを計算するi,jことでi*cols+j、私は一人でそれを行うには持っていないこと配列型の存在を正当化する理由は良いのに十分だと思います。
Fortran、2011年

2
「構造体の宣言[...]は、コンパイルされたイメージに静的メモリ割り当てを作成します」いいえ、(構造体型またはその他の型の)変数を宣言すると、これが行われます。構造体を宣言すると、その型の大きさと各メンバーのオフセットがコンパイラにどのように伝えられるかがわかります。1つは割り当てないか、ローカル変数としてのみ割り当てる場合があります。
Random832 2011年

1
+1は、コンパイラに関する限り、固定サイズの配列が「タイプ」であることの実際的な価値を多分に識別します-多次元配列、特に、コンパイラーがポインター計算を実行して、それらに簡単にインデックスを付けることができるという事実適切なサイズの場合、配列を実際の「タイプ」にすることで、他の単純な言語ルールから自然かつエレガントに抜けます。それなしで同じ構文シュガーを実現できますが、ロジックはより特殊なケースでなければなりません。コンパイラと言語仕様で。
mtraceur

2

値型に配列を使用できないようにしたいのはなぜですか?

int a[4] = {0,1,2,3};


ポインタがあっても、それを使用できますね。コンパイラは、何をすべきかを知っていればよいだけです。
Daniel Scocco

2
@DanielScoccoそして、どうすればそれが何をすべきかを知るのでしょうか?アレイ
ラチェットフリーク

同意-配列でない場合、ローカルまたはグローバル変数として同じタイプのアイテムのシーケンス用にスペースを予約する機能を何と呼びますか?ポインタ型はポインタ型です-それが指すものがシーケンスであるかどうか、またはどのサイズのシーケンスであるかは指定しません。
Steve314、2011年

4
@DanielScoccoポインタが付いていない配列に付属しているのは、サイズに関する情報です。したがって、(ポインター、サイズ)のペアまたは(ポインター、ポインター)のペアは、配列の名前と同じだけの情報を保持できます。これが配列ポインターのペアであること(またはその逆)であることを意味するかどうかは、形而上学的な質問のように思えるので、私はそれに答えることにはあまり興味がありません。(ポインタのペアはスライスのように見えるため、答えはおそらくノーです。したがって、配列を参照するだけでは
不十分

1
@Daniel Scocco、配列にはサイズがあります。スタックに配列を割り当てたり、配列を連続した構造にしたり、共用体内で使用したりできます。配列タイプが存在しない場合、それは不可能です。
SKロジック

1

外部メモリのない8031など、サポートしていないプラットフォームをどのように処理しますmallocalloca?たぶん、Cはビッグアイロン用だけでなく、エレベーターのコントローラーやトースター用でもあることを忘れているでしょう。


1

Cでは、式内の配列表記は常に単純なポインター演算です。式での配列識別子のすべての使用は、「Tの配列」から「Tへのポインター」にすぐに変換され、値は配列の最初の要素へのポインターに変換されます。配列表記(例:)a[1][2]は常にポインタ演算(例:)に展開されます*(*(t+1)+2)

ただし、宣言と定義の配列表記はまったく別のものです。配列宣言子は、「Tの配列」型を記述します。この型の値は、T型の要素のシーケンスです。配列オブジェクトの定義は、便利で簡単に理解できる配列表記を使用して、オブジェクトの目的の配列に適切な量のストレージを割り当て、配列識別子がポインタのように見えなくてもこのストレージを参照できるようにすることです。実際には、宣言または定義の配列表記は、sizeof()算術を使用して式を生成するマクロであり、定義の場合は、alloca() 自動配列の場合、またはグローバル配列のリンカーでの同等の場合、およびコンパイル時にすべて実行する(C99可変長配列を除く)。

式で配列表記を使用することと配列型を使用することはそれほど密接に関連しているわけではありませんが、式で配列表記を使用して、配列として宣言または定義されたオブジェクトのストレージを参照することは伝統と慣用です。ポインターの計算をよりクリーンで意味のあるものにするために、ポインター型で配列表記を簡単に使用できます。確かにCでは、形式の式は式とまったくe1[e2]同じ*((e1)+(e2))です。通常のバイナリ変換が2つのオペランドに適用され、結果は常に左辺値になります。間接演算子は(以来*)そのオペランドの一つとしてポインタを有していなければならないe1し、e2ポインタでなければならず、他方が整数でなければならないが、それは問題ではありません「Tの配列」の最初の単項変換は「Tへのポインタ」に変換するためです。式の配列表記は、実質的にはポインター算術式を生成するコンパイラー(言語レベル)マクロです。

したがって、実際にはCはすでに提案された方法で動作しますが、2つの非常に異なるコンテキストでの表記法の使用を混乱させています。

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