说明:
主要内容参照自Working Draft, Standard for Programming Language C++ - N4810的6.6.2 Object model小节。
一、对象模型:
在C++中,对象模型的构念(constructs)是创建,销毁,引用,访问和操作对象。
下列情况会创建对象:
- 定义(definition);
- new表达式(new-expression);
- 隐式地更改联合体(union)的活动成员(该点应该是针对C++17引入的launder指针优化屏障函数);
- 临时对象的创建;
对象在其构造期间,整个生命周期和摧毁期间,都占据一块存储区域。
注意:函数不是对象,无论它是否以对象的方式占用存储。
对象的属性在在创建时就已经确定了。对象具有类型,对象可以有一个名字。对象有一个存储持续时间,它影响其生命周期。
有些对象是多态的, 具体实现生成的信息与每个具体对象相关联,这样让程序在执行期间可以确定该对象的类型。
对于其他对象,其中值的解释由用于访问他们的表达式的类型确定。
二、子对象和完整对象:
1. 对象可以包含其他对象,称为子对象。子对象可以是成员子对象,基类子对象或者是一个数组元素。
2. 一个对象不是任何其他对象的子对象,称为完整对象。
3. 如果在与成员子对象或数组元素e关联的存储中创建对象(可能在其生命周期内,也可能不在其生命周期内),则创建的对象是包含对象的e的子对象,如果:
- e的包含对象的生命周期已经开始而没有结束,并且
- 新对象的存储完全覆盖与e关联的存储位置,而且
- 新对象与e的类型相同(忽略cv-限制,c指const,v指volatile)。
注意:如果子对象包含引用成员或const子对象,则原始子对象的名称不能用于访问新对象。
#include <assert.h>
#include <iostream>
using namespace std;
struct X { const int n; };
union U { X x; float f; };
void tong() {
U u = { { 1 } };
cout << "Union u memory location is " << (void*)&u
<< ",u.x.n = " << u.x.n << ", u.f = " << u.f << endl;
u.f = 5.f; // OK, creates new subobject of u
cout << "u.x.n = " << u.x.n << ", u.f = " << u.f << endl;
X *p = new (&u.x) X{ 2 }; // OK, creates new subobject of u
cout << "Pointer p memory location is " << (void*)p
<< ",p->n = " << p->n << endl;
cout << "u.x.n = " << u.x.n << ", u.f = " << u.f << endl;
assert(p->n == 2); // OK
// The launder function was introduced by C++17, which is not supported by the C++11 compiler.
//assert(*std::launder(&u.x.n) == 2); // OK
assert(u.x.n == 2); // undefined behavior, u.x does not name new subobject
}
int main()
{
tong();
return 0;
}
编译并运行,输出的结果如下:
Union u memory location is 010AFBA0, u.x.n = 1, u.f = 1.4013e-45
u.x.n = 1084227584, u.f = 5
Pointer p memory location is 010AFBA0, p->n = 2
u.x.n = 2, u.f = 2.8026e-45
可以看到,联合体对象u和指针对象p具体相同的内存位置。
assert(u.x.n == 2),在当前测试环境是成立的,依赖于编译器的实现。
4. 如果一个完整对象在与“unsigned char [N]”类型或“std :: byte [N]”类型的另一个对象e相关联的存储中创建,则该数组为创建的对象提供存储,如果:
- e的生命已经开始而没有结束,并且
- 新对象的存储完全适合e,而且
- 没有更小的数组对象满足这些约束。
注意:如果一个数组的部分存储空间之前是为另一个对象提供存储,则另一个对象的生命周期已经结束,因为其存储空间已被重用。
#include <iostream>
using namespace std;
/*
// The template of the original sample is as follows, but it cannot be compiled.
// It is not sure whether it is related to the compiler version.
template<typename ...T>
struct AlignedUnion {
alignas(T...) unsigned char data[max(sizeof(T)...)];
};
*/
struct AlignedUnion {
alignas(sizeof(int)) unsigned char data[sizeof(int)];
};
int f() {
AlignedUnion au;
cout << "AlignedUnion au memory location is " << (void*)&au << endl;
int *p = new (au.data) int; // OK, au.data provides storage
cout << "Pointer p memory location is " << (void*)p << endl;
char *c = new (au.data) char(); // OK, ends lifetime of *p
cout << "Char c memory location is " << (void*)c << endl;
char *d = new (au.data + 1) char();
cout << "Char d memory location is " << (void*)d << endl;
cout << "*c + *d = " << *c + *d << endl;
return *c + *d; // OK
}
struct A { unsigned char a[32]; };
struct B { unsigned char b[16]; };
int main()
{
f();
A a;
cout << "Struct a memory location is " << (void*)&a << endl;
B *b = new (a.a + 8) B; // a.a provides storage for *b
cout << "Pointer b memory location is " << (void*)b << endl;
int *p = new (b->b + 4) int; // b->b provides storage for *p
// a.a does not provide storage for *p (directly),
// but *p is nested within a (see below)
cout << "Pointer p memory location is " << (void*)p << endl;
return 0;
}
编译并运行,输出的结果如下:
AlignedUnion au memory location is 0102FABC
Pointer p memory location is 0102FABC
Char c memory location is 0102FABC
Char d memory location is 0102FABD
*c + *d = 0
Struct a memory location is 0102FBCC
Pointer b memory location is 0102FBD4
Pointer p memory location is 0102FBD8
输出的地址与期望的一致,虽然char *c = new (au.data) char(); // OK, ends lifetime of *p结束了指针p有生命周期,但其值仍指向之前的位置,再次使用会引起数据污染。
5.如下情形表示,对象a嵌套在另一个对象b中:
- a是b的子对象,或者
- b为a提供存储,或者
- 存在一个对象c,其中a嵌套在c中,c又嵌套在b中。
6.对于每个对象x,有一些对象称为x的完整对象,确定如下:
- 如果x是一个完整的对象,那么x的完整对象就是它自己。
- 否则,x的完整对象是:包含x(唯一)的对象的完整对象。
7.如果一个类类型是一个完整的对象,或者是一个数据成员,或者是一个数组元素,则将其类型视为最终派生类。以区别于任何基类子对象的类类型,最终派生类类型或非类类型的对象称为最终派生对象。
8.潜在重叠子对象,是:
- 基类子对象,或
- 使用no_unique_address属性声明的非静态数据成员。
9.如果对象具有非零大小,则:
- 不是潜在重叠子对象,或
- 不是类类型,或
- 具有虚拟成员函数或虚拟基类的类类型,或
- 具有非零大小的子对象或非零长度的位域。
否则,如果对象是没有非静态数据成员的标准布局类类型的基类子对象,则它的大小为零。 否则,对象的大小为零的情况是由具体实现(编译器)定义的。
除非它是位字段,否则具有非零大小的对象将占用一个或多个存储字节,大小包括由其任何子对象全部或部分占用(字节对齐就会存在部分占用)的每个字节。平凡可复制(trivially copyable,is_trivially_copyable)或标准布局(standard-layout)类型的对象应占用连续的存储字节。
三、对象地址:
1.除非一个对象是位字段或是大小为零的子对象,否则该对象的地址是它占用的第一个字节的地址。
2.两个具有重叠生命周期且不是位字段的对象可能具有相同的地址,1)当其中一个对象嵌套在另一个对象中,2)或者至少有一个是大小为零的子对象并且它们是不同的数据类型。
否则,它们具有不同的地址并占据不相交的存储字节。
#include <iostream>
using namespace std;
char* str1 = "hello";
char* str2 = "hello";
static const char test1 = 'x';
static const char test2 = 'x';
int main()
{
std::cout << std::boolalpha;
cout << "test1 memory location is " << (void*)&test1 << endl;
cout << "test2 memory location is " << (void*)&test2 << endl;
cout << "&test1 != &test2 is " << (&test1 != &test2) << endl; // always true
cout << "str1 memory location is " << (void*)&str1 << endl;
cout << "str2 memory location is " << (void*)&str2 << endl;
cout << "&str1 != &str2 is " << (&str1 != &str2) << endl; // always true
cout << "Pointer str1 value is " << (void*)str1 << endl;
cout << "Pointer str2 value is " << (void*)str2 << endl;
cout << "str1 == str2 is " << (str1 == str2) << endl; // always true
return 0;
}
编译并运行,输出的结果如下:
test1 memory location is 01228B30
test2 memory location is 01228B31
&test1 != &test2 is true
str1 memory location is 0122B034
str2 memory location is 0122B038
&str1 != &str2 is true
Pointer str1 value is 01228C5C
Pointer str2 value is 01228C5C
str1 == str2 is true
3.零大小的非位字段子对象的地址是,该子对象的完整对象占用的存储空间中一个未指定的字节。
注意:C ++提供了各种基本类型和多种从现有类型组合新类型的方法。