ASCII藝術規則!
讓我們來看一個2D數組。我們假設該數組是2個字節的short
整數,並且地址也是方便的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;
類型這些指針是「指針數組[3] short
'或short (*A)[3]
。
還可以寫爲:
&A = 0x4000;
類型的,這是 '指針數組[3] [3]的short
',或short (*A)[3][3]
。
其中一個主要的差異是在對象的大小,因爲這代碼演示:
#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. build 5658)(LLVM)上編譯爲64位模式時的輸出建立2335.15.00)'),是:
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]
的地址與A[0]
和A
的地址是如何相同的,但指向的對象的大小是不同的。 &A[0][0]
指向單個(短)整數; &A[0]
指向3個(短)整數的數組; &A
指向一個3x3(短)整數數組。
現在我們需要看看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);
}
這是一個兩半的程序。一半解剖陣列a
;另一個解剖雙指針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位地址。
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)
這樣可以防止關於未使用的變量的警告,還可以識別7個3個整數數組的地址。
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
的大小現在是24個字節,而不是18個,因爲它是3個(64位)指針的數組。 &a[n]
的大小是8個字節,因爲每個都是一個指針。將數據加載到數組位置的方法也大不相同 - 您必須查看彙編器才能看到該數據,因爲C源代碼看起來相同。
在2D陣列碼,爲A[i][j]
加載操作計算:的A
增加(3 * i + j) * sizeof(short)
在指針代碼的陣列,爲A[i][j]
加載操作計算:
- 的
a
- 字節地址添加
i * sizeof(short *)
到字節地址
- 從該計算出的值取字節地址,稱它爲
b
- 增加
j * sizeof(short)
增加到b
- 取出從地址
b
2字節整數爲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
所有這些都在一個單獨的(主要)函數中。當將各種指針傳遞給函數並訪問這些指針後面的數組時,您需要對自己進行並行實驗。
當你使用'int ** b'時會發生什麼?一些編譯器錯誤? – user1025189 2012-02-12 11:14:44
是錯誤:無法將int [3] *轉換爲int ** – user1171901 2012-02-12 11:34:55
請閱讀[comp.lang.c FAQ](http://c-faq.com)的第6節。 – 2012-02-12 11:51:44