栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

[笔记]C++对象模型

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

[笔记]C++对象模型

C++对象模型笔记 第一章 对象
  • 对象空间占用:至少1byte,普通成员变量占用空间,static成员不占用该对象空间(存储与数据段中),成员函数不占用对象空间。
struct obj {};
static_assert(sizeof(obj) == 1, "");

struct obj2{
	int data;
	static int s_data;	// static member variable
	void func() {}	// member function
	static void func2() {}
};
static_assert(sizeof(obj2) == 4, "");
  • 一个对象中若存在虚函数,则至少增加一个只想虚函数表(virtual table)的指针
  • 为了访问速度以及性能,编译器可能会进行内存字节对齐优化。
struct obj {
	char ch;
	int i32;
};
// 优化与平台/编译器有关
// 以下代码测试平台&编译器版本: gcc version 11.1.0 (Ubuntu 11.1.0-1ubuntu1~18.04.1) 
static_assert(sizeof(obj) == 8, "");
  • this指针调整:我们在访问类成员函数时,会隐式地传入类对象的this指针,一般多发生于多重继承的情况下是。如访问的是某些子类的成员函数,则可能需要调整this指针。
struct A { uint64_t A_data; };
struct B : public A { uint64_t B_data; };
struct C : public B { uint64_t C_data; };

  • 编译器何时会为我们生成"合成构造函数"?
    • 该类没有任何构造函数&但包含一个类类型的成员变量
    • 父类带默认构造&子类没有任何构造函数
    • 一个类有虚函数&类没有任何构造函数
    • 一个类带有虚基类
虚函数
  • 虚函数表指针位于对象开始的位置,虚函数表的内容与子类是否重写了虚函数有关。若子类完全没有override虚函数,那么子类对应的虚函数表中的内容和父类虚函数表的内容是一致的,但要注意它们只是内容一致,实际上存储的是两张virtual table。
#include 
struct base {
    virtual void func() { std::cout << "basen"; }
    virtual ~base() {}
};
struct derived : public base {
    virtual void func() override { std::cout << "derivedn"; };
    virtual ~derived() {}
};

int main() {
    base *b = new base();
    base *d = new derived();
    [[maybe_unused]]int a = 0xff;
}

  • 虚函数表在内存布局
  • 多重继承下,子类的虚函数表指针可能存在多个
struct A {
    virtual void funcA() {}
    virtual ~A() {}
};

struct B {
    virtual void funcB() {}
    virtual ~B() {}
};

struct C : public A, public B {
	virtual ~C() {}
};

static_assert(sizeof(A) == 8, "");
static_assert(sizeof(B) == 8, "");
static_assert(sizeof(C) == 16, "");
  • 诸如存在虚函数的类,使用memset/memcpy之类的函数可能会存在语义不一致的问题。
struct mydata {
	int data;
	virtual void func() {}
	virtual ~mydata() {}
	mydata() {
		memset(this, 0, sizeof(mydata)); // 会将vptr清零
		// ...
	}
};


int main() {
	auto *ptr = new mydata();
	//ptr->func(); runtime error
}
  • 要注意虚函数调用的时机,非指针、引用的变量调用virtual function是不会触发虚函数的
  • 虚函数在构造函数中插入对vptr赋值的代码,即使在继承下,构造顺序是:父类→子类,因此在子类的构造函数结束后,vptr最终的值是正确的,这里存在多次对vptr赋值的情况。
数据
  • 编译器对成员函数的解析在整个类定义完毕后才开始
  • 成员函数参数类型的确定时机:最近碰到原则
struct myobj {
	using myType = int;
	// using myType to define others...
};
  • linux nm命令可查看各个symbol在内存空间的地址
  • global variable, global function. static member function, static member variable的地址在编译器即可确定,通常存放于bss段,数据段,他们加载(映射)到内存后地址也不会发生改变
  • 如何修改x字节对齐?
