C++的对象模型

说明:

主要内容参照自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 ++提供了各种基本类型和多种从现有类型组合新类型的方法。

发布了8 篇原创文章 · 获赞 12 · 访问量 5268

猜你喜欢

转载自blog.csdn.net/Hi_douzi/article/details/90711945
今日推荐