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

C/C++ 易忽略的關鍵細節:指標、字串、靜態、求值順序

     編輯:關於C語言

1. 字串字面值 vs 字元陣列:輸出行為大不同


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"。

2. 指標變數大小固定 (平台相關)


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

指標儲存的是**位址**,大小僅取決於架構位寬,**與指向類型無關**。這是指標能泛型操作記憶體的基礎。

3. static 區域變數:只初始化一次,生命週期全程


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(); 初始化運算式**只在第一次呼叫時執行**。若依賴外部狀態初始化,後續呼叫不會重新計算。

4. printf 參數求值順序:未指定 (通常右向左)


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

5. 字串拷貝慣用語:指標版 vs 索引版


// 經典指標版 (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++;
}

指標版精簡但需熟練掌握「賦值表達式值為賦值結果」與「後置遞增優先級」。

6. strlen() 陷阱:只數可見字元,不含結束符


char s[] = "abc";
printf("%zu %zu\n", strlen(s), sizeof(s)); // 3 4
// 分配緩衝區需 +1: malloc(strlen(src) + 1)

7. 迴圈次數未知時的指標遍歷模式


// 字串遍歷
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。

8. 結構體對齊與 sizeof 陷阱


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) 查看對齊要求。

9. 位欄位不可取址、跨平台佈局未定義


struct { unsigned int a:1, b:3, c:4; } bf;
// &bf.a 錯誤:位欄位無位址
// 位元排序 (高位在前/低位在前) 由編譯器決定,不可用於網路/檔案格式

10. 避坑清單速查

現象原因修正
修改字串字面值崩潰指向唯讀區用陣列 char s[] = "...
sizeof指標以為是陣列大小指標退化傳遞長度參數或用宏 ARRAY_SIZE(arr)
static 變數初始化邏輯不重跑只初始化一次改用函數內靜態標記 + 手動初始化
printf 多副作用參數結果隨機求值順序未定義拆分語句,單一序列點單一修改
strcpy 目的緩衝區太小不檢查長度用 strncpy/snprintf 或動態分配
結構體大小不等於成員大小和對齊填充重排成員、用 pragma pack 謹慎壓縮
位欄位跨平台序列化不一致佈局未定義手動位運算封裝 get/set 函數

總結

C/C++ 看似簡單的語法細節 (指標、static、求值順序、字串、結構體佈局) 暗藏大量未定義行為與可移植性陷阱。**理解底層機制 (記憶體模型、編譯器行為、標準規定)**,配合良好習慣 (拆分複雜表達式、const 正確性、邊界檢查、靜態分析工具),才能寫出健壯、可移植的系統級代碼。

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