snprintf 和 strncpy 的細節區別

語言: CN / TW / HK

現在一般不能用 sprintf 和 strcpy ,推薦使用 snprintf 和 strncpy ,以防止緩衝區溢位:

char buffer[32];
snprintf(buffer, sizeof(buffer), "xxxxxxxxx");
strncpy(buffer, "xxxxxxx", sizeof(buffer));

但 snprintf 和 strncpy 有一些很細節的區別,一不注意就會出錯:

  • 如果預複製(或列印)的字串長度小於緩衝區長度(上面例子裡的 32 ),那麼 snprintf 會在末尾新增一個 0 ,而 strncpy 會在剩餘的空間都填上 0。
  • 如果預複製(或列印)的字串長度大於等於緩衝區長度(上面例子裡的 32 ),那麼 snprintf 會複製 31 個字元,然後填上一個 0 ,此時 buffer 是一個正常的 C 字串,但比源字串字元數要少。但 strncpy 會複製剛好 32 個字元,不會新增 0。此時 buffer 不是一個正常的 C 字串,可能引起緩衝區溢位。最新的編譯器會給出警告。
  • snprintf 返回列印的字元數(不包含尾部的 0 ), strncpy 返回 buffer (貌似多此一舉)。

從防範風險的角度看, snprintf 更安全,但 snprintf 要慢很多。實際工作中需要取捨。

從 GCC9 開始,編譯器會給 strncpy 給出一個 -Wstringop-truncation 的編譯警告,提示可能沒有 0 結束字元問題。

從實際工作角度,可以構造一個 sprintf 和 strncpy 的安全結合版:

inline char * safe_strncpy(char* dest, const char* src, size_t n)
{
    assert(n > 0);
    strncpy(dest, src, n - 1);
    dest[n - 1] = 0;

    return dest;
}

最後一點,如果確信源字串長度不小於快取區長度,可以直接用 memset ,效率更高(但注意最後沒有新增 0 ):

char buffer[8];
memcpy(buffer, "xxxxxxxxx", sizeof(buffer));

Q. E. D.