char *ptr = "Linux";
*ptr = 'T'; // 段錯誤!"Linux" 存放在唯讀段
printf("%s", ptr);
正確姿勢:
char ptr[] = "Linux"; // 分配在棧上,可修改
ptr[0] = 'T'; // OK,輸出 "Tinux"
核心區別:char *ptr = "..." 指向唯讀區;char ptr[] = "..." 複製到可寫內存。
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。
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'。
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 且無序列點
|| && ?: ,(逗號運算符)、語句結尾 ;。i = i + 1 讀取舊值計算新值)。a[i++] = i:i++ 副作用與賦值無序列點 → UB。
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);
char s[] = "..."。argv 修改有風險 → 用系統 API 改進程名。strncpy 手動補 \0。-Wall -Wextra -Wsequence-point(GCC/Clang)捕獲大部分 UB。