字串在C語言中並非基本型別,而是以以空字元'\0'結尾的字元陣列形式存在。這一看似簡單的設計,卻孕育了C語言靈活高效也危險的特性。本文從記憶體佈局、建立方式、參數傳遞、修改限制、長度計算五個維度,系統梳理字串核心知識與常見陷阱。
#include <stdio.h>
int main(void) {
// "H" 是字串字面量,佔 2 字節:'H' + '\0'
// 'H' 是字元常數,佔 1 字節(實際為 int 型別,通常 4 字節)
printf("sizeof(\"c\") = %zu, sizeof('s') = %zu\n", sizeof("c"), sizeof('s'));
// 輸出: sizeof("c") = 2, sizeof('s') = 4 (64位系統 int 為 4 字節)
return 0;
}
結論: 雙引號建立的是字串字面量(含結束符),單引號建立的是字元常數(整型值)。
char *p = "hello world!";
這行代碼背後發生了什麼?
"hello world!\0"。"hello world!" 的值是該字串首字元地址(即字元 'h' 的地址)。p 指向這個地址。驗證:
printf("%s\n", p); // 輸出整個字串
printf("%c\n", *("hello")); // 輸出 'h',字串字面量直接解引用獲取首字元
char *p = "hello world!\n";
p[0] = 'H'; // 編譯可能通過,運行時段錯誤!
printf("%c\n", p[0]);
原因: p 指向唯讀資料段,硬體記憶體保護機制禁止寫入。
正確姿勢: 使用字元陣列,內容會被複製到棧區(可讀寫)。
char arr[] = "hello world!\n"; // 棧上分配,內容可修改
arr[0] = 'H'; // 合法
printf("%s", arr); // 輸出 "Hello world!"
void func(char *str) { /* ... */ }
func("hello"); // 字串字面量地址傳給 str
格式化字串漏洞示例:
char *p = "hello word! %d\n";
printf(p, 5); // 危險!若 p 來自用戶輸入,可導致格式化字串攻擊
// 安全寫法:printf("%s", p); 或 printf("hello word! %d\n", 5);
strlen vs sizeof#include <string.h>
int main(void) {
char *p = "hello";
char a[] = "hello";
printf("strlen(p) = %zu\n", strlen(p)); // 5,按 '\0' 計算有效長度
printf("strlen(a) = %zu\n", strlen(a)); // 5
printf("sizeof(p) = %zu\n", sizeof(p)); // 8 (64位指標大小)
printf("sizeof(a) = %zu\n", sizeof(a)); // 6 (含 '\0' 的陣列總大小)
return 0;
}
核心區別:
strlen:運行時遍歷計數,不包含結束符,僅適用於以'\0'結尾的字串。sizeof:編譯期計算型別佔用字節數。指標型別大小固定(4/8字節),陣列型別為總大小(含結束符)。#include <unistd.h> // write
void func(void) {
int *p = "hello!"; // 強制轉型,掩蓋編輯器警告
write(1, p, 10); // 寫入 10 字節,超出字串長度,輸出後續記憶體垃圾值
}
int main(void) {
int *q = "hello world";
func();
return 0;
}
教訓: C語言不做邊界檢查,越界言問導致未定義行為(輸出亂碼、崩潰、安全漏洞)。
strncpy、snprintf等帶長度限制的安全函式。strlen(邏輯長度)與sizeof(佔用空間)的本質區別。掌握字串在記憶體中的真實面貌,是寫出健壯、安全C代碼的基石。