礦坑系列首頁:首頁
hackmd 版首頁:首頁
new
是 C++ 內一個幫助我們操作記憶體的工具,傳統的 pure C 內也有類似的工具 malloc
,然而由於 C++ 語言上內建多支援了物件導向的特性,導致 new
比 malloc
多做了一些事情
本篇大部分內容為整理 Effective C++ 而來,並額外補上一些細節
首先先講一下 malloc
在 C++ 的行為,要想講這個我們最好先知道兩個東西,一個為 Aggregates,一個為 POD,全名為 Plain Old Data,這兩個東西對於 C-style coding 非常的有幫助,在某些 Modern C++ 的場合也會有幫助
這邊我主要翻譯一篇 stackoverflow 上的解釋,這篇寫的挺好的,大家也可以去看看原文( 連結 )
Aggregate 與 POD 的定義在 C++11、C++14、C++17 甚至 C++20 時都有更動,但基本上都是增加了新的東西進去,因此這裡主要以 C++03 為主來去解釋,幫助大家先快速理解,後面再回頭介紹 C++11 後的定義
一般我們理解的定義來自於 Standard 的定義:
(C++03 8.5.1 §1):
An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).
較新的定義(C++17):
n4659(11.6.1 - 1):
An aggregate is an array or a class with
── no user-provided, explicit, or inherited constructors ([class.ctor]),
── no private or protected non-static data members (Clause [class.access]),
── no virtual functions, and
── no virtual, private, or protected base classes ([class.mi]).[ Note: Aggregate initialization does not allow accessing protected and private base class' members or constructors. — end note ]
所以你可以看見一個 array 肯定是個 Aggregate,而 class 的話則要符合上面那些條件,注意 draft 裡面的 class 有包含 struct 與 union
我們詳細看一下有關 class 的幾個重點
private
或 protected
的 non-static data member,但如果是 static data member 就沒關係,非 constructor 的 member function 也沒關係,但不能有 virtual function現在我們就來看看 Aggregate 有什麼好處,主要是可以使用 Aggregate initialization {}
來初始化,你可以常常在 array 的初始化裡看到它,舉個例子:
Type arr[n] = {a1, a2, …, am};
根據 n
與 m
的不同會有幾個情況:
m == n
m < n
m > n
n == m
看個例子:
而對於 class,初始化的方法長的差不多,{}
內會照 class definition 的順序對其 non-static data member 做初始化,同樣的如果 {}
太少,剩下的元素也是使用 value-initialized,如果它不能使用 value-initialized,就會導致 compile-time error,看個例子:
上例中這裡 y.c
被初始化為 a
,y.x.i1
初始化為 10
,y.i[1]
初始化為 30
,y.f
初始化為 0.0
看到這裡你可能會想說為什麼需要這些設定,前面也講了,這可以幫助 C-style coding,某種程度上也代表這個 Type 類似 built-in type,如 int
、char
等可以幫助 compiler 去做優化
使用 {}
初始化代表這個 class 內的元素就只有 {}
內的元素,且初始化時不需要額外特別的操作
當你的 class 有自定義的 constructor 時,通常表示在初始化內部成員時需要一些額外的操作,因此使用 aggregate-initialized 會造成 compile-time error
而如果你的 class 有 virtual function,在大多數的 compiler 實作下,這個 class 會有對應的 vtable,而指向 vtable 的指標 this->__vptr
通常會利用 constructor 的 member initialize list 初始化,因此使用 aggregate-initialized 也是不恰當的
對於 vtable,有興趣的可以看看這篇:What happens in the hardware when I call a virtual function? How many layers of indirection are there? How much overhead is there?
POD 全名叫 Plain Old Data,一般我們理解的 POD 與 C++03 標準內的定義相同:
(C++ 03 9 §4):
A POD-struct is an aggregate class that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor. Similarly, a POD-union is an aggregate union that has no non-static data members of type non-POD-struct, non-POD-union (or array of such types) or reference, and has no user-defined copy assignment operator and no user-defined destructor. A POD class is a class that is either a POD-struct or a POD-union.
先不看 union 與 enum,把上面的文字稍微過濾一下:
An aggregate class is called a POD if it has no user-defined copy-assignment operator and destructor and none of its nonstatic members is a non-POD class, array of non-POD, or a reference.
這段話基本上表達了幾件事:
順便看一下比較新的例子 (C++17):
n4659 (12 - 10):
A POD struct109 is a non-union class that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). Similarly, a POD union is a union that is both a trivial class and a standard-layout class, and has no non-static data members of type non-POD struct, non-POD union (or array of such types). A POD class is a class that is either a POD struct or a POD union.
舉一些 POD 的例子:
而對於 POD-class、POD-union、scalar types 和它們的 array,C++ 給了他們一個名字叫 POD type
n4659 (6.9 - 9):
Arithmetic types, enumeration types, pointer types, pointer to member types, std::nullptr_t, and cv-qualified versions of these types are collectively called scalar types. Scalar types, POD classes, arrays of such types and cv-qualified versions of these types are collectively called POD types.
POD 的好處就很多了,這邊舉幾個例子:
POD-class 與 C struct 非常接近,但 POD class 可以有 member function、static member,不過這兩者並不會影響 memory layout。 所以如果你想要寫一個給 C 或甚至 .NET 用的 portable dll,你就需要讓你 exported function 的參數和回傳值都使用 POD-type,簡單來說就是 POD 對序列化很有幫助
non-POD class 的生命週期從 constructor 完成開始,到 destructor 完成時結束;POD-class 的生命週期則是從物件占用記憶體開始,不用等到建構子執行完畢,而生命週期也是到其釋放記憶體結束
對於 POD types 的物件,標準保證其可以直接使用 memcpy
,當你將 POD-class 的內容使用 memcpy
複製到 char/unsinged char array,再對陣列使用 memcpy
把內容複製回物件時,物件會維持原先的值,內容不變。 這件事對 non-POD type object 是沒有保證的
看個例子:
goto 可以跳過 POD-type 物件的宣告,進入宣告後段的程式碼:
POD-type 物件的第一個成員位址會和物件本身的位址一樣,舉個例子:
由於 windows 不好分析,這邊以 Linux 環境為主來講 malloc 的原理
這部分要講很深的話會牽扯到整個 Linux 的記憶體管理,但這並不是我們的重點,所以我盡量不要講太多,提到必要的東西就好,
記憶體的分配分為動態分配與靜態分配,靜態分配發生在編譯與 linking 時期,而動態分配則是在程式調入和執行的時候發生的
Placement new 本身是一個能夠幫助我們使用 memory pool 的工具,它能夠在預先配置好的一段記憶體建構物件
這在某些時刻特別有用,如監聽程式、伺服器程式、或是要實作如 std::vector
這類高效能的容器時。因為這類東西,記憶體配置與釋放的頻率很高,若此時使用傳統的 new 與 delete 來配置與釋放記憶體,可能會有效率上的疑慮(new 在申請時需要查表),也有可能會造成 memory fragmentation 等問題
甚至有時候有些核心程式不允許記憶體申請失敗,我們希望每次的記憶體申請都是成功的,這種極端的要求下,使用 memory pool 的好處就很大
首先,new operator/delete operator 為運算子,而 operator new/operator delete 為函式
Operator new 是一個幫忙配置 raw memory 的「函式」,它可以被 overload,同樣的 operator delete 也可以,如:
operator new 也有可能是透過一般的函式呼叫形式來呼叫的;呼叫的形式有很多種,可以到這邊參考:operator new, operator new[ ]
而 new operator 就是我們平常用來分配記憶體的 operator,如
要注意 new operator 不能被 overload,挺合理的,因為它是 operator
使用 new operator 時它會先去呼叫 operator new,幫我們分配足夠的 raw memory,再去呼叫對象的建構子
Placement new 是 operator new 的一種標準的 global overload,它不像一般的 operator new 可以被替換掉
它的形式主要長:
不過 placement new 在執行時會忽略第一個 size_t
的參數,並回傳第二個參數
placement new 的用處是於 ptr
處建構我們想要的物件,通常我們會於前方先分配好 memory pool,並將其指標傳入,像是:
此處的 alignas(T) unsigned char buf[sizeof(T)];
就為我們分配的 memory pool,大小為一個 T
的大小,前方的 alignas
是幫助對齊的,不是我們這邊的重點,而這裡使用 unsigned char
的原因是因為 sizeof(char) == 1
,你也可以用 char
或 std::byte
等等,我自己傾向使用 std::byte
需要注意的是物件使用完畢後需要自己手動呼叫解構子,所以上例使用了 tptr->~T();
,如此一來前面建構的物件會被解構,但先前分配好的 buf
不會被釋放
2. In what cases do I use malloc and/or new?
4. What are Aggregates and PODs and how/why are they special?