0%

C++对象模型 Runtime Semantics

对象的构造与析构

全局对象

C++保证在main函数之前将全局变量构造出来,在main函数结束后销毁

clang中会增加__cxx_global_var_init调用构造函数,并且通过atexit注册析构函数

1
2
3
4
5
6
7
8
9
10
11
__cxx_global_var_init:                  # @__cxx_global_var_init
push rbp
mov rbp, rsp
lea rdi, [rip + b]
call B::B() [complete object constructor]
lea rdi, [rip + B::~B() [complete object destructor]]
lea rsi, [rip + b]
lea rdx, [rip + __dso_handle]
call __cxa_atexit@PLT
pop rbp
ret

多个全局对象在__cxx_global_var_init.1(以此类推)中构造,每个文件有一个_GLOBAL__sub_I_负责调用

1
2
3
4
5
6
7
_GLOBAL__sub_I_example.cpp:             # @_GLOBAL__sub_I_example.cpp
push rbp
mov rbp, rsp
call __cxx_global_var_init
call __cxx_global_var_init.1
pop rbp
ret

局部静态对象

1
2
3
4
void foo() {
static B b;
int i = 1+1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
foo():                                # @foo()
push rbp
mov rbp, rsp
sub rsp, 16
cmp byte ptr [rip + guard variable for foo()::b], 0
jne .LBB0_3
lea rdi, [rip + guard variable for foo()::b]
call __cxa_guard_acquire@PLT
cmp eax, 0
je .LBB0_3
lea rdi, [rip + foo()::b]
call B::B() [complete object constructor]
lea rdi, [rip + B::~B() [complete object destructor]]
lea rsi, [rip + foo()::b]
lea rdx, [rip + __dso_handle]
call __cxa_atexit@PLT
lea rdi, [rip + guard variable for foo()::b]
call __cxa_guard_release@PLT
.LBB0_3:
mov dword ptr [rbp - 4], 2
add rsp, 16
pop rbp
ret

在函数中判断对应的guard variable表示是否初始化,如果没有那么加锁__cxa_guard_acquire然后调用对应的构造函数,注册析构函数
如果已经初始化直接跳转到.LBB0_3:执行函数体内容

对象数组

1
2
3
int main() {
B b[10];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
        lea     rax, [rbp - 336]
mov qword ptr [rbp - 376], rax # 8-byte Spill
add rax, 320
mov qword ptr [rbp - 368], rax # 8-byte Spill
.LBB0_3: # =>This Inner Loop Header: Depth=1
mov rdi, qword ptr [rbp - 368] # 8-byte Reload
add rdi, -32
mov qword ptr [rbp - 384], rdi # 8-byte Spill
call B::~B() [complete object destructor]
mov rcx, qword ptr [rbp - 376] # 8-byte Reload
mov rax, qword ptr [rbp - 384] # 8-byte Reload
cmp rax, rcx
mov qword ptr [rbp - 368], rax # 8-byte Spill
jne .LBB0_3
mov eax, dword ptr [rbp - 4]
add rsp, 384
pop rbp
ret

一个循环,调用构造函数,析构的时候同样是一个循环

new和delete

针对数组的new

不要出现下面的情况

1
2
Base *b = new Derived[10];
delete []b; // call ~Base()

For the first (non-array) form, expression must be a pointer to an object type or a class type contextually implicitly convertible to such pointer, and its value must be either null or pointer to a non-array object created by a new-expression, or a pointer to a base subobject of a non-array object created by a new-expression. The pointed-to type of expression must be similar to the type of the object (or of a base subobject). If expression is anything else, including if it is a pointer obtained by the array form of new-expression, the behavior is undefined.
For the second (array) form, expression must be a null pointer value or a pointer value previously obtained by an array form of new-expression whose allocation function was not a non-allocating form (i.e. overload (10)). The pointed-to type of expression must be similar to the element type of the array object. If expression is anything else, including if it’s a pointer obtained by the non-array form of new-expression, the behavior is undefined.

注意加粗的那句话,delete关键词可以接受指向Base的指针,而delete[]没有这句话

Important bug for C++ object dynam… | Apple Developer Forums
c++ - Possible bug on delete[] for all g++ versions or not defined behaviour for this? - Stack Overflow
operator delete, operator delete[] - cppreference.com

Placement Operator New

可以在指定位置构造一个对象

1
2
Derived *d = static_cast<Derived *>(malloc(sizeof(Derived)));
new (d) Derived;

其实construct的实现就是用了这个

1
2
3
4
5
template <class _Up, class... _Args>
_LIBCPP_DEPRECATED_IN_CXX17 _LIBCPP_INLINE_VISIBILITY
void construct(_Up* __p, _Args&&... __args) {
::new ((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
}

临时对象

sum = x + y执行完成后就可以立即释放x + y表达式所引入的临时变量。但是有两种情况是例外:

  • 用临时变量来声明对象。例如A sum = x + y,表达式x + y生成的临时对象实际成为了sum,需要超出了sum的作用域才能释放;
  • 将临时变量绑定到引用。例如const A &sumRef = x + y,如果立即释放x + y所生成的临时对象,则引用类型的sumRef则会失去意义;
    当然还有左值引用之类的可以延长临时变量生命周期,这就不是这本书讨论的了