アスキーアートルール!
2D配列を図で見てみましょう。short
配列が2バイト整数であり、アドレスも便利な2バイトであると仮定しましょう。必要に応じて、これはZilog Z80チップにすることもできますが、これは数を少なくするためだけのものです。
short A[3][3];
+---------+---------+---------+
| A[0][0] | A[0][1] | A[0][2] |
+---------+---------+---------+
| A[1][0] | A[1][1] | A[1][2] |
+---------+---------+---------+
| A[2][0] | A[2][1] | A[2][2] |
+---------+---------+---------+
アドレスを想定しましょう:A = 0x4000
。したがってshort *
、配列の要素のアドレスは次のとおりです。
&A[0][0] = 0x4000;
&A[0][1] = 0x4002;
&A[0][2] = 0x4004;
&A[1][0] = 0x4006;
&A[1][1] = 0x4008;
&A[1][2] = 0x400A;
&A[2][0] = 0x400C;
&A[2][1] = 0x400E;
&A[2][2] = 0x4010;
ここで、次のように記述できることにも注意してください。
&A[0] = 0x4000;
&A[1] = 0x4006;
&A[2] = 0x400C;
これらのポインタのタイプは、'のarray[3]へのポインタshort
'、またはshort (*A)[3]
。
次のように書くこともできます。
&A = 0x4000;
このタイプは、'のarray[3] [3]へのポインタshort
、またはshort (*A)[3][3]
。
このコードが示すように、主な違いの1つはオブジェクトのサイズにあります。
#include <stdio.h>
#include <inttypes.h>
static void print_address(const char *tag, uintptr_t address, size_t size);
int main(void)
{
char buffer[32];
short A[3][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } };
int i, j;
print_address("A", (uintptr_t)A, sizeof(A));
print_address("&A", (uintptr_t)&A, sizeof(*(&A)));
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
sprintf(buffer, "&A[%d][%d]", i, j);
print_address(buffer, (uintptr_t)&A[i][j], sizeof(*(&A[i][j])));
}
}
for (i = 0; i < 3; i++)
{
sprintf(buffer, "&A[%d]", i);
print_address(buffer, (uintptr_t)&A[i], sizeof(*(&A[i])));
}
putchar('\n');
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf(" A[%d][%d] = %d", i, j, A[i][j]);
}
putchar('\n');
}
return 0;
}
static void print_address(const char *tag, uintptr_t address, size_t size)
{
printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size);
}
このプログラムは、関数のマスキング操作で16ビットアドレスを偽造しprint_address()
ます。
MacOS X 10.7.2(GCC'i686-apple-darwin11-llvm-gcc-4.2(GCC)4.2.1(Apple Inc.ビルド5658に基づく)(LLVMビルド2335.15.00)で64ビットモードでコンパイルされた場合の出力)')、 だった:
A = 0xD5C0 (size 18)
&A = 0xD5C0 (size 18)
&A[0][0] = 0xD5C0 (size 2)
&A[0][1] = 0xD5C2 (size 2)
&A[0][2] = 0xD5C4 (size 2)
&A[1][0] = 0xD5C6 (size 2)
&A[1][1] = 0xD5C8 (size 2)
&A[1][2] = 0xD5CA (size 2)
&A[2][0] = 0xD5CC (size 2)
&A[2][1] = 0xD5CE (size 2)
&A[2][2] = 0xD5D0 (size 2)
&A[0] = 0xD5C0 (size 6)
&A[1] = 0xD5C6 (size 6)
&A[2] = 0xD5CC (size 6)
A[0][0] = 0 A[0][1] = 1 A[0][2] = 2
A[1][0] = 3 A[1][1] = 4 A[1][2] = 5
A[2][0] = 6 A[2][1] = 7 A[2][2] = 8
32ビットモードでマスキング操作なしでバリアントをコンパイルし、次の出力を取得しました。
A = 0xC00E06D0 (size 18)
&A = 0xC00E06D0 (size 18)
&A[0][0] = 0xC00E06D0 (size 2)
&A[0][1] = 0xC00E06D2 (size 2)
&A[0][2] = 0xC00E06D4 (size 2)
&A[1][0] = 0xC00E06D6 (size 2)
&A[1][1] = 0xC00E06D8 (size 2)
&A[1][2] = 0xC00E06DA (size 2)
&A[2][0] = 0xC00E06DC (size 2)
&A[2][1] = 0xC00E06DE (size 2)
&A[2][2] = 0xC00E06E0 (size 2)
&A[0] = 0xC00E06D0 (size 6)
&A[1] = 0xC00E06D6 (size 6)
&A[2] = 0xC00E06DC (size 6)
A[0][0] = 0 A[0][1] = 1 A[0][2] = 2
A[1][0] = 3 A[1][1] = 4 A[1][2] = 5
A[2][0] = 6 A[2][1] = 7 A[2][2] = 8
また、64ビットモードでは、バリアントからの出力は次のとおりです。
A = 0x7FFF65BB15C0 (size 18)
&A = 0x7FFF65BB15C0 (size 18)
&A[0][0] = 0x7FFF65BB15C0 (size 2)
&A[0][1] = 0x7FFF65BB15C2 (size 2)
&A[0][2] = 0x7FFF65BB15C4 (size 2)
&A[1][0] = 0x7FFF65BB15C6 (size 2)
&A[1][1] = 0x7FFF65BB15C8 (size 2)
&A[1][2] = 0x7FFF65BB15CA (size 2)
&A[2][0] = 0x7FFF65BB15CC (size 2)
&A[2][1] = 0x7FFF65BB15CE (size 2)
&A[2][2] = 0x7FFF65BB15D0 (size 2)
&A[0] = 0x7FFF65BB15C0 (size 6)
&A[1] = 0x7FFF65BB15C6 (size 6)
&A[2] = 0x7FFF65BB15CC (size 6)
A[0][0] = 0 A[0][1] = 1 A[0][2] = 2
A[1][0] = 3 A[1][1] = 4 A[1][2] = 5
A[2][0] = 6 A[2][1] = 7 A[2][2] = 8
32ビットと64ビットのアドレスバージョンには多くのノイズがあるため、「疑似16ビット」アドレスバージョンを維持できます。
のアドレスがとA[0][0]
のアドレスと同じであるが、ポイントされているオブジェクトのサイズが異なることに 注意してください。単一の(短い)整数を指します。3つの(短い)整数の配列を指します。3x3(短い)整数の配列を指します。A[0]
A
&A[0][0]
&A[0]
&A
次に、どのように機能するかを確認する必要がありますshort **
。動作はまったく異なります。これは、前の例に関連しているが異なるテストコードです。
#include <stdio.h>
#include <inttypes.h>
static void print_address(const char *tag, uintptr_t address, size_t size);
int main(void)
{
char buffer[32];
short t[3] = { 99, 98, 97 };
short u[3] = { 88, 87, 86 };
short v[3] = { 77, 76, 75 };
short w[3] = { 66, 65, 64 };
short x[3] = { 55, 54, 53 };
short y[3] = { 44, 43, 42 };
short z[3] = { 33, 32, 31 };
short *a[3] = { t, v, y };
short **p = a;
int i, j;
print_address("t", (uintptr_t)t, sizeof(t));
print_address("u", (uintptr_t)u, sizeof(u));
print_address("v", (uintptr_t)v, sizeof(v));
print_address("w", (uintptr_t)w, sizeof(w));
print_address("x", (uintptr_t)x, sizeof(x));
print_address("y", (uintptr_t)y, sizeof(y));
print_address("z", (uintptr_t)z, sizeof(z));
print_address("a", (uintptr_t)a, sizeof(a));
print_address("&a", (uintptr_t)&a, sizeof(*(&a)));
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
sprintf(buffer, "&a[%d][%d]", i, j);
print_address(buffer, (uintptr_t)&a[i][j], sizeof(*(&a[i][j])));
}
}
for (i = 0; i < 3; i++)
{
sprintf(buffer, "&a[%d]", i);
print_address(buffer, (uintptr_t)&a[i], sizeof(*(&a[i])));
}
putchar('\n');
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf(" a[%d][%d] = %d", i, j, a[i][j]);
}
putchar('\n');
}
putchar('\n');
print_address("p", (uintptr_t)p, sizeof(*(p)));
print_address("&p", (uintptr_t)&p, sizeof(*(&p)));
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
sprintf(buffer, "&p[%d][%d]", i, j);
print_address(buffer, (uintptr_t)&p[i][j], sizeof(*(&p[i][j])));
}
}
for (i = 0; i < 3; i++)
{
sprintf(buffer, "&p[%d]", i);
print_address(buffer, (uintptr_t)&p[i], sizeof(*(&p[i])));
}
putchar('\n');
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf(" p[%d][%d] = %d", i, j, p[i][j]);
}
putchar('\n');
}
return 0;
}
static void print_address(const char *tag, uintptr_t address, size_t size)
{
printf("%-8s = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size);
}
これは2つの半分のプログラムです。半分は配列を分析しa
ます; もう1つは、ダブルポインタを分析しますp
。これを理解するのに役立つASCIIアートを次に示します。
+------+------+------+ +------+------+------+
| 99 | 98 | 97 | t = 0x1000 | 88 | 87 | 86 | u = 0x1100
+------+------+------+ +------+------+------+
+------+------+------+ +------+------+------+
| 77 | 76 | 75 | v = 0x1200 | 66 | 65 | 64 | w = 0x1300
+------+------+------+ +------+------+------+
+------+------+------+ +------+------+------+
| 55 | 54 | 53 | x = 0x1400 | 44 | 43 | 42 | y = 0x1500
+------+------+------+ +------+------+------+
+------+------+------+
| 33 | 32 | 31 | z = 0x1600
+------+------+------+
+--------+--------+--------+
| 0x1000 | 0x1200 | 0x1500 | a = 0x2000
+--------+--------+--------+
+--------+
| 0x2000 | p = 0x3000
+--------+
配列は「任意の」場所に配置されていることに注意してくださいt
。z
図では隣接していません。一部の配列は、たとえば別のファイルからのグローバル変数である可能性があり、他の配列は同じファイル内で関数の外部にある静的変数である可能性があり、他の配列は静的であるが関数に対してローカルである可能性があります。これらのローカル自動変数。p
アドレスを含む変数がどのようになっているのかがわかります。アドレスは配列のアドレスですa
。次に、アレイa
には3つのアドレス、他の3つのアレイのアドレスが含まれます。
これは、人為的に分割されたプログラムの64ビットコンパイルからの出力です。16進アドレスの最後の4桁を除くすべてをマスクすることにより、16ビットアドレスをシミュレートします。
t = 0x75DA (size 6)
u = 0x75D4 (size 6)
v = 0x75CE (size 6)
w = 0x75C8 (size 6)
x = 0x75C2 (size 6)
y = 0x75BC (size 6)
z = 0x75B6 (size 6)
これにより、未使用の変数に関する警告が防止され、3つの整数の7つの配列のアドレスも識別されます。
a = 0x7598 (size 24)
&a = 0x7598 (size 24)
&a[0][0] = 0x75DA (size 2)
&a[0][1] = 0x75DC (size 2)
&a[0][2] = 0x75DE (size 2)
&a[1][0] = 0x75CE (size 2)
&a[1][1] = 0x75D0 (size 2)
&a[1][2] = 0x75D2 (size 2)
&a[2][0] = 0x75BC (size 2)
&a[2][1] = 0x75BE (size 2)
&a[2][2] = 0x75C0 (size 2)
&a[0] = 0x7598 (size 8)
&a[1] = 0x75A0 (size 8)
&a[2] = 0x75A8 (size 8)
a[0][0] = 99 a[0][1] = 98 a[0][2] = 97
a[1][0] = 77 a[1][1] = 76 a[1][2] = 75
a[2][0] = 44 a[2][1] = 43 a[2][2] = 42
重要な違いに注意してください。のサイズはa
、3(64ビット)ポインターの配列であるため、18ではなく24バイトになりました。&a[n]
それぞれがポインタであるため、のサイズは8バイトです。配列の場所にデータをロードする方法もまったく異なります。Cのソースコードは同じように見えるため、アセンブラを調べて確認する必要があります。
2D配列コードでは、のロード操作は次のようにA[i][j]
計算します。
- のバイトアドレス
A
(3 * i + j) * sizeof(short)
そのバイトアドレスに追加します
- そのアドレスから2バイト整数をフェッチします。
ポインタコードの配列では、のロード操作は次のようにA[i][j]
計算します。
- のバイトアドレス
a
i * sizeof(short *)
そのバイトアドレスに追加します
- その計算値からバイトアドレスをフェッチし、それを呼び出します
b
j * sizeof(short)
に追加b
- アドレスから2バイト整数をフェッチします
b
の出力p
は多少異なります。特に、のアドレスは。のアドレスとは異なることに注意して p
ください。ただし、それを過ぎると、動作は基本的に同じになります。 p
p = 0x7598 (size 8)
&p = 0x7590 (size 8)
&p[0][0] = 0x75DA (size 2)
&p[0][1] = 0x75DC (size 2)
&p[0][2] = 0x75DE (size 2)
&p[1][0] = 0x75CE (size 2)
&p[1][1] = 0x75D0 (size 2)
&p[1][2] = 0x75D2 (size 2)
&p[2][0] = 0x75BC (size 2)
&p[2][1] = 0x75BE (size 2)
&p[2][2] = 0x75C0 (size 2)
&p[0] = 0x7598 (size 8)
&p[1] = 0x75A0 (size 8)
&p[2] = 0x75A8 (size 8)
p[0][0] = 99 p[0][1] = 98 p[0][2] = 97
p[1][0] = 77 p[1][1] = 76 p[1][2] = 75
p[2][0] = 44 p[2][1] = 43 p[2][2] = 42
これらはすべて、単一の(メイン)機能に含まれていました。さまざまなポインターを関数に渡し、それらのポインターの背後にある配列にアクセスするときは、独自の並列実験を行う必要があります。