5

以下では、誰かが実行時に得られた出力を説明できます:

#include<stdio.h>
void main()
{
char a[5][10]={"one","two","three","four","five"};
char **str=a;
printf("%p ", &a[0]);
printf("\n%p ", &str[0]);
printf("\n%p ", &str[3]);
printf("\n%p ", &str[1][56]);
printf("\n%p ", &(*(*(str+4)+1)));
}

以下は、観測された出力です。

0xbf7f6286 
0xbf7f6286 
0xbf7f6292 
0x38 
0x1 
  • &a[0] は配列の先頭アドレスのアドレス
  • &str[0] は同じです

    str[3] のアドレスが 0xbf7f6292 である理由を説明できる人がいます。私の理解によれば、 &str[0] と &str[3] は 30 バイト異なるはずです。

    また、他の2つのケースの出力についても誰かが説明してください。

前もって感謝します

4

5 に答える 5

8

非常に短い答え

これを変える:

char **str;

これになるには:

char (*str)[10];

その後、コードのエラーを修正します。の型strは、2 次元配列の型と互換性がありません。私は自由に戻り値の型をintforに修正しましたmain()(C 標準では void は許可されていません)。技術的に未定義の動作があるソースにもマークを付けました。

#include <stdio.h>
int main()
{
    char a[5][10]={"one","two","three","four","five"};
    char (*str)[10] = a;
    printf("%p ", &a[0]);
    printf("\n%p ", &str[0]);
    printf("\n%p ", &str[3]);
    printf("\n%p ", &str[1][56]);       // this is undefined behaviour
    printf("\n%p ", &(*(*(str+4)+1)));
    return 0;
}

printf()また、最後のステートメントには&(*(...))不要な余分なものがあることに注意してください。これは次のように削除できます。これは同等です。

    printf("\n%p ", *(str+4)+1);

出力 (システム依存)

0x7fff5fbff900 
0x7fff5fbff900 
0x7fff5fbff91e 
0x7fff5fbff942 
0x7fff5fbff929 

非常に(非常に、非常に)長い答え

2 次元配列がポインタ ツー ポインタと同等であるという仮定は正しくありません。これらは、構文のみの使用法が似ています。以下は、配列aがメモリ内でどのように表現されるかの大まかなレイアウトです。

char a[5][10]

    0   1   2   3   4   5   6   7   8   9 
  -----------------------------------------
0 | o | n | e | 0 |   |   |   |   |   |   |
  -----------------------------------------
1 | t | w | o | 0 |   |   |   |   |   |   |
  -----------------------------------------
2 | t | h | r | e | e | 0 |   |   |   |   |
  -----------------------------------------
3 | f | o | u | r | 0 |   |   |   |   |   |
  -----------------------------------------
4 | f | i | v | e | 0 |   |   |   |   |   |
  -----------------------------------------

2 番目の行の開始アドレスである は、最初の行の開始アドレスを&a[1]10 バイト過ぎていることに注意してください&a[0](配列全体の開始アドレスとは偶然ではありません)。以下はこれを示しています。

int main()
{
    char a[5][10] = { "one", "two", "three", "four", "five" };

    printf("&a    = %p\n", &a);
    printf("&a[0] = %p\n", &a[0]);
    printf("a[0]  = %p\n", a[0]);
    printf("&a[1] = %p\n", &a[1]);
    printf("a[1]  = %p\n", a[1]);
    return 0;
}

出力

&a    = 0x7fff5fbff8e0
&a[0] = 0x7fff5fbff8e0
a[0]  = 0x7fff5fbff8e0
&a[1] = 0x7fff5fbff8ea
a[1]  = 0x7fff5fbff8ea

のアドレスは、配列の先頭からa[1]10 バイト ( 0x0a16 進数) 後ろにあることに注意してください。しかし、なぜ?これに答えるには、型付きポインター演算の基本を理解する必要があります。


型付きポインター演算はどのように機能しますか?

C および C++ では、 を除くすべてのポインターvoidが型指定されます。には、ポインターに関連付けられた基本データ型があります。例えば。

int *iptr = malloc(5*sizeof(int));

iptr上記は、5 つの整数のサイズに割り当てられたメモリ領域を指しています。通常配列をアドレス指定するようにアドレス指定すると、次のようになります。

iptr[1] = 1;

しかし、次のように簡単に対処できます。

*(iptr+1) = 1;

結果は同じになります。2 番目の配列スロット (0 スロットが最初) に値 1 を格納します。*逆参照演算子は、アドレス (ポインターに格納されているアドレス) を介したアクセスを許可することがわかっています。しかし、次の整数スロットにアクセスするために(iptr+1)4 バイト (型が 32 ビットの場合、64 ビットの場合は 8 バイト) をスキップする方法を知るにはどうすればよいでしょうか?int

