承接上篇,本文聚焦指標運算、多維陣列解析、大小端、結構體對齊規則,並補充現代編譯器行為與最佳化技巧。
++*p; /* (*p)++ 內容加 1 */ (*p)++; /* 同上,明確括號 */ *p++; /* *(p++) 指標遞增,取原位址內容 */ *++p; /* *(++p) 指標先遞增,取新位址內容 */
口訣:「後綴 ++ 綁定最緊,前綴 *、++ 從右向左結合」。
a[i] 定義等價於 *(a + i);pa[i] 等價 *(pa + i)。int a[10]; int *p = a; → p 指向 a[0]。a 為常量指標(非左值,不可修改),p 為變數指標。sizeof(a) = 整個陣列大小;sizeof(p) = 指標大小。若 int *p = &arr[5];,則 p[-1] 等價 arr[4],語法合法、行為定義良好,前提是存取範圍在陣列邊界內。
strcpy 正確寫法char *strcpy(char *dst, const char *src) {
char *ret = dst;
while ((*dst++ = *src++) != '\0');
return ret; /* 傳回目的地址,支援鏈式呼叫 */
}
原書範例缺少暫存回傳值,導致呼叫端無法取得目的指標。
void f(int a[2][13]); void f(int a[][13]); /* 第一維退化,第二維保留 */ void f(int (*a)[13]); /* 指向「含 13 個 int 的陣列」的指標 */
呼叫端傳 int arr[2][13]; f(arr); 皆合法。注意:int ** 無法直接接收二維陣列(記憶體佈局不同)。
int *p[13]; /* 13 個 int* 的陣列 */ int (*p)[13]; /* 1 個指向 int[13] 的指標 */
解讀口訣:「從變數名出發,遇 [] 說『陣列』,遇 * 說『指向』,優先級 () > [] > *」。
/* 宣告:指向「接受兩 int、回傳 int」函式的指標 */
int (*cmp)(int, int);
/* typedef 簡化 */
typedef int (*cmp_fn)(int, int);
cmp_fn cmp = strcmp; /* 若簽名相容 */
/* 函式指標陣列 */
int (*ops[2])(int, int) = { add, sub };
/* 回傳函式指標的函式 */
int (*selector(int mode))(int, int) { return mode ? add : sub; }
範例:char (*(*x[3])())[5]
x 開始:x 是陣列 [3]*x[3] → 指標() → 函式* → 指標[5] → 陣列char結論:x 是「含 3 個元素的陣列,每個元素是指標,指向『回傳「含 5 個 char 陣列的指標』的函式』」。
int a[10]; 在記憶體中為 10 個 int 緊密排列,&a[i+1] - &a[i] == sizeof(int)。指標遞增 p+1 自動跨越 sizeof(*p) 位元組。
uint32_t x = 0x01020304;
uint8_t *p = (uint8_t *)&x;
bool is_little = (p[0] == 0x04); /* 低位在低址 = 小端 */
/* 32 位元整數位元組序交換 */
uint32_t bswap32(uint32_t v) {
return (v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | (v << 24);
}
網路傳輸統一用大端(Network Byte Order),htonl/ntohl 為標準轉換函式。
struct S1 { char c; int i; }; /* 大小 8:c(1) + pad(3) + i(4) */
struct S2 { char c; struct S1 s; }; /* 大小 12:c(1) + pad(3) + S1(8) */
struct S3 { struct S1 s; char c; }; /* 大小 12:S1(8) + c(1) + pad(3) */
關鍵發現:巢狀結構體 不 作為不可分割整體對齊,而是「展開」其內部成員依序排列,各自遵循對齊規則。S2 中 s 起始位址為 4 的倍數(int 對齊),而非 8 的倍數。
struct Flags {
unsigned is_keyword : 1;
unsigned is_extern : 1;
unsigned is_static : 1;
unsigned : 0; /* 強制下一位欄從新儲存單元開始 */
unsigned reserved : 29;
};
unsigned int 或 signed int,位序(LSB→MSB 或 MSB→LSB)由實作定義(GCC 為 LSB→MSB)。union U { float f; uint32_t u; };
union U u = { .f = 3.0f };
printf("0x%08X\n", u.u); /* 0x40400000,合法型別雙關(C99 起) */
C11 明確允許透過聯合體進行型別雙關,但需注意嚴格別名規則對非聯合體存取的限制。
指標是 C 語言的靈魂,記憶體佈局是效能的基石。熟練掌握「宣告解析法、對齊推算、大小端轉換」,能讓你在系統程式、嵌入式、高效能計算中遊刃有餘。