printf("%s", NULL) 和 printf("%s ", NULL) 的區別?

printf("%s
", (char*)NULL)

printf("%s", (char*)NULL);

fflush(stdout);

有什麼區別?

為什麼前者會導致Segmentation fault

後者列印(null)


因為:

  1. glibc 的 printf() 考慮了 %s 的參數是 NULL 的情況。
  2. gcc 編譯器會把 printf("%s
    ", x) 改寫為 puts(x),因為後者不需要 parse format string。
  3. 但是 puts() 沒有考慮參數是 NULL 的情況。

因此第一種寫法會 coredump,第二種不會。


這是一類月經問題。

無論 C / C++,都要求 printf 的 %s 對應一個有效的指向字元數組的指針。如果提供的參數與要求不服,則對應的行為未定義。

If any argument is not the correct type for the corresponding conversion specification, the behavior is undefined.

所以 printf 考慮了 %s 的參數是 NULL 的情況嗎?沒有,標準只規定其行為未定義。

無論格式化字元串寫成什麼樣子,只要你為其中的 %s 對應一個 NULL,列印「(null)」、列印「你這個笨蛋」、段錯誤、刪除磁碟上所有文件、幫你叫個外賣……都是合理合法的 C / C++ 程序的行為。

所以總結回答一下問題:由於是未定義行為,它們可能有區別,也可能沒有區別。

---

月經回答:通過彙編來解釋 C / C++ 的行為,和盲人摸象沒有區別。


更新:的確,彙編並不是有效的的方法,只能看到部分編譯器的現象,要真正理解c的行為還是要啃標準。

~~~~~~~~~最騷的分割線~~~~~~~~~~

剛剛在知乎找到這個問題,然後發表了自己的結果,然後就在stackoverflow找到了答案,不得不說stackoverflow真是很厲害,至少每一個程序員都應該懂得利用這個網站(還有Google[順便吐槽一下百度搜索出來都是什麼鬼無聊東西,還是趁早退百度保平安])

首先,我們分別編譯兩個文件

#include &
int main(void){
printf("%s", (char *)NULL);
}

#include &
int main(void){
printf("%s
", (char *)NULL);
}

通過附加-save-temps選項來查看彙編代碼

這是"%s"的

這是"%s
"的

可以發現,

"%s"這個是調用了printf函數進行處理,就像 @陳碩 所說的printf把這個當成了一種情況來處理,識別到這是一個NULL指針,

而"%s
"這個則是直接調用了puts函數來進行輸出,對於NULL指針並沒有作判斷處理,所以才會引發Segmentation fault。

而這個又正好解決了我的疑問,為什麼再"%s
"在任何位置加一點東西又不會引發Segmentation fault, 原因在於這次put又不足以解析這一段字元串,把他交給了printf來處理

以下附答案連接

What is the behavior of printing NULL with printf"s %s specifier?

printf("%s
",str); gives segmentation fault but printf("%s",str); don"t, where "str" is a string pointer

---原答案----

我也遇到這個問題

printf("%s1
", (char *)NULL);
printf("1%s
", (char *)NULL);
printf("%s
1", (char *)NULL);
printf("%s", (char *)NULL);
printf("%s
1", (char *)NULL);
printf("%s ", (char *)NULL);
printf("%s
", (char *)NULL);

這麼多,就是最後一個會Segmentation fault

但是只要稍微改一下就不會出現,只有這個特定的格式才會出現,這是什麼BUG


首先拋出vs2015:

沒有段錯誤,代碼如下:

加入"x00"進行對照,如果真的變為puts,"x00"由於是正經的字元串不會崩潰,NULL則會讓puts發生段錯誤,然後idapro反彙編結果如下:

調用了四次printf,可見在VS中二者寫法並沒有區別;

gcc結果如下,先貼代碼:

其中如果加入最後一個printf,會崩潰的,注釋掉最後一個printf編譯成功得到的二進位彙編如下:

它優化的是%s
,而%s反而沒有優化。

對於最後一個printf,會被優化為puts(NULL),顯然不合法,發生段錯誤

然而printf(NULL)是合法的,輸出為(NULL)

如上


vs2015測試,沒有錯誤。


推薦閱讀:

現代編程語言需要具備什麼要素?
int 和 long int 的區別在哪裡?
VR需要掌握什麼編程語言?
有哪些比較好的關於編譯原理 ,操作系統的網路公開課?

TAG:編程語言 | C編程語言 | C | CC |