![](/img/trans.png)
[英]How does the compiler control help in allocating & deallocating memory in C++?
[英]C++,deallocating already deallocated does not give a compiler error
首先我有三個文件,
mystring.cpp用於實現功能。
#include <iostream>
#include "mystring.hpp"
// Default constructor
MyString::MyString()
{
data = 0;
length = 0;
}
MyString::MyString(int n)
{
data = new char [n];
length = n;
}
MyString::MyString(const char* str, int n)
{
length = n;
data = new char [n];
for (int i = 0; i < n; i++)
{
data[i] = str[i];
}
}
void MyString::trim(int n)
{
if(n < length)
{
int newlength = n;
char* newdata = new char [newlength];
for (int i = 0; i < newlength; i++)
{
newdata[i] = data[i];
}
delete[] data;
data = newdata;
length = newlength;
}
}
MyString::~MyString()
{
delete[] data;
}
void MyString::append(const MyString& rhs)
{
// Determine the length of the resulting
// string and allocate room for it.
int newlength = length + rhs.length;
char* newdata = new char [newlength];
// Copy the current string's data
for (int i = 0; i < length; i++)
{
newdata[i] = data[i];
}
// Copy the given string's data
for (int i = 0; i < rhs.length; i++)
{
newdata[i + length] = rhs.data[i];
}
// Now we must deallocate the original memory
// and update the member variables
delete[] data;
data = newdata;
length = newlength;
}
void MyString::print(char separator) const
{
for (int i = 0; i < length - 1; i++)
{
std::cout << data[i] << separator;
}
std::cout << data[length - 1] << std::endl;
}
其次是一個頭文件mystring.hpp
#ifndef __mystring_hpp__
#define __mystring_hpp__
class MyString
{
public:
// Constructors
MyString();
MyString(int n);
MyString(const char* str, int n);
~MyString();
// Modify the current string by appending "rhs" to it
void append(const MyString& rhs);
// Trim the string such that it contains "n" characters.
// If "n" is larger than the current string's length,
// then do nothing.
void trim(int n);
// Prints this string by putting the separator
// character between each element in the data.
// By default, do not print anything.
void print(char separator = '\0') const;
// This is the destructor. It is automatically
// called when an object of this class is destroyed.
//~MyString(); //Implement!
// This is the assignment operator which is automatically
// called when an object of type MyString is assigned
// to another object of the same type. Technically, it
// does not have to return "MyString&" but we do so to
// allow chaining assignments such as: str1 = str2 = str3
//MyString& operator=(const MyString& rhs); //Implement!
// Copy constructor. Different from the assignment operator,
// this is called when an object is "being created" as a
// copy of another object.
//MyString(const MyString& rhs); //Implement!
private:
char* data;
int length;
};
#endif // __mystring_v1_hpp__
最后 main_assignment.cpp 包括 main.cpp
#include "mystring.hpp"
int main()
{
MyString str1("ali", 3);
MyString str2("veli", 4);
str1 = str2;
return 0;
}
這里的問題是,我沒有重載 MyString 類的賦值運算符,所以在 *main_assignment.cpp * 中,兩個字符串應該指向相同的內存,並且當程序返回時,它應該首先嘗試解除分配兩個中的一個,但是當它來到另一個它會嘗試釋放一個指向一些“未定義”內存的指針。
從我的角度來看,它應該崩潰但程序運行完美,為什么?
當您delete
一個指針兩次時程序的行為是未定義的。 這意味着標准沒有指定在這種情況下應該發生什么——特別是它沒有指定程序應該崩潰。 它可能 - 或者它可能表現得如預期 - 或者它可能會繼續但以意想不到的方式表現,似乎與調用未定義行為的代碼部分沒有任何關系。
正如@TypeIA 已經提到的,C++ 規范中未定義雙重釋放的行為。
而且,事實上,它已經在 GCC 1.17 中作為復活節彩蛋實現了。 但是,請注意,這僅適用於“特定情況”。 編譯器需要非常確定在啟動某些游戲之前調用了未定義的行為。
在大多數情況下,編譯器不能確定啟動復活節彩蛋。 例如,雙重釋放通常不會像您的簡化案例那樣以一種方法實現。 它們發生在例如從磁盤讀取錯誤文件后,或者如果用戶以錯誤的順序按下按鈕等。這些信息在編譯時根本不可用。
因此,任何普通的編譯器都可能會編譯與您在代碼中編寫的內容相對應的內容。 (但是,優化器可能會妨礙您並刪除未使用的變量等。在編寫演示代碼時有時需要考慮這一點。)
如果不是這種情況,您將需要擔心您的程序中的任何錯誤都會向您的雇主發送一封電子郵件,讓您辭職,在 Reddit 上發布您的所有照片,然后格式化您的硬盤。
好的,這就是我對未定義行為的看法。 也許不是很受歡迎,我可能應該因為上述事情而受到一些反對。 然而,我已經調試了很多,根據我 10 年的 WinDbg 經驗,我會說事情通常是可以預測和調試的。
回到你原來的問題...
在問題的標題中,您寫道
解除分配已經解除分配不會給出編譯器錯誤
如果您之前閱讀過我的陳述,希望這不是編譯器錯誤而是運行時錯誤是合乎邏輯的。
我假設您的意思是運行時錯誤,因為您還說:
從我的角度來看,它應該崩潰但程序運行完美
而且您肯定沒想到編譯器會崩潰。
我已將您的代碼復制/粘貼到 Visual Studio 2017 C++ 項目中。 我在 Windows 7 SP1 x64 上的調試版本和發布版本中運行 x86 版本 - 它兩次都崩潰了。
調試構建(在調試器中運行):
發布版本(不在調試器中運行):
在WinDbg中可以看到類的析構函數調用free()
, free()
調用HeapFree()
, HeapFree()
檢測到double free,產生0xc0000374
異常。
0:000> *** Release Build, debugged when crashed
0:000> k
# ChildEBP RetAddr
00 0036f9d0 775bf8a9 ntdll!RtlReportCriticalFailure+0x57
01 0036f9e0 775bf989 ntdll!RtlpReportHeapFailure+0x21
02 0036fa14 7756d95c ntdll!RtlpLogHeapFailure+0xa1
03 0036fa44 0f64fddb ntdll!RtlFreeHeap+0x64
04 0036fa58 0f64fda8 ucrtbase!_free_base+0x1b
05 0036fa68 0137106d ucrtbase!free+0x18
06 (Inline) -------- DeallocateTwice!MyString::{dtor}+0x6 [c:\users\for example john\documents\visual studio 2017\projects\deallocatetwice\mystring.cpp @ 49]
07 0036faa0 01371277 DeallocateTwice!main+0x6d [c:\users\for example john\documents\visual studio 2017\projects\deallocatetwice\deallocatetwice.cpp @ 11]
08 (Inline) -------- DeallocateTwice!invoke_main+0x1c [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 78]
09 0036fae8 74f0343d DeallocateTwice!__scrt_common_main_seh+0xfa [f:\dd\vctools\crt\vcstartup\src\startup\exe_common.inl @ 288]
0a 0036faf4 77529802 kernel32!BaseThreadInitThunk+0xe
0b 0036fb34 775297d5 ntdll!__RtlUserThreadStart+0x70
0c 0036fb4c 00000000 ntdll!_RtlUserThreadStart+0x1b
所以恕我直言,你的假設“它應該崩潰”是一個很好的假設 - 我也會假設它。 它並沒有讓我失望。
如何進行...
下一次,在你的語言上要准確,這樣人們就會開始信任你。 如果您混合編譯時間和運行時,或者混合堆棧和堆,或者翻轉物理 RAM 和虛擬內存,這不是一個好的起點。
要獲得此類情況的幫助,您需要提出更好的問題。 不要問 C++ 標准怎么說。 在大多數情況下,它將是“未定義的”。
相反,發布確切的環境,可能比我做的更准確。 給我們編譯器版本號和編譯器命令行選項。 創建一個最小的、完整的、可行的示例供我們重現。 展示您的調試技巧以及您走了多遠。
然后,有人可能會來回答您為什么它沒有在您的情況下崩潰,例如:
sxi
)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.