1. 程式人生 > >visual studio中使用printf列印迭代器內容時與cout的差別

visual studio中使用printf列印迭代器內容時與cout的差別

迭代器是c++中用於遍歷容器中的元素的資料型別,但是今天在使用vs2015進行除錯的時候,發現對於c++的容器的實現,vs 2015與vc++ 6.0有很大的差異,應該是使用vs進行編譯時的一個漏洞吧。

問題起源

關於下面的程式碼,在vs 2015和vc++ 6.0兩個平臺上的執行結果大不相同。

#include<string>
#include<iostream>

using namespace std;

void main() {
    string start = "ASD";
    string::iterator a = start.begin();
    printf
("%x,%x,%x\n", a,*a,*a); getchar(); }

在以往使用迭代器的時候,筆者總是使用cout進行輸出,但是今天由於一些格式上的原因使用了printf對內容進行輸出,但是輸出結果與預期差異過大。按照筆者分析,使用printf進行輸出,三個內容應該依次為 迭代器指向的地址,’A’的十六進位制,’A’的十六進位制。但是最後的輸出結果卻如圖所示:
vs輸出結果

vc++的輸出結果和預期的一樣,但是為何同樣的程式碼在兩個平臺上的輸出結果不一樣呢,這個問題引起了筆者的深思。

測試另一段程式碼

起初,我以為在c++中,由於容器的結構的原因,printf不能對容器這個物件進行解釋,所以不能用printf對容器進行輸出*a,因此,我嘗試瞭如下程式碼,測試能否使用printf輸出*a。

#include<string>
#include<iostream>

using namespace std;

void main() {
    string start = "ASD";
    string::iterator a = start.begin();
    printf("%x,%x\n",*a,*a);

    getchar();
}

但是輸出結果卻如我所料,輸出了兩個0x41,因此,可以斷定,使用printf可以正確的輸出迭代器指向的內容。但是為什麼會出現上述的問題呢?

反彙編檢視程式碼

為了搞清楚問題的根源,我們對目標程式的反彙編程式碼進行了分析,反彙編程式碼如下所示,為了方便,直接將註釋寫在下面的程式碼片段:

lea         ecx,[a]  
call        std::_String_iterator<std::_String_val<std::_Simple_types<char> > >::operator*  
movsx       eax,byte ptr [eax]  
push        eax  
//該程式碼片段為將迭代器的地址放到ecx暫存器中,然後呼叫迭代器中過載的運算*,
//取出迭代器當前指向的字元,並將地址儲存在eax中,然後使用movsx將字元存入eax並壓棧。
lea         ecx,[a]  
call        std::_String_iterator<std::_String_val<std::_Simple_types<char> > >::operator* 
movsx       ecx,byte ptr [eax]  
push        ecx  
//該程式碼片段同上

sub         esp,0Ch  //提前提升堆疊空間,用於儲存迭代器
mov         edx,esp  //將esp儲存到edx,用於之後將多個內容移動到堆疊的時候進行定址
mov         eax,dword ptr [a]  //取得迭代器a的起始偏移,然後在後方依次將a的內
                            //容儲存到預留的堆疊空間,生成一個a的副本
mov         dword ptr [edx],eax  
mov         ecx,dword ptr [ebp-40h]  
mov         dword ptr [edx+4],ecx  
mov         eax,dword ptr [ebp-3Ch]  
mov         dword ptr [edx+8],eax //迭代器a的副本生成結束


push        41CC1Ch  //0x41cc1c指向printf的格式化引數列表,即"%x,%x,%x\n"
call        _printf (04115F5h)  
add         esp,18h  

通過上述彙編程式碼可以看出,在printf執行前堆疊的狀態如下所示:

0x0019FDD4  1c cc 41 00  .?A.        格式化字串地址
0x0019FDD8  e0 1c 5c 00  ?.\.        迭代器a的副本偏移兩個位元組
0x0019FDDC  00 00 00 00  ....        迭代器a的副本偏移一個位元組
0x0019FDE0  d8 fe 19 00  ??..        迭代器a的副本偏移0個位元組,三個位元組組成迭代器a的副本 
0x0019FDE4  41 00 00 00  A...        第一個*a 
0x0019FDE8  41 00 00 00              第二個*a

但是經常使用printf都知道,printf中對引數進行定位都是使用ebp+xxx進行定位的,而且每個引數都是一個機器長度。只是解釋方式用格式化字串進行判定解釋為指標還是對應的數,因此,可以判定,此處printf認為傳入三個引數分別為迭代器副本的三個位元組,因此出現了上述的問題。

猜想程式碼測試

基於上面的猜想,在printf看來,傳入的引數數量應該是五個,所以,筆者寫出瞭如下的程式碼用於驗證猜想:

void main() {
    string start = "ASD";
    string::iterator a = start.begin();
    printf("%x,%x,%x,%x,%x\n",a,*a,*a);

    getchar();
}
64fc88,0,19fed8,41,41

輸出結果果然與預期的一樣。問題到此算是搞清楚了。

使用vc++ 6.0進行編譯

在vs中存在該問題,所以筆者也想測試下載vc++ 6.0上的表現。在vc++ 6.0進行編譯的時候,輸出的結果均為想要得到的結果。當該程式碼執行在vc++ 6.0中的時候,執行結果如下圖所示:
這裡寫圖片描述
可見vc++ 6.0中並未出現和vs中一樣的問題,這個應該算是vs在編譯的時候存在的一個問題吧。但是為什麼會出現這種問題呢?兩者有什麼在對迭代器的實現上有什麼差異呢?請見下回分解。