#pragma pack(4)
  • 成员&成员函数偏移
    • 参考operator_member_access
  • 非虚函数的单继承下,数据成员顺序与继承顺序一致,需要注意字节对齐
  • 存在虚函数的单继承下,vptr始终在对象的最前面,数据成员顺序与继承顺序一致,同样地也需要考虑字节对齐
  • 多继承出现的菱形继承问题需要使用虚基类解决
函数
  • 多继承下如果类中有虚函数,那么析构函数也应该是虚函数,否则函数语义不正确
  • 多继承,如果n个父类都包含虚函数,则子类存在n个vptr,且都需要this指针调整
对象构造
  • 父类到子类的构造顺序,如果是虚函数,则每次都早都要对vptr赋值
  • 析构函数和构造函数顺序相反,虚基类?
  • 函数中的static对象保证只会初始化一次
#include 

struct Data
{
    uint64_t A;
    uint64_t B;
    Data()
    {
        A = 0xff;
        B = 0xee;
    }
    // ...
};

auto func()
{
    static Data global_variable;
    Data *ptr = &global_variable;
    return ptr;
}

int main()
{
    [[maybe_unused]] auto i = func();
    [[maybe_unused]] auto j = func();
    [[maybe_unused]] int k = 0xff;
}



析构:最后会在atexit中注册

objdump反汇编结果:

00000000000008e5 :
 8e5:	55                   	push   %rbp
 8e6:	48 89 e5             	mov    %rsp,%rbp
 8e9:	48 83 ec 10          	sub    $0x10,%rsp
 8ed:	0f b6 05 8c 08 20 00 	movzbl 0x20088c(%rip),%eax        # 201180 
 8f4:	84 c0                	test   %al,%al
 8f6:	0f 94 c0             	sete   %al
 8f9:	84 c0                	test   %al,%al
 8fb:	74 59                	je     956 
 8fd:	48 8d 05 7c 08 20 00 	lea    0x20087c(%rip),%rax        # 201180 
 904:	48 89 c7             	mov    %rax,%rdi
 907:	e8 d4 fe ff ff       	callq  7e0 <__cxa_guard_acquire@plt>
 90c:	85 c0                	test   %eax,%eax
 90e:	0f 95 c0             	setne  %al
 911:	84 c0                	test   %al,%al
 913:	74 41                	je     956 
 915:	48 8d 05 54 08 20 00 	lea    0x200854(%rip),%rax        # 201170 
 91c:	48 89 c7             	mov    %rax,%rdi
 91f:	e8 d2 00 00 00       	callq  9f6 
 924:	48 8d 05 dd 06 20 00 	lea    0x2006dd(%rip),%rax        # 201008 <__dso_handle>
 92b:	48 89 c2             	mov    %rax,%rdx
 92e:	48 8d 05 3b 08 20 00 	lea    0x20083b(%rip),%rax        # 201170 
 935:	48 89 c6             	mov    %rax,%rsi
 938:	48 8d 05 d9 00 00 00 	lea    0xd9(%rip),%rax        # a18 
 93f:	48 89 c7             	mov    %rax,%rdi
 942:	e8 69 fe ff ff       	callq  7b0 <__cxa_atexit@plt>
 947:	48 8d 05 32 08 20 00 	lea    0x200832(%rip),%rax        # 201180 
 94e:	48 89 c7             	mov    %rax,%rdi
 951:	e8 4a fe ff ff       	callq  7a0 <__cxa_guard_release@plt>
 956:	48 8d 05 13 08 20 00 	lea    0x200813(%rip),%rax        # 201170 
 95d:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
 961:	48 8b 45 f8          	mov    -0x8(%rbp),%rax
 965:	c9                   	leaveq 
 966:	c3                   	retq 
  • 局部静态对象数组和局部静态对象类似,只是数组要调用sizeof(arr)的构造函数和析构函数
reference
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/921143.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号