首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

《C 程式設計語言》(K&R)閱讀筆記(二):指標、記憶體對齊與結構體

     編輯:關於C語言

前言

承接上篇,本文聚焦指標運算、多維陣列解析、大小端、結構體對齊規則,並補充現代編譯器行為與最佳化技巧。

一、指標運算核心法則

1.1 運算子優先序與結合性

++*p;   /* (*p)++  內容加 1 */
(*p)++;  /* 同上,明確括號 */
*p++;    /* *(p++)  指標遞增,取原位址內容 */
*++p;    /* *(++p)  指標先遞增,取新位址內容 */

口訣:「後綴 ++ 綁定最緊,前綴 *、++ 從右向左結合」。

1.2 陣列名與指標的等價與差異

  • a[i] 定義等價於 *(a + i);pa[i] 等價 *(pa + i)。
  • int a[10]; int *p = a; → p 指向 a[0]。
  • 關鍵差異:a 為常量指標(非左值,不可修改),p 為變數指標。
  • sizeof(a) = 整個陣列大小;sizeof(p) = 指標大小。

1.3 負下標合法性

若 int *p = &arr[5];,則 p[-1] 等價 arr[4],語法合法、行為定義良好,前提是存取範圍在陣列邊界內。

1.4 字串複製 strcpy 正確寫法

char *strcpy(char *dst, const char *src) {
    char *ret = dst;
    while ((*dst++ = *src++) != '\0');
    return ret;          /* 傳回目的地址,支援鏈式呼叫 */
}

原書範例缺少暫存回傳值,導致呼叫端無法取得目的指標。

1.5 二維陣列參數傳遞三種等價宣告

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 ** 無法直接接收二維陣列(記憶體佈局不同)。

1.6 指標陣列 vs 陣列指標

int *p[13];      /* 13 個 int* 的陣列 */
int (*p)[13];    /* 1 個指向 int[13] 的指標 */

解讀口訣:「從變數名出發,遇 [] 說『陣列』,遇 * 說『指向』,優先級 () > [] > *」。

1.7 函式指標進階

/* 宣告:指向「接受兩 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; }

1.8 複雜宣告解析系統法

範例:char (*(*x[3])())[5]

  1. 從 x 開始:x 是陣列 [3]
  2. 陣列元素:*x[3] → 指標
  3. 指標指向:() → 函式
  4. 函式回傳:* → 指標
  5. 指標指向:[5] → 陣列
  6. 陣列元素:char

結論:x 是「含 3 個元素的陣列,每個元素是指標,指向『回傳「含 5 個 char 陣列的指標』的函式』」。

二、記憶體佈局與大小端

2.1 陣列連續佈局

int a[10]; 在記憶體中為 10 個 int 緊密排列,&a[i+1] - &a[i] == sizeof(int)。指標遞增 p+1 自動跨越 sizeof(*p) 位元組。

2.2 大小端判斷與轉換

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 為標準轉換函式。

三、結構體對齊與位欄

3.1 對齊三原則

  1. 結構體起始位址為其最嚴格對齊需求的倍數。
  2. 成員依宣告順序排列,每個成員起始位址為自身對齊需求的倍數(必要時插入填充位元組)。
  3. 結構體總大小為最嚴格對齊需求的倍數(尾端填充)。

3.2 巢狀結構體對齊實測

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 的倍數。

3.3 位欄

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 語言的靈魂,記憶體佈局是效能的基石。熟練掌握「宣告解析法、對齊推算、大小端轉換」,能讓你在系統程式、嵌入式、高效能計算中遊刃有餘。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved