非常に短い答え:
これを変える:
char **str;
これになるには:
char (*str)[10];
その後、コードのエラーを修正します。の型str
は、2 次元配列の型と互換性がありません。私は自由に戻り値の型をint
forに修正しました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 バイト ( 0x0a
16 進数) 後ろにあることに注意してください。しかし、なぜ?これに答えるには、型付きポインター演算の基本を理解する必要があります。
型付きポインター演算はどのように機能しますか?
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 バイトiptr
でiptr+1
はないことに注意してください。これは4バイトです (私のシステムでは の幅ですint
)。次に、1 バイトの幅をとchar
で示します。最後に、2 つのポインター値を持つカスタム データ型は、0x6C、つまり 108 バイト幅であることを示しています。(構造体のパッキングとフィールドの配置により、より大きくなる可能性がありますが、この例ではそうではなかったのは幸運でした)。構造体には 2 つの 4 バイト データ フィールド ( anおよび a ) と 100 要素幅の char バッファーが含まれているため、これは理にかなっています。pchr
pchr+1
Data
dptr
dptr+1
int
float
ところで、その逆もまた真であり、経験豊富な 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 倍(バイト単位) をそのアドレスに追加します。良い。私たちはあなたの秒からそのアドレスが何であるかを知っています:str
char *
printf
0xbf7f6286
また、システム上のポインターの幅は 4 バイト (32 ビット ポインター) であることがわかっています。したがって...
0xbf7f6286 + (3 * 4)
また....
0xbf7f6286 + 0x0C = 0xbf7f6292