回答:型付きポインター演算のためです。コンパイラは、ポインターの基になるintの幅 (この場合は型の幅) をバイト単位で認識します。スケーラー値をポインターに加算またはポインターから減算すると、コンパイラーはこの「型幅」を考慮して適切なコードを生成します。また、ユーザー タイプでも機能します。これを以下に示します。

#include <stdio.h>

typedef struct Data
{
    int ival;
    float fval;
    char buffer[100];
} Data;

int main()
{
    int ivals[10];
    int *iptr = ivals;
    char str[] = "Message";
    char *pchr = str;
    Data data[2];
    Data *dptr = data;

    printf("iptr    = %p\n", iptr);
    printf("iptr+1  = %p\n", iptr+1);

    printf("pchr    = %p\n", pchr);
    printf("pchr+1  = %p\n", pchr+1);

    printf("dptr    = %p\n", dptr);
    printf("dptr+1  = %p\n", dptr+1);

    return 0;
}

出力

iptr    = 0x7fff5fbff900
iptr+1  = 0x7fff5fbff904
pchr    = 0x7fff5fbff8f0
pchr+1  = 0x7fff5fbff8f1
dptr    = 0x7fff5fbff810
dptr+1  = 0x7fff5fbff87c

と のアドレスの違いが1 バイトiptriptr+1ないことに注意してください。これは4バイトです (私のシステムでは の幅ですint)。次に、1 バイトの幅をとcharで示します。最後に、2 つのポインター値を持つカスタム データ型は、0x6C、つまり 108 バイト幅であることを示しています。(構造体のパッキングとフィールドの配置により、より大きくなる可能性がありますが、この例ではそうではなかったのは幸運でした)。構造体には 2 つの 4 バイト データ フィールド ( anおよび a ) と 100 要素幅の char バッファーが含まれているため、これは理にかなっています。pchrpchr+1Datadptrdptr+1intfloat

ところで、その逆もまた真であり、経験豊富な C/C++ プログラマーでさえ考慮されないことがよくあります。型付きポインタ差分です。有効なメモリの連続した領域内に、指定された型の有効なポインターが 2 つある場合:

int ar[10];
int *iptr1 = ar+1;
int *iptr5 = ar+5;

次の結果として何が得られると思いますか。

printf("%lu", iptr5 - iptr1);

4答えは.. おっと、あなたは言います。大したことではありませんか?あなたはそれを信じていませんか?これは、ポインター演算を使用して特定の要素のバッファー内のオフセットを決定する場合に非常に便利です。

要約すると、次のような式がある場合:

int ar[5];
int *iptr = ar;

iptr[1] = 1;

次と同等であることがわかります。

*(iptr+1) = 1;

これは、素人の言葉で言えば、「iptr変数に保持されているアドレスを取得し、それに1 *(バイト単位の幅int)バイトを追加1してから、返されたアドレスを介して逆参照されたメモリに値を格納する」ことを意味します。

サイト バー: これも機能します。理由を考えてみてください

1[iptr] = 1;

あなたの (私たちの) サンプル配列に戻って、 の同じアドレスを参照するとどうなるかを見てみましょうa

char **str = a; // Error: Incompatible pointer types: char ** and char[5][10]

それはうまくいきません。しかし、それがあったと仮定しましょう。char **文字へのポインタへのポインタです。これは、変数自体がポインタ以外のものを保持していないことを意味します。aベース行幅などの概念はありません。したがって、のアドレスをdouble-pointer に入れると仮定しますstr

char **str = (char **)(a); // Should NEVER do this, here for demonstration only.
char *s0 = str[0];  // what do you suppose this is?

テスト プログラムを少し更新します。

int main()
{
    char a[5][10] = { "one", "two", "three", "four", "five" };
    char **str = (char **)a;
    char *s0 = str[0];
    char *s1 = str[1];

    printf("&a    = %p\n", &a);
    printf("&a[0] = %p\n", &a[0]);
    printf("a[0]  = %p\n", a[0]);
    printf("&a[1] = %p\n", &a[1]);
    printf("a[1]  = %p\n", a[1]);

    printf("str   = %p\n", str);
    printf("s0    = %p\n", s0);
    printf("s1    = %p\n", s1);

    return 0;
}

次の結果が得られます。

&a    = 0x7fff5fbff900
&a[0] = 0x7fff5fbff900
a[0]  = 0x7fff5fbff900
&a[1] = 0x7fff5fbff90a
a[1]  = 0x7fff5fbff90a
str   = 0x7fff5fbff900
s0    = 0x656e6f
s1    = 0x6f77740000

ええと、strそれは私たちが望んでいるように見えますが、それは何s0ですか? なぜ、それは文字の ASCII 文字値です。どれ?まともなASCIIテーブルを簡単にチェックすると、次のことがわかります。

0x65 : e
0x6e : n
0x6f : o

