2013-11-04 45 views
0

我有一臺服務器和一個客戶端。我正在使用winsock2。客戶端發送4個字節:C++ winsock2壞指針斷點觸發

char *ack = new char[4]; 
sprintf(ack, "%d", counter); 
sendto(clientSocket, ack, 4, 0, (struct sockaddr*)&remote, sizeof(remote)); 

和服務器接收這些4個字節:

char* acks = new char[4]; 
if((bytes = recvfrom(serverSocket, acks, 4, 0, (struct sockaddr*)&remote, &remote_size)) == SOCKET_ERROR) { 
    cout << "socket error = " << WSAGetLastError() << endl; 
    break; 
} 
if(bytes > 0) { 
    sscanf(acks, "%d", &i); 
} 

我得到這個錯誤,我無法弄清楚如何解決它:

檢測到嚴重錯誤c0000374

server.exe觸發了一個斷點。

我知道指針和內存分配有問題。但我對C++不太好,我不知道該怎麼做。

+0

如果你確定char的大小是5的話,爲什麼不只是'char acks [5] = {0};'另外,你能顯示「remote」和「remove_size」的聲明嗎? – Brandon

+0

大小實際上是4.我嘗試了不同的事情,並忘記將代碼恢復到正確的形式。數組指針的聲明應該是: 'SOCKADDR_IN remote; int remote_size = sizeof(remote);' 並感謝您的幫助。 – corneliu

+0

我實際上編輯了我的原始帖子並修正了char數組指針的長度 – corneliu

回答

0

字符串格式化溢出

最迫切的問題是,你正在使用的sprintf和sscanf。避免使用sprintf和sscanf - 它們使你很容易意外地創建你在這裏看到的錯誤類型,這是緩衝區溢出(在你的客戶端和你的服務器上)。

考慮您的客戶端上會發生什麼,當你「計數器」值是1729,您的代碼將運行

sprintf(ack, "%d", 1729); 

1729的C風格的字符串表現是五個字節長 - 每一個字節char值'1','7','2','9''\0'。但是你的ack緩衝區只有4字節長!現在你已經將最後一個零字節寫入了你從未分配過的內存塊。在C/C++中,這是未定義的行爲,這意味着你的程序可能會崩潰,或者它可能不會崩潰,如果它沒有崩潰,它最終可能會稍微錯誤,或者它可能工作得很好,或者它可能會大多數時間都工作,除了星期二休息。

這不是一個好地方。

您可能想知道,「如果這太可怕了,爲什麼sprintf只是返回一個錯誤,或者我用一個太小的緩衝區來調用它?答案是sprintf無法進行檢查,因爲它沒有給你任何方式告訴它ack實際上有多大。當你的代碼在這裏調用sprintf,知道ack是4字節長(因爲你剛剛創建了它),但是所有sprintf看到的都是指向某個內存的指針 - 你沒有告訴它一個長度,所以它只需要盲目地希望你給它的大量內存足夠大。

盲目地希望是寫軟件的一個很不好的方法。

您可以在這裏考慮幾種選擇。

  1. 如果你實際上只是想通過線路發送一個int,有沒有真正的任何需要字符串化的INT在所有 - 只是把它以原生格式通過傳遞reinterpret_cast<char*>(&counter)作爲緩衝區的SendTo 與sizeof(計數器)作爲相應的緩衝區長度。在另一端的recvfrom中使用類似的結構。請注意,如果您的發件人和您的收件人具有不同的基本整數表示(例如,如果他們使用不同的字首),則此操作會中斷,但由於您在此處討論Winsock,因此我假設您假設兩端都是最近的Windows版本不會成爲問題。
  2. 如果您確實需要首先將內容串聯起來,請使用size-cognizant字符串轉換函數,如boost::format(由於它在std :: string而不是原始char *緩衝區中進行處理,因此隱含大小認知)或_snprintf_s/_snscanf_s (它顯式接收緩衝區長度參數,但是是特定於Microsoft的)。

recvfrom的訪問衝突

中的sscanf溢出/ sprintf的不一定然而解釋,:

我只想補充一點,在sscanf的行中出現的錯誤。 如果我在recvfrom行註釋該行發生錯誤。

一個可能的解釋可能爲遠程地址不能提供足夠的空間,但只要你remote_size是你remote的正確反映,我預計這會導致recvfrom返回一個錯誤,不會崩潰。另一種可能性是傳遞錯誤的內存/句柄(例如,如果你已經設置了new運算符來避免失敗,或者你的套接字初始化失敗並且你沒有保護)。如果沒有看到代碼正在初始化所有變量,並且理想情況下您在該場景中遇到的實際錯誤,則無法完全說明問題。


即使sprintf的不能趕上這一點,靜態分析工具(如那些包含在Visual Studio 2012/2013)是非常有能力抓住這個特殊的bug。如果您運行通過默認的Visual Studio 2012的代碼分析工具的發佈代碼,它會抱怨有:

錯誤C4996:「sprintf的」:此函數或變量可能是不安全的

一些人們更喜歡static_cast<char*>(static_cast<void*>(&counter))reinterpret_cast<char*>(&counter)。兩者都起作用,它本質上是編碼公約的選擇。

例如,如果你在初始化remoteSOCKADDR_IN,而不是SOCKADDR_STORAGE,你可能會,如果你碰巧從IPv6地址收到遇到這樣的錯誤。 This answer經歷了一些相關的血淋淋的細節。

+0

非常感謝您的詳細解釋。現在它是有道理的。我實際上認爲int字節存儲爲字節。所以如果一個整數永遠不會超過32位= 4字節,我認爲這個長度永遠不會溢出。我增加了字符數組指針的長度爲8,現在就可以了。它可能會溢出大整數,但我的程序不會使用這樣的大整數。現在它工作正常。我急於交付這個項目,所以我沒有時間做更深層的修改。 請注意,這個網站被命名爲堆棧溢出是有原因的:-) – corneliu

+0

int(在Windows上)確實是4個字節長,但int *的*字符串表示可能更長(每字節一個字節數字加空終止符)。 –