核心區別一覽
| 特性 | sizeof | strlen |
| 性質 | 運算符(編譯期計算) | 庫函數(運行期計算) |
| 參數 | 類型或表達式(不求值) | const char*(必須以 \0 結尾) |
| 返回 | 類型/對象佔用位元組數(含結構填充) | 字串有效字符數(不含 \0) |
| 陣列退化 | 不退化(保留完整類型) | 退化為指標 |
| 動態記憶體 | 只能得指標大小 | 正常計算字串長度 |
經典案例對照表
char str1[] = "abc"; // char[4] {'a','b','c','\0'}
char str2[] = {'a','b','c'}; // char[3] 無 \0
char str3[5] = "abc"; // char[5] {'a','b','c','\0','\0'}
char *p = "abcde"; // char* → 唯讀區 "abcde\0"
sizeof(str1) = 4; strlen(str1) = 3
sizeof(str2) = 3; strlen(str2) = 隨機值(無 \0,UB)
sizeof(str3) = 5; strlen(str3) = 3
sizeof(p) = 8; strlen(p) = 5 // 64位系統指標8字節
深度原理解析
1. sizeof 編譯期計算
- 對陣列:
sizeof(arr) = 元素數 × sizeof(元素類型)。
- 對指標:
sizeof(ptr) = 指標本身大小(4/8 字節)。
- 表達式不求值:
int a=1; sizeof(a=2); → sizeof(int),a 仍為 1。
- 函數調用:
sizeof(f()) → sizeof(返回類型),f() 不會真調用。
2. strlen 運行期掃描
- 從指標開始逐字節檢查直到遇到
\0。
- 若陣列無
\0(如 char a[3]={'a','b','c'};),讀越界 → 未定義行為。
- 只接受
char*,不能直接傳 int* 或結構體。
3. 字串字面量的特殊性
char *p = "hello"; // p 指向 .rodata 區,不可修改
char a[] = "hello"; // 編譯器把字面量複製到棧陣列 a,可修改
// 等價於
const char *p = "hello";
char a[] = {'h','e','l','l','o','\0'};
實戰陷阱
- 函數參數退化:
void f(char s[100]) 實為 char* s,函數內 sizeof(s) 只得指標大小。
- 結構體填充:
struct {char c; int i;} → sizeof=8(非 5),對齊導致。
- 多維陣列:
int a[3][4]; sizeof(a)=48,但傳參退化成 int(*)[4]。
最佳實踐
- 需要「容器大小」用
sizeof;需要「字串長度」用 strlen。
- 自定義陣列長度宏:
#define ARR_LEN(arr) (sizeof(arr)/sizeof((arr)[0])) —— 僅適用於陣列變數,不可用於指標。
- 動態字串結構體:
struct Str { size_t len; char *data; } 避免重複 strlen。