memset、memcpy 等內存操作函數是 C 程序員的「家常便飯」,但其參數類型 size_t(無符號)與指針算術的微妙交互,極易導致驚人的邊界錯誤。本文深度剖析三大經典陷阱。
int a[100]; int *b = a; sizeof(a) // 400 (數組大小) sizeof(b) // 4/8 (指針大小),非數組大小!
結論:sizeof 是編譯期運算符,僅對類型或數組表達式返回對象大小;對指針變量返回指針本身大小。函數參數中的數組退化為指針,sizeof 失效。
int a[100]; memset(a, 0, 100); // 錯誤!僅清零前 100 字節(25 個 int) memset(a, 0, sizeof(a)); // 正確:400 字節 memset(a, 0, 100 * sizeof(int)); // 正確
原因:void *memset(void *s, int c, size_t n) 的 n 是字節數。傳入元素個數會導致部分內存未初始化。
面試必考:手寫 memcpy。
void *my_memcpy(void *dest, const void *src, size_t n) {
if (!dest || !src) return NULL; // 空指針防護
// 負長度:size_t 無符號,傳入 -1 會變巨大正數,調用者責任保證 n 合法
// 重疊:標準 memcpy 不保證重疊正確,需用 memmove
unsigned char *d = (unsigned char*)dest;
const unsigned char *s = (const unsigned char*)src;
while (n--) *d++ = *s++;
return dest;
}
關鍵點:
size_t 無符號,n-- 循環條件 while (n--) 正確處理 0。memmove(反向拷貝或臨時緩衝區)。struct S { char c; int i; }; // 可能佈局:c + 3字節填充 + i
struct S s;
memset(&s, 0, sizeof(s)); // 看似正確,但...
// 若結構體含浮點數,全 0 位模式不一定是 0.0(雖 IEEE 754 是)
// 若含指針,全 0 不一定是 NULL(極少數架構)
// 更安全:s = (struct S){0}; // C99 指定初始化器
memset/memcpy/memmove 第三參數**永遠用 sizeof(對象) 或 元素數 * sizeof(類型)**,別寫死數字。sizeof 獲取數組長度**,需額外傳長度參數。memmove**。(Type){0} 或指定初始化器,避免填充字節/浮點數/指針表示陷阱。-Wsizeof-pointer-memaccess (GCC/Clang) 自動檢測 sizeof(指針) 誤用。