char s1[] = "hello"; // 陣列在棧上,內容可修改
char *s2 = "hello"; // 指向唯讀區字面值,不可修改
char *s3 = s1; // 指向 s1
printf("%s %s %s\n", s1, s2, s3); // 皆輸出 "hello"
// 但 sizeof(s1)=6, sizeof(s2)=8(指標大小), sizeof(s3)=8
關鍵:char *p = "lit" 指向常量區,**嘗試修改 *p = 'H' 導致未定義行為 (通常段錯)**。需修改內容請用陣列 char s[] = "lit"。
int *p; char *q; double *r; struct Big *s;
printf("%zu %zu %zu %zu\n", sizeof(p), sizeof(q), sizeof(r), sizeof(s));
// 64位系統全輸出 8,32位全輸出 4
指標儲存的是**位址**,大小僅取決於架構位寬,**與指向類型無關**。這是指標能泛型操作記憶體的基礎。
int counter() {
static int cnt = 0; // 程式啟動時初始化為 0,僅執行一次
return ++cnt;
}
int main() {
printf("%d %d %d\n", counter(), counter(), counter()); // 1 2 3
}
陷阱:static int cnt = get_initial(); 初始化運算式**只在第一次呼叫時執行**。若依賴外部狀態初始化,後續呼叫不會重新計算。
int i = 1;
printf("%d %d %d\n", i++, ++i, i); // 輸出結果編譯器相關!
// GCC x86 常見輸出:3 3 1 (右向左求值)
// 但標準規定:未定義行為 (同一序列點多次修改 i)
鐵律:**單一語句中不可對同一變數進行多次副作用操作**。拆分為多行:
int a = i++; int b = ++i; int c = i;
printf("%d %d %d\n", a, b, c); // 明確、可移植、無 UB
// 經典指標版 (K&R 風格)
void strcpy_ptr(char *dest, const char *src) {
while (*dest++ = *src++); // 賦值結果作為條件,\0 為 0 結束
}
// 索引版 (較易讀)
void strcpy_idx(char *dest, const char *src) {
int i = 0;
while ((dest[i] = src[i]) != '\0') i++;
}
指標版精簡但需熟練掌握「賦值表達式值為賦值結果」與「後置遞增優先級」。
char s[] = "abc";
printf("%zu %zu\n", strlen(s), sizeof(s)); // 3 4
// 分配緩衝區需 +1: malloc(strlen(src) + 1)
// 字串遍歷
for (char *p = str; *p; p++) putchar(*p);
// 或
while (*p) { ...; p++; }
// 陣列遍歷 (已知長度 n)
for (int i = 0; i < n; i++) ...
// 或指標版
for (int *p = arr, *end = arr + n; p < end; p++) ...
終止條件:字串靠 *p == '\0',陣列靠計數或 p < end。
struct A { char c; int i; }; // 大小通常 8 (c+3填充+i)
struct B { int i; char c; }; // 大小通常 8 (i+c+3填充)
struct C { char c; char d; int i; }; // 通常 8
// 重新排列成員可減小結構體大小
用 offsetof (stddef.h) 查看成員偏移,_Alignof (C11) 查看對齊要求。
struct { unsigned int a:1, b:3, c:4; } bf;
// &bf.a 錯誤:位欄位無位址
// 位元排序 (高位在前/低位在前) 由編譯器決定,不可用於網路/檔案格式
| 現象 | 原因 | 修正 |
|---|---|---|
| 修改字串字面值崩潰 | 指向唯讀區 | 用陣列 char s[] = "... |
| sizeof指標以為是陣列大小 | 指標退化 | 傳遞長度參數或用宏 ARRAY_SIZE(arr) |
| static 變數初始化邏輯不重跑 | 只初始化一次 | 改用函數內靜態標記 + 手動初始化 |
| printf 多副作用參數結果隨機 | 求值順序未定義 | 拆分語句,單一序列點單一修改 |
| strcpy 目的緩衝區太小 | 不檢查長度 | 用 strncpy/snprintf 或動態分配 |
| 結構體大小不等於成員大小和 | 對齊填充 | 重排成員、用 pragma pack 謹慎壓縮 |
| 位欄位跨平台序列化不一致 | 佈局未定義 | 手動位運算封裝 get/set 函數 |
C/C++ 看似簡單的語法細節 (指標、static、求值順序、字串、結構體佈局) 暗藏大量未定義行為與可移植性陷阱。**理解底層機制 (記憶體模型、編譯器行為、標準規定)**,配合良好習慣 (拆分複雜表達式、const 正確性、邊界檢查、靜態分析工具),才能寫出健壯、可移植的系統級代碼。