剛開學, 比較忙, 沒有太多時間寫網誌了, 今天分享一個前幾天自己在寫 C++ 的時候遇到的一個關於記憶體方面的坑吧

錯誤的動態記憶體配置在 C++ 程式設計中非常常見, 導致其的主要原因是指標的亂用、隨意回收、隨意配置等. 昨天在寫 C++ 程式碼的時候, 誤將 operator newdelete[] 混合使用. 其實在保證絕對安全的情況之下, 這兩者混合使用確是沒有什麼問題的, 但是就怕水平不夠

我們來看一個例子

class Foo {

private:

    int *p;

public:

    Foo() : p {new int} {}

    ~Foo() {

        delete this->p;

    }

};

這種含有指標的類別在 C++ 中特別多, 尤其是我們在寫資料結構的時候, 我們為了保證低層的速度, 我們通常直接使用 C++ 內建的指標進行操作, 而不對指標機型封裝或者使用已經封裝好的智慧指標

我們來看看如果對 Foo 類別混合使用 operator newdelete[] 會怎麼樣

class Foo {

private:

    int *p;

public:

    Foo() : p {new int} {}

    ~Foo() {

        delete this->p;

    }

};

int main(int argc, char *argv[]) {

    auto arr {reinterpret_cast<Foo *>(::operator new (sizeof(Foo) * 10))};

    delete[] arr;

}

下面是記憶體檢測工具 Valgrind 給出的檢測結果

new、operator new、delete 與 operator delete-Jonny'Blog

為什麼會出現這樣的情況呢?

因為使用 operator new 配置的記憶體和 malloc 一樣, 都是未初始化的, 而對於未初始化的指標, 其指向的記憶體位址是未知的, 而且肯定也不是 nullptr, 所以當我們使用 delete[] 時, 就會產生 Invalid Free

這個時候我們需要稍微深究 delete[] 的行為

因為 Invalid Free 錯誤的出現, 從而肯定了 delete[] 訪問了 Foo 類別的解構子

因此, 我們可以從中推斷 :

delete[] 首先按照申請的指標的大小對每一個申請的元素進行解構, 之後一次性釋放記憶體, 這是導致 Invalid Free 的根本原因

因此, 我們應該儘量避免混用 operator newdelete

但是有一些例外, 對於具有 trivial destructor 的型別來說, 混用一般來說不出現問題

int main(int argc, char *argv[]) {

    auto arr {reinterpret_cast<int *>(::operator new (sizeof(int) * 10))};

    delete[] arr;

}

此時, Valgrind 並沒有給出任何錯誤

new、operator new、delete 與 operator delete-Jonny'Blog

不過, 這裡只是 Clang 給出的結果, Clang 可能會對此進行特殊的優化, 具體以 C++ 標準為準