2016-12-25 8 views
3

多次元配列に関しては混乱があります。 int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}};C多次元配列と逆参照配列ポインタ

私は、変数zippo*zippoを印刷するとき、それは同じメモリアドレスを示し、次のように最も近い私の理解で私を助けに来た質問は、私は、多次元配列を初期化してい

Pointer address in a C multidimensional array

この記事ましたどちらの場合でも、私が**zippoを印刷すると、2(最初のサブアレイの最初の値)が出力されます。私の質問は、zippoが最初の配列の最初の値を出力するために2回デリファレンスされたとき、コンパイラはそれをどのように知っていますか?たとえば、メモリアドレスがzippoの場合、zippo*zippoの値が15の場合、次のような表現をメモリに格納する必要があります。

memory addresses

それは、*zippoはちょうどそうだから、15であることを起こる場所、で値を見つけるために、メモリ位置15に行くというのが私の理解であるそれを印刷するために別の時間の原因15を参照外してはいけません再び?

+0

ギザギザの配列(int **のようなもの)については、すでに何百もの質問があります。なぜ完全に異なるタイプが使用されると思いますか? 'int'を持っていれば' _Complex'を 'printf'できません! – Olaf

答えて

-1

if the memory address of zippo is 30 and the value of zippo and *zippo is 15のように見えますが、それは起こっていません。 データ型に関して考えてみましょう。超次元配列を取る。

