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

《12 個有趣的 C 語言問答》評析(三):未定義行為與標準庫陷阱

     編輯:關於C語言

本系列第三篇,聚焦 C 標準中最隱蔽也最危險的未定義行為(UB)、標準庫函數誤用、指針運算邊界等深水區問題。原文不僅示範錯誤代碼,連解釋都包含技術謬誤。

第 6 題:atexit 與 _exit 的交互

原文問題代碼

void func(void) { printf("Cleanup\n"); }
int main(void) {
    atexit(func);
    for (int i = 0; i < 0xffffff; i++);  // 忙等
    _exit(0);  // 直接終止,不調用 atexit 註冊函數!
}

關鍵區別

正確模式

// 正常退出
int main(void) {
    atexit(cleanup);
    // ...
    return 0;  // 或 exit(0)
}

// 異常立即終止(如 fork 後子進程)
if (pid == 0) {
    _exit(0);  // 不執行父進程的 atexit,不衝刷父進程緩衝區
}

第 7 題:memset 參數類型陷阱

原文片段

int func(void *ptr)  // 返回 int,實際需 void*
void *memset(void *s, int c, size_t n);

標準簽名解析

void *memset(void *s, int c, size_t n);
// s: 目標內存起始地址
// c: 填充字節值(取低 8 位,即 unsigned char)
// n: 填充字節數
// 返回: s(便於鏈式調用)

常見錯誤

  • memset(ptr, 0xFF, n) → 實際填充 0xFF(正確)
  • memset(ptr, 0x100, n) → 低 8 位為 0,填充 0x00(陷阱!)
  • memset(arr, 1, n) 整型數組 → 每字節置 1,非每元素置 1

正確用法模式

char *ptr = "Linux";
printf("%c\n", *ptr++);  // 輸出 L
printf("%c\n", *ptr);    // 輸出 i

運算符優先級解析

  • ++(後置)優先級高於 *(解引用)
  • *ptr++ 等價於 *(ptr++)
  • 步驟:① 取當前 ptr 值 ② 解引用得 'L' ③ ptr 指向下一字符

對比:前置遞增

char *ptr = "Linux";
printf("%c\n", *++ptr);  // ① ptr+1 ② 解引用得 'i'
printf("%c\n", ++*ptr);  // ① 解引用得 'i' ② 字符值+1 得 'j' ③ 寫回內存(常量段崩潰!)

第 9 題:字符串字面量修改導致崩潰

原文代碼

char *ptr = "Linux";
*ptr = 'T';  // 未定義行為:修改只讀內存段
printf("%s\n", ptr);

內存佈局解析

// 字符串字面量存儲在 .rodata 段(只讀)
// ptr 指向 .rodata 區域
// *ptr = 'T' 觸發 SIGSEGV / SIGBUS

// 正確:字符數組在棧上,可修改
char arr[] = "Linux";
arr[0] = 'T';  // 合法
printf("%s\n", arr);  // "Tinux"

第 10 題:修改 argv[0] 更改進程名

原文代碼

char buff[100];
memset(buff, 0, sizeof(buff));
strncpy(buff, argv[0], sizeof(buff));
memset(argv[0], 0, strlen(buff));  // 清空原名
strncpy(argv[0], "NewName", 7);    // 寫入新名

多重缺陷

  1. strlen(buff) 在 memset 後為 0,實際未清空
  2. argv[0] 指向的內存大小未知,寫入可能越界
  3. 修改 argv 字符串行為實現定義,不可移植
  4. 忽略 strncpy 不保證 \0 結尾

跨平台進程名設置


核心教訓總結

函數調用 atexit 註冊函數刷新標準 I/O 緩衝區調用對象析構函數
exit()✅ 是✅ 是✅ C++ 是
_exit() / _Exit()❌ 否❌ 否❌ 否
return (main)✅ 是✅ 是✅ C++ 是
abort()❌ 否❌ 否❌ 否

C 語言的「坑」大多源於對標準規範的不熟悉。讀懂標準、理解實現、驗證假設,是從「能寫出跑的代碼」到「寫出正確代碼」的必經之路。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
主題原文錯誤正確認知
程序退出用 _exit 代替 exit正常退出用 exit/return;_exit 僅用於特殊場景
memset 填充值忽略 int 截斷為字節填充值取低 8 位;整型數組慎用非 0/-1 值
指針遞增混淆 *ptr++ 與 *++ptr後置++:先解引用再遞增指針
字符串字面量嘗試修改只讀內存;需修改請用字符數組
argv 修改不檢查長度、邏輯錯誤不可移植;Linux 用 prctl,Windows 用 API