それは逆の「1」という言葉です(逆は私のシステムでのマルチバイト値のエンディアン処理によって引き起こされますが、問題が明らかであることを願っています.その2番目の値はどうですか:

0x6f : o
0x77 : w
0x74 : t

ええ、それは「2」です。では、なぜ配列内のバイトをポインターとして取得するのでしょうか?

うーん..ええ、コメントで言ったように。double ポインターと 2 次元配列が同義であるとあなたに言った、またはほのめかした人は誰でも間違っていました。型付きポインター演算への中断を思い出してください。これを覚えて:

str[1]

この:

*(str+1)

は同義です。良い。ポインタのは何ですか?strそれが指す型はcharポインタです。したがって、これの間のバイト数の違い:

str + 0

この

str + 1

のサイズになりますchar*。私のシステムでは 4 バイトです (私は 32 ビットのポインタを持っています)。これは、見かけのアドレスが元の配列のベースへの 4 バイトstr[1]データである理由を説明しています。

したがって、最初の基本的な質問に答えます (はい、最終的にそれにたどり着きました)。

なぜstr[3]ですか0xbf7f6292

答え: これ:

&str[3]

これと同等です:

(str + 3)

しかし、上からわかっている(str + 3)のは、 に格納されているアドレスだけであり、型ポイントstrの幅の 3 倍(バイト単位) をそのアドレスに追加します。良い。私たちはあなたの秒からそのアドレスが何であるかを知っています:strchar *printf

0xbf7f6286

また、システム上のポインターの幅は 4 バイト (32 ビット ポインター) であることがわかっています。したがって...

0xbf7f6286 + (3 * 4)

また....

0xbf7f6286 + 0x0C = 0xbf7f6292
于 2013-03-14T05:29:45.697 に答える
3

最初の 2 つの printf は明らかに同じアドレスを出力します。つまり、1 次元配列である 2 次元配列の最初の要素です。したがって、どちらも配列の先頭、つまり a[0][0] を指していることに疑いの余地はありません。

char **str はポインターであるため、str[3] を実行すると、内部的には (str+3) を意味し、おそらくコードは 32 ビット マシンで実行されているため、ポインター airthematic に従って 12 バイト先にジャンプします。配列と混同しないでください。str は配列ではなく 2 次元ポインターです。

0xbf7f6286 str[0] 0xbf7f6292 str[3] diff は 0xc で、予想どおり正しいです。

printf("\n%p ", &str[1][56]); printf("\n%p ", &( ( (str+4)+1)));

上記の最後の 2 つのステートメントは、ポインター airthematic を間違って理解したために印刷しようとしていたものです。

あなたの疑問を解決するのに役立つことを願っています。

于 2013-03-14T04:56:36.797 に答える
1
char a[5][10]={"one","two","three","four","five"};
char **str=a;

2つの開始ポインターがどのように機能するか、および配列との関係について説明します。これにより、自分で質問を解決しようとする可能性が高くなります。

従来、二重星ポインターには、別のポインターの「アドレス」が割り当てられていました。

char   a =  5;
char  *b = &a;
char **c = &b;

したがって、cを一度参照解除すると

printf("\n%d",*c)

、'b'に含まれる値を取得します。これは、'a'のアドレスに他なりません。

cを2回参照解除した場合

printf("\n%d",**c)

、'a'の値を直接取得します。

次に、配列とポインターの関係について説明します。

配列の名前はそのアドレスの同義語です。つまり、配列の名前はそれ自体の場所です。

例:

int array [5] = {1,2,3,4,5};
printf("\n%p",array);
printf("\n%p",&array);
printf("\n%p",&array[0]);

それらはすべて同じアドレスを出力します。

したがって、これは無効なポインタ割り当てです

char **str=a;

** strは、アドレスを保持するポインタのアドレスを想定していますが、アドレス[配列の名前=a]のみを渡しているためです。

したがって、要するに、2つ星のポインタを使用して2次元配列を処理することはできません。

この質問は、ポインターを使用した2次元配列の処理を扱います。

于 2013-03-14T05:22:03.903 に答える
0

str+3 と言うとき、それは str+(3*sizeof(char **)) を意味します。したがって、' char ** ' のサイズが 4 バイトの場合、str は 12 バイト増加します。

于 2013-03-14T05:07:01.877 に答える
0

2D 配列を出力したい場合は、2 つのループ (2 つの「for」ループをお勧めします) を使用し、一方を他方の中に入れ子にする必要があります。

出力に関しては、配列要素にアクセスする前に「&」と入力すると、そのアドレスを要求しているため、それらはすべてのアドレスです (明白なことを述べるつもりはありません)。

配列 a を str に等しく設定しているため、実際にはポインター str を配列 'a' が指すアドレスに設定しています。

アクセス演算子 [int n] を使用すると、実際には、配列 (またはポインター) が実際に指している場所から 'n' 個のアドレス スロットを調べるようにコンピューターに指示していることになります。したがって、上位 2 つよりも 16 多いのはなぜですか。

最後の 2 行は Hex のように見えますが、最後の 2 行の出力行については完全にはわかりません。

于 2013-03-14T05:07:35.150 に答える