web-dev-qa-db-ja.com

C配列はメモリ内でどのように表現されますか?

Cを使用している場合、メモリ内で通常の変数とポインタがどのように表されるかを理解していると思います。

たとえば、ポインタPtrにはアドレスがあり、その値は別のアドレス、つまりそれが指しているメモリ内のスペースであることを理解するのは簡単です。次のコード:

int main(){
    int x = 10;
    int *Ptr;
    Ptr = &x;
return 0;
}

メモリ内では次のように表現されます。

+---------------------+-------------+---------+
| Variable Name       | Address     | Value   | 
+---------------------+-------------+---------+
| x                   | 3342        | 10      |
+---------------------+-------------+---------+
| Ptr                 | 5466        | 3342    |
+---------------------+-------------+---------+

ただし、配列がメモリ内でどのように表されるかを理解するのは困難です。たとえば、コード:

int main(){
    int x[5];
        x[0]=12;
        x[1]=13;
        x[2]=14;

    printf("%p\n",(void*)x);
    printf("%p\n",(void*)&x);

return 0;
}

同じアドレスを2回出力します(簡略化のため10568)。つまり、x ==&xです。ただし、* x(または配列表記のx [0])は12に等しく、*(x + 1)(または配列表記のx [1])は13に等々です。これはどのように表すことができますか? 1つの方法は次のとおりです。

+---------------------+-------------+----------+----------------------+
| Variable Name       | Address     | Value    | Value IF array       |
+---------------------+-------------+----------+----------------------+
| x                   | 10568       | 10568    | 12                   |
+---------------------+-------------+----------+----------------------+
|                     | 10572       |          | 13                   | 
+---------------------+-------------+----------+----------------------+
|                     | 10576       |          | 14                   | 
+---------------------+-------------+----------+----------------------+
|                     | 10580       |          | trash                | 
+---------------------+-------------+----------+----------------------+
|                     | 10584       |          | trash                | 
+---------------------+-------------+----------+----------------------+

これは何が起きるかに近いですか、それとも完全にオフですか?

26
Daniel Scocco

配列は、間にスペースのない連続したオブジェクトのブロックです。つまり、2番目の例のxは、メモリ内で次のように表されます。

_+---------------------+-------------+---------+
| Variable Name       | Address     | Value   | 
+---------------------+-------------+---------+
| x                   | 10568       | 12      |
|                     |             +---------+
|                     |             | 13      |
|                     |             +---------+
|                     |             | 14      |
|                     |             +---------+
|                     |             | ??      |
|                     |             +---------+
|                     |             | ??      |
+---------------------+-------------+---------+
_

つまり、xは5つのintsであり、単一のアドレスを持っています。

配列の奇妙な部分は、配列の格納方法ではなく、式での評価方法です。単項_&_またはsizeof演算子の対象ではない場所で配列名を使用すると、最初のメンバーのアドレスに評価されます。

つまり、単にxと書くと、タイプ_int *_の値10568を取得します。

一方、_&x_を記述した場合、特別な規則は適用されません。そのため、_&_演算子は通常どおりに機能します。つまり、配列のアドレスをフェッチします。例では、これはタイプint (*)[5]の値10568になります。

_x == &x_を使用する理由は、配列は最初のメンバーから始まるため、配列の最初のメンバーのアドレスは、必ず配列自体のアドレスと等しいためです。

33
caf

あなたの図は正しいです。 &xの奇妙さは、配列がメモリ内でどのように表現されるかとは関係ありません。これは、array-> pointer decayに関係しています。 xは、値のコンテキストでそれ自体が最初の要素へのポインタに減衰します。つまり、&x[0]と同等です。 &xは配列へのポインタであり、2つが数値的に等しいという事実は、配列のアドレスが数値的に最初の要素のアドレスに等しいということです。

22
Raymond Chen

はい、そうです。 C配列は、x + (y * sizeof(type))を計算して、インデックス付きの値_x[y]_を見つけます。 xは、配列の開始アドレスです。 y * sizeof(type)は、それからのオフセットです。 _x[0]_は、xと同じアドレスを生成します。

多次元配列も同様に行われるため、_int x[y][z]_はsizeof(int) * y * zメモリを消費します。

このため、いくつかの愚かなCポインターのトリックを行うことができます。また、配列のサイズを取得することは(ほとんど)不可能です。

2
Schwern

C FAQの「配列とポインタ」セクション には、いくつかの役立つ情報があります。

0
Sinan Ünür

ダニエル、

これは難しくありません。基本的な考え方はありますが、配列のメモリ表現に大きな違いはありません。配列を宣言する場合は、

     void main(){
         int arr[5]={0,1,2,3,4};


     }

配列を初期化(定義)しました。したがって、5つの要素はメモリ内の隣接する5つの場所に格納されます。これは、各要素のメモリアドレスを参照することで確認できます。 Cの他のプリミティブデータ型とは異なり、配列識別子(ここではarr)自体がそのポインタを表します。初心者の場合、このアイデアは曖昧に見えますが、進むにつれて快適に感じるでしょう。

      printf("%d",arr);

この行には、最初の要素arr [0]のメモリアドレスが表示されます。これは、最初の要素のアドレスを参照することに似ています。

      printf("%d",&arr[0]);

これで、すべての要素のメモリ位置を表示できます。次のコードがその仕事をします。

    int i;
    for(i=0;i<5;i++){
       printf("location of %d is %d\n",arr[i],&arr[i]);
    } 

各アドレスが4のギャップでインクリメントされます(整数が32ビット長の場合)。したがって、配列がメモリにどのように格納されるかを簡単に理解できます。

別の方法で同じことを試すこともできます。

    int i;
    for(i=0;i<5;i++){
       printf("location of %d is %d\n",*(a+i),a+i);
    }

両方のケースで同じ答えのセットを取得し、同等のものを取得しようとします。

異なるデータ型(char、float、struct型)を使用して同じ実験を試してください。隣接する要素間のギャップが、単一の要素のサイズに基づいてどのように変化するかがわかります。

0
Tharindu Rusira

C配列は、同じサイズの連続する値を持つ単なるメモリのブロックです。 malloc()を呼び出すと、メモリブロックが付与されます。 _foo[5]_は*(foo + 5)と同じです。

例-foo.c:

_#include <stdio.h>

int main(void)
{
    int foo[5];
    printf("&foo[0]: %tx\n", &foo[0]);
    printf("foo: %tx\n\n", foo);
    printf("&foo[3]: %tx\n", &foo[3]);
    printf("foo: %tx\n", foo + 3);
}
_

出力:

_$ ./foo
&foo[0]: 5fbff5a4
foo: 5fbff5a4

&foo[3]: 5fbff5b0
foo: 5fbff5b0
_
0
ObscureRobot

Cの配列は、各メンバーのブロックが同じサイズの順次メモリブロックです。これがポインタが機能する理由であり、最初のメンバーのアドレスに基づいてオフセットを探します。

0
alex