今天在研究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之間,屬於用戶空間不能訪問的內存地址,所以系統就直接報錯了。
其實這些細微之處還挺有趣的。