25

CI のポインターと配列に関する詳細を読むと、少し混乱してしまいます。一方では、配列はデータ型として見ることができます。一方、配列は変更不可能な左辺値になる傾向があります。コンパイラは、実行時に配列の識別子を定数アドレスとインデックスによって与えられた位置を計算するための式に置き換えるようなことをすると思います。

myArray[3] -(compiler)-> AE8349F + 3 * sizeof(<type>)

配列がデータ型であると言うとき、これは正確にはどういう意味ですか? 配列が実際に何であるか、およびコンパイラによってどのように扱われるかについての私の混乱した理解を明確にするのを手伝ってくれることを願っています.

4

2 に答える 2

21

配列がデータ型であると言うとき、これは正確にはどういう意味ですか?

データ型は、定義済みの特性を持つ値を持つデータのセットです。データ型の例: 整数、浮動小数点数、文字、文字列、ポインター

配列は、すべて同じ名前と同じ型を持つという事実によって関連付けられたメモリ位置のグループです。


なぜ配列が変更できないのか疑問に思っているなら、私が今まで読んだ中で最も良い説明は次のとおりです。

Cは、デニス・リッチーの心から完全に形成されたわけではありません。これは、B (BCPL から派生した) として知られる初期の言語から派生したものです。1 B は「型のない」言語でした。整数、浮動小数点数、テキスト、レコードなどの異なる型はありませんでした。代わりに、すべてが固定長の単語または「セル」(本質的には符号なし整数) でした。メモリは、セルの線形配列として扱われました。次のように、B で配列を割り当てた場合

auto V[10];

コンパイラは 11 個のセルを割り当てました。配列自体の 10 個の連続したセルに加えて、最初のセルの位置を含む V にバインドされたセル:

    +----+
V:  |    | -----+
    +----+      |
     ...        |
    +----+      |
    |    | <----+
    +----+
    |    |
    +----+
    |    |      
    +----+
    |    |
    +----+
     ...

Ritchie がstructC に型を追加していたとき、彼はこの配置が彼にいくつかの問題を引き起こしていることに気付きました。たとえば、ファイルまたはディレクトリ テーブルのエントリを表す構造体型を作成したいと考えていました。

struct {
  int inumber;
  char name[14];
};

彼は、この構造がエントリを抽象的な方法で記述するだけでなく、実際のファイル テーブル エントリのビットを表すことも望んでいました。これには、配列内の最初の要素の位置を格納するための余分なセルや単語がありませんでした。そこで彼はそれを取り除きました - 最初の要素のアドレスを格納する別の場所を確保する代わりに、配列式が評価されるときに最初の要素のアドレスが計算されるように C を書きました。

これが、次のようなことができない理由です

int a[N], b[N];
a = b;

と の両方がそのコンテキストのポインターaに評価されるためです。と同等です。配列の最初の要素のアドレスを実際に格納するメモリはありません。コンパイラは、変換フェーズで単純に計算します。 b3 = 4


1. これはすべてThe Development of the C Languageという論文からの抜粋です。


詳細については、この回答をお読みください。


編集:より明確にするために; 変更可能な左辺値、変更不可能な左辺値、および右辺値 (要するに) の違い。

これらの種類の式の違いは次のとおりです。

  • 変更可能な左辺は、アドレス指定可能 (単項 & のオペランドにすることができます) および代入可能 (= の左オペランドにすることができます) です。
  • 変更不可能な左辺値はアドレス可能ですが、割り当て可能ではありません。
  • r値は、アドレス可能でも代入可能でもありません。
于 2013-07-23T21:43:38.183 に答える
0

配列は、メモリの連続したブロックです。これは、順番にメモリに配置されることを意味します。次のような配列を定義するとしましょう。

int x[4];

どこsizeof(int) == 32のビット。

これは、このようにメモリに配置されます(任意の開始アドレスを選択します。たとえば、0x00000001

0x00000001 - 0x00000004
[element 0]
0x00000005 - 0x00000008
[element 1]
0x00000009 - 0x0000000C
[element 2]
0x0000000D - 0x00000010
[element 3]

コンパイラが識別子を置き換えるのは正しいです。配列は本質的にポインターであることを覚えておいてください (これを学んだ場合。そうでない場合は、何か新しいことを学んだことになります!)。0x00000001C/C++ では、配列名は配列の最初の要素へのポインター (または、この例ではアドレスを指すポインター) です。これを行うことにより:

std::cout << x[2];

ポインター演算であるそのメモリアドレスに2を追加するようにコンパイラーに指示しています。代わりに、変数を使用してインデックスを作成するとします。

int i = 2;
std::cout << x[i];

コンパイラはこれを見ます:

int i = 2;
std::cout << x + (i * sizeof(int));

基本的に、データ型のサイズに指定されたインデックスを掛けて、それを配列のベースアドレスに追加します。コンパイラは基本的に index-of 演算子[]を取り、それをポインターによる加算に変換します。

これについて本当に頭を回転させたい場合は、次のコードを検討してください。

std::cout << 2[x];

これは完全に有効です。理由がわかれば、コンセプトを理解できたことになります。

于 2013-07-23T20:55:49.667 に答える