int zappo[2][3][4][5][6] = {{{{{45,55,66,77,88,99},{12,22,32.... 

あなたはこのような変数を定義(スタック上に、mallocをチェーン使用していない)、コンパイラは別の* zappoと** zappoなどのために他のためにそれがで45,55,66,77,88,99,12,22,32 を書き込み、1つの値zappoを割り当てません。連続したメモリブロック、例えば0xfe。今コンパイル時に、それはそれを知っています

zappo is a pointer, and has a value 0xfe 
*zappo is a pointer, and has a value 0xfe 
**zappo is a pointer, and has a value 0xfe 
***zappo is a pointer, and has a value 0xfe 
****zappo is a pointer, and has a value 0xfe 
*****zappo is a pointer, and has a value 0xfe, but this one points to an int! 

コンパイラはデータ型に関して考えます。したがって、最後の逆参照のみがintになり、残りは1つのアドレスになります。 これは(いくつかのALLOCと、​​ヒープ内の)

int *****zappo; 

を宣言し、苦労手動アレイ構造の作成と同じではありません。それはあなたのボックスの類推を使用できる場所です。

+0

これはもっと理にかなっています。私はJavaから来て、動的に割り当てられるだけの配列に慣れています。ありがとうございます – BlaqICE

+0

ようこそ。それは、ポインタへの配列の崩壊として知られているCの奇抜です。詳細は、たとえばDeep Cの秘密、Chap 4の本で見つけることができます。しかし、操作上の知識には必要ありません。 – Elan

+3

このレベルの実装の詳細は使用しないでください。 **自動**変数のC言語でのスタックの必要はありません。 '* zappo'はポインタではなく**配列です! '** zappo'は**ポインタではなく**配列です! ...そしてどれも '0xfe'の値を持っていません。それはちょうど彼らのアドレスです。配列の値は何ですか?配列はポインタではありません(実際には後半の状態です)! – Olaf

-1

いいえ*zippo場所15に行かないで、この場所の値を見つけます。 そうならばprintf(" * ((int **) zippo) = %p\n", * ((int **) zippo));printf(" *zippo = %p\n", *zippo);と同じものを出力しますが、そうではありません。

私はこのコードを実行すると、これは私が得るものです:

#include <stdio.h> 

int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}}; 

int main(){ 
    printf("zippo[0] = %p\n", (void *) (zippo[0])); 
    printf(" zippo = %p\n", (void *) zippo); 
    printf(" *zippo = %p\n", (void *) (*zippo)); 
    printf(" **zippo = %d\n", (int) (**zippo)); 
    printf(" * ((int **) zippo) = %p\n", (void *) (* ((int **) zippo))); 
} 

これは私が得るものです:

zippo = 0x804a040 
*zippo = 0x804a040 
**zippo = 2 
* ((int **) zippo) = 0x2 

私は何の警告が隠されていないことを確認するためにgcc -Wall -Wextra -Wpedantic -pedanticを使用してこのコードをコンパイルし、オプション-m32には32ビットのアドレス(intと同じサイズ)があります。

実際に何が起こっているのか分かりにくいので、対応するアセンブリコードを見てみることにしました。 gcc -S file.c -o file.sを使用して、以下を得る。

最初の変数宣言:

.globl zippo 
    .data 
    .align 32 
    .type zippo, @object 
    .size zippo, 32 
zippo: 
    .long 2 
    .long 4 
    .long 6 
    .long 8 
    .long 1 
    .long 3 
    .long 5 
    .long 7 
    .section .rodata 
.LC0: 
    .string "zippo[0] = %p\n" 
.LC1: 
    .string " zippo = %p\n" 
.LC2: 
    .string " *zippo = %p\n" 
.LC3: 
    .string " **zippo = %d\n" 
.LC4: 
    .string " * ((int **) zippo) = %p\n" 

correcspondingアセンブリprintf("zippo[0] = %p\n", (void *) (zippo[0]));用:

movl $zippo, %esi 
movl $.LC0, %edi 
movl $0, %eax 
call printf 

correcspondingアセンブリprintf(" zippo = %p\n", (void *) zippo);用:

movl $zippo, %esi 
movl $.LC1, %edi 
movl $0, %eax 
call printf 

printf(" *zippo = %p\n", (void *) (*zippo));

ためcorrecspondingアセンブリ
movl $zippo, %esi 
movl $.LC2, %edi 
movl $0, %eax 
call printf 

printf(" **zippo = %d\n", (int) (**zippo));ためcorrecspondingアセンブリ:

movl $zippo, %eax 
movl (%rax), %eax 
movl %eax, %esi 
movl $.LC3, %edi 
movl $0, %eax 
call printf 

correcspondingアセンブリprintf(" * ((int **) zippo) = %p\n", (void *) (* ((int **) zippo)));

movl $zippo, %eax 
movq (%rax), %rax 
movq %rax, %rsi 
movl $.LC4, %edi 
movl $0, %eax 
call printf 

ためのあなたがここに気づくことができるように、3最初のprintfのために、対応するアセンブリがexactely同じです(フォーマットに対応するLCxはどのような変更ですか)。 最後の2つのprintfと同じことです。

zippoが2次元配列であることをコンパイラが認識しているので、*zippoは、最初の要素のアドレスからデータが始まる1次元配列です。

+0

これは無視されません! – Olaf

+0

説明できます... – Hedi

+0

コードは未定義の動作を呼び出します。 'int'へのポインタとして' int'配列を読み込みます。 '%p'が' void * 'を期待し、キャストしなければならないという事実は、UBのもう一つの原因です(コンパイラは実際に文句を言うべきです)。 "コンパイラは、メモリに存在していてもいないzippoはナンセンスであることを知っています。もちろん、* zippoは存在します。それは配列です。 – Olaf

1

あなたは低レベルすぎると思っています。あなたの質問は、変数名とタイプ(言語レベル)に関係しています。

int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}};を宣言すると、2つのintの4つの配列の配列になります。あなたは何を表現する必要があるかに応じて、さまざまな方法でアクセスできます。サブオブジェクトには以下のものが含まれます:

| 2 | 4 | 6 | 8 | 1 | 3 | 5 | 7 | The storage for zippo: 8 contiguous ints 

|<--+---+---+---+---+---+---+-->| zippo (the whole array), is an int[4][2] 
|<--+-->|       zippo[0] (also known as *zippo) is an int[2] 
       |<--+-->|   zippo[2] is also an int[2] 
|<->|        zippo[0][0] (also known as **zippo) is an int 
      |<->|     zippo[1][1] is also an int 

これらのサブオブジェクトは重複し、場合によってはアドレスを共有できることがわかります。何が依然としてそれらをオブジェクト(あなた、言語、コンパイラのために)にするかは、それらの型です。

は、例えば、(その最初の半分である)zippo[0]zippo[0][0]は同じアドレスを持っているが、他の2つのintの配列であるが、それらの一つは、intあります。

インデックスをzippo[0][0]に保存したり、整数計算でzippo[0]を使用したりすることはできません。同じストレージを共有していても、意味の異なるオブジェクトです。

配列へのインデックス付けにはポインタの算術演算が含まれていますが、ポインタの実際の連鎖はありません。int***は最初の意味を暗示していません。それはすべての変数名です。