printf函數的 %s 與 %c

今天在研究C++的時候在Mac上隨手寫了些例子,結果發現一個關於printf很有趣的現象:

先定義一個模板:

template<tyepname type>class data_count{
    type a;
    type b;
public:
    data_count(type A,type B):a(A),b(B){}
    type add(){return a+b;}
    type sub(){return a-b;}
}


使用一下這模板:

data_count<char> c('a','b');
printf("check it %c",c.add());

一切正常,於是我突發奇想改了一下printf語句:

printf("check it %s",c.add());

系統直接報了 EXC_BAD_ACCESS,非法訪問內存?

我不確定是否因爲模板類的問題,所以再試了一下:

printf("check it %s",'c');

此時clang報出警告: Format specifies type 'char*' but the argument has type 'char'     (clang的錯誤信息簡直比gcc好太多了,特別在出現鏈接錯誤的時候)

我沒管,直接跑起來,結果運行時報出 EXC_BAD_ACCESS的錯誤,果然是因爲printf中%s與%c的不同造成的。

去扒代碼:

static int printf(const char *fmt, ...)
 	{
 	    va_list args;
 	    int i;
 	 
 	    va_start(args, fmt);
	    write(1,printbuf,i=vsprintf(printbuf, fmt, args));
 	    va_end(args);
	    return i;
 	}
這是Linux源碼中實現的printf(沒找Mac下的,因爲這種代碼原理應該都是一樣的),思路很簡單:將變參(va_list)中的所有參數組裝好,全部寫到1(stdout)的FD中從而輸出到屏幕。
這裏貌似只有vsprintf會訪問內存導致BAD_ACCESS了,vasprintf函數的代碼很長,其中訪問內存的地方有不少,結合報錯的彙編語句:

0x7ff8b9a8732   pxor %xmm0,%xmm0 //將xmm0寄存器清零

0x7ff8b9a8732   pcmpeqb  (%rdi),%xmm0 //將rdi寄存器指向的內存的內容與 xmm0 作比較

來看,應該是某個與0比較的操作導致的,而且顯示是在 strlen()函數中的出現的。

由此可以推斷出,應該是vsprintf中的這一段:

case 's':
 	            s = va_arg(args, char *);
 	            len = strlen(s);
	            if (precision < 0)
 	                precision = len;
 	            else if (len > precision)
 	                len = precision;
導致的,vsprintf查看到格式符有 %s 後,就找對應的參數,直接對其進行調用 strlen 查看其長度去了。
根據我的分析,情況應該是這樣的:

printf中遇到%s後,就把對應位置的參數當做是char* 指針對待,對其調用strlen查看其長度,然後當這個參數只是char的時候,就會把字符內容當做指針地址,而一個char的取值在0-255之間,屬於用戶空間不能訪問的內存地址,所以系統就直接報錯了。

其實這些細微之處還挺有趣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章