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

C 語言經典面試陷阱解析:字串常量修改、argv 改名、副作用與序列點

     編輯:關於C語言

陷阱 1:修改字串常量導致崩潰


char *ptr = "Linux";
*ptr = 'T';  // 段錯誤!"Linux" 存放在唯讀段
printf("%s", ptr);

正確姿勢:


char ptr[] = "Linux";  // 分配在棧上,可修改
ptr[0] = 'T';          // OK,輸出 "Tinux"

核心區別:char *ptr = "..." 指向唯讀區;char ptr[] = "..." 複製到可寫內存。

陷阱 2:修改 argv[0] 偽裝進程名


int main(int argc, char *argv[]) {
    char buff[100];
    strncpy(buff, argv[0], sizeof(buff));  // 保存原名
    memset(argv[0], 0, strlen(argv[0]));   // 清零原內存
    strncpy(argv[0], "NewName", 7);        // 寫入新名
    for (;;);  // 此時 ps/top 顯示 NewName
}

原理:argv[0] 指向內核分配的命令行參數緩衝區(可寫),長度固定。memset 長度必須用 strlen(argv[0]) 而非 strlen(buff),否則可能越界或清零不全。

副作用:argv[1]... 緊隨其後,過長新名會覆蓋 argv[1]。生產環境請用 prctl(PR_SET_NAME) 或 setproctitle。

陷阱 3:strncpy 不自動補 \0


char s[4];
strncpy(s, "1", 4);  // s = {'1', '\0', '\0', '\0'}
printf("%d %d %d %d", s[0], s[1], s[2], s[3]);  // 49 0 0 0

若源串長度 ≥ 目的長度,不會寫入 \0,必須手動 s[n-1] = '\0'。

陷阱 4:副作用與序列點——未定義行為大全


a = (++a) + (++a) + (++a) + (++a);  // UB:同一序列點多次修改 a
printf("%d,%d,%d,%d", ++i, --i, i++, i--);  // UB:參數求值順序未規定
i = i + 1;  // OK:單一修改,有序列點
j = i + (i = 2);  // UB:同一表達式讀寫 i 且無序列點

關鍵規則(C99/C11)

  1. 序列點:函數調用返回、|| && ?: ,(逗號運算符)、語句結尾 ;。
  2. 兩個序列點之間:對象至多被修改一次;若被讀取,必須為確定修改後的值(如 i = i + 1 讀取舊值計算新值)。
  3. a[i++] = i:i++ 副作用與賦值無序列點 → UB。

陷阱 5:函數參數求值順序


int a=10,b=20,c=30;
printf("%d..%d..%d", a+b+c, (b=b*2), (c=c*2));
// 輸出不確定!參數求值順序未規定(右到左、左到右、亂序均合法)

鐵律:不要在函數參數中進行有副作用的操作。拆分為多條語句:


b *= 2; c *= 2;
printf("%d..%d..%d", a+b+c, b, c);

總結:避坑清單

  1. 字串常量不可寫 → 用數組 char s[] = "..."。
  2. argv 修改有風險 → 用系統 API 改進程名。
  3. strncpy 手動補 \0。
  4. 同一表達式/參數列表中,變量至多修改一次,讀寫須有序列點隔離。
  5. 函數參數不寫副作用代碼。
  6. 開啟編譯器警告 -Wall -Wextra -Wsequence-point(GCC/Clang)捕獲大部分 UB。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved