C++对象模型探索--04数据语义
创始人
2024-02-17 12:42:52
0

数据语义学

数据成员绑定时机

  1. 编译器对成员函数的解析是整个类定义完毕后才开始的。因为只有整个类定义完毕后编译器才能够知道类的成员变量,才能根据时机在需
    要出现类成员变量的场合做出适当的解析(成员函数中解析成类中的成员变量,全局函数中解析成全局的变量
  2. 对于成员函数参数是在编译器第一次遇到这个类型的时候决定的

进程内存空间布局

不同的数据在内存中有不同的保存时机、保存位置。在执行程序时,总内存会被分为多个段,称为文本,未初始化的全局、初始化的全局、栈和堆段。将整个程序加载到文本段中,并在堆栈存储器中选择存储器到变量。

High Addresses ---> .----------------------.|      Environment     ||----------------------||                      |   Functions and variable are declared|         STACK        |   on the stack.base pointer -> | - - - - - - - - - - -||           |          ||           v          |:                      :.                      .   The stack grows down into unused space.         Empty        .   while the heap grows up. .                      ..                      .   (other memory maps do occur here, such .                      .    as dynamic libraries, and different memory:                      :    allocate)|           ^          ||           |          |brk point -> | - - - - - - - - - - -|   Dynamic memory is declared on the heap|          HEAP        ||                      ||----------------------||          BSS         |   Uninitialized data (BSS)|----------------------|   |          Data        |   Initialized data (DS)|----------------------||          Text        |   Binary code
Low Addresses ----> '----------------------'

栈(STACK)

  1. 它位于较高的地址,与堆段的增长和收缩方向正好相反。
  2. 函数的局部变量存在于栈上
  3. 每个函数都有一个栈帧,调用函数时,将在栈中创建一个栈帧。栈帧包含函数的局部变量参数和返回值。
  4. 栈包含一个LIFO结构。函数变量在调用时被压入栈,返回时将函数变量从栈弹出。
  5. SP(栈指针)寄存器跟踪栈的顶部。

堆(HEAP)

  1. 堆区域由进程中的所有共享库和动态加载的模块共享。它在堆栈的相反方向上增长和收缩。
  2. 用于在运行时分配内存。由内存管理函数(如malloc、calloc、free等)管理的堆区域,这些函数可以在内部使用brk和sbrk系统调用来调整其大小。

未初始化的数据块(BSS)

此段包含所有未初始化的全局和静态变量,所有变量都由零或者空指针初始化。程序加载器在加载程序时为BSS节分配内存。

初始化的数据块(DS)

此段包含显式初始化的全局变量和静态变量,大小由程序源代码中值的大小决定,在运行时不会更改。它具有读写权限,因此可以在运行时更改此段的变量值。

代码段(TEXT)

该段是一个只读段,包含已编译程序的二进制文件。该段是可共享的,因此对于文本编辑器等频繁执行的程序,内存中只需要一个副本

C++单继承下内存布局分析

#include 
class A
{
public:int a;A() : a(0x1) {}virtual void foo() { std::cout << "A::foo()" << std::endl; }void bar() { std::cout << "A::bar()" << std::endl; }
};class B : public A
{
public:int b;B() : A(), b(0x2) {}void foo() { std::cout << "B::foo()" << std::endl; }
};class C : public B
{
public:int c;C() : B(), c(0x3) {}void foo() { std::cout << "C::foo()" << std::endl; }
};int main(int argc, char **argv)
{A a;B b;C c;B *p = &c;p->foo();std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;return 0;
}

g++ main.cpp -o main -std=c++14 -g
gdb查看

(gdb) b 29
Breakpoint 1 at 0x120b: file main.cpp, line 29.
(gdb) r
Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:29
29          A a;
(gdb) n
30          B b;
(gdb) set print pretty on
(gdb) set print vtbl on
(gdb) p a
$1 = {_vptr.A = 0x555555557d38 ,a = 1
}
(gdb) p/a &a
$2 = 0x7fffffffdbf0
(gdb) p/a &a.a
$3 = 0x7fffffffdbf8
(gdb) p sizeof(a)
$4 = 16
(gdb) x/2xg &a 
0x7fffffffdbf0: 0x0000555555557d38      0x00007fff00000001
(gdb) info vtbl a
vtable for 'A' @ 0x555555557d38 (subobject @ 0x7fffffffdbf0):
[0]: 0x555555555344 
  • _vptr.A:代表a对象所含有的虚函数表指针,0x555555557d38为第一个虚函数也即foo()的地址,真正虚函数表的起始地址为0x555555557d38 - 16,还会有一些虚函数表头信息,vptr 总是指向 虚函数表的第一个函数入口
  • 对象a所在的地址为0x7fffffffdbf0,整个对象占16个字节,其中8个字节为vptr虚函数表指针,4个字节为数据int a
  • vtable for ‘A’ @ 0x555555557d38

gdb查看

(gdb) n
31          C c;
(gdb) p b
$6 = { = {_vptr.A = 0x555555557d20 ,a = 1}, members of B:b = 2
}
(gdb) p sizeof(b)
$7 = 16
(gdb) n
32          B *p = &c;
(gdb) p c
$8 = { = { = {_vptr.A = 0x555555557d08 ,a = 1}, members of B:b = 2}, members of C:c = 3
}
(gdb) p sizeof(c)
$9 = 24
(gdb) info vtbl b
vtable for 'B' @ 0x555555557d20 (subobject @ 0x7fffffffdc00):
[0]: 0x5555555553ba 
(gdb) info vtbl c
vtable for 'C' @ 0x555555557d08 (subobject @ 0x7fffffffdc10):
[0]: 0x555555555430 

如果class B中申明了新的虚函数(比如foo2),class B中依然只有一个虚函数表,只不过会把foo2加入到该表中。此时class A的虚函数表不会包含foo2。

C++多重继承下内存布局分析

#include class A
{int a;virtual void foo() { std::cout << "A::foo()" << std::endl; }
};class B
{int b;virtual void bar() { std::cout << "B::bar()" << std::endl; }
};class C : public A, public B
{int c;void foo() { std::cout << "C::foo()" << std::endl; }void bar() { std::cout << "C::bar()" << std::endl; }
};int main(int argc, char **argv)
{A a;B b;C c;std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;return 0;
}

gdb查看

gdb main 
(gdb) b 28
Breakpoint 1 at 0x122f: file main.cpp, line 28.
(gdb) set print pretty on
(gdb) set print vtbl on
(gdb) set print object on
(gdb) p a
No symbol "a" in current context.
(gdb) r
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".Breakpoint 1, main (argc=1, argv=0x7fffffffdd48) at main.cpp:28
28          std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
(gdb) p a
$1 = (A) {_vptr.A = 0x555555557d20 ,a = -134528544
}
(gdb) p b
$2 = (B) {_vptr.B = 0x555555557d08 ,b = -135408993
}
(gdb) p/a c
$3 = (C) { = {_vptr.A = 0x555555557cd0 ,a = 0xfffffffff7eab60a},  = {_vptr.B = 0x555555557cf0 ,b = 0xfffffffff7fb3e88}, members of C:c = 0x7fff
--Type  for more, q to quit, c to continue without paging--
}
(gdb) p sizeof(c)
$4 = 32
(gdb) x/5ag &c
0x7fffffffdc00: 0x555555557cd0 <_ZTV1C+16>      0x7ffff7eab60a <_ZNSt9basic_iosIwSt11char_traitsIwEE15_M_cache_localeERKSt6locale+90>
0x7fffffffdc10: 0x555555557cf0 <_ZTV1C+48>      0x7ffff7fb3e88 <_ZSt5wclog+8>
0x7fffffffdc20: 0x7ffff7fb3940
  • 数据成员int a, int b, int c都未初始化,此时是UB未定义行为
  • 对象c含有两个虚函数表指针_vptr.A和_vptr.B,占用32个字节内存,3个int数据成员,两个虚函数表指针
  • 对象c的内存布局为 c: vptr.A | a | vptr.B | b | c

C++虚继承下内存布局分析

#include class A
{int a;virtual void foo() { std::cout << "A::foo()" << std::endl; }
};class B : virtual public A
{int b;virtual void foo() { std::cout << "B::foo()" << std::endl; }
};class C : virtual public A
{int c;void foo() { std::cout << "C::foo()" << std::endl; }
};class D : public B, public C
{int d;virtual void foo() { std::cout << "D::foo()" << std::endl; }
};int main(int argc, char **argv)
{A a;B b;C c;D d;A *pa = &d;B *pb = &d;C *p c = &d;std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;return 0;
}

gdb查看

gdb main
(gdb) b 37
Breakpoint 1 at 0x1247: file main.cpp, line 37.
(gdb) r
37          std::cout << sizeof(int) << " " << sizeof(int *) << std::endl;
(gdb) set print pretty on
(gdb) set print object on
(gdb) set print vtbl on
(gdb) p a
$1 = (A) {_vptr.A = 0x555555557ce0 ,a = 0
}
(gdb) p b
$2 = (B) { = {_vptr.A = 0x555555557cb8 ,a = -238370304}, members of B:_vptr.B = 0x555555557c98 ,b = 0
--Type  for more, q to quit, c to continue without paging--
}
(gdb) p c
$3 = (C) { = {_vptr.A = 0x555555557c68 ,a = -134528544}, members of C:_vptr.C = 0x555555557c48 ,c = -134529192
}
(gdb) p d
$4 = (D) { = { = {_vptr.A = 0x555555557b70 ,a = -134529400}, members of B:_vptr.B = 0x555555557b30 ,b = -135408993},  = {members of C:_vptr.C = 0x555555557b50 ,c = -135612918}, members of D:d = 32767
}
(gdb) p &d
$5 = (D *) 0x7fffffffdbf0
  • 对象内存布局
    • a: _vptr.A | a
    • b: _vptr.A | a | _vptr.B | b
    • c: _vptr.A | a | _vptr.C | c
    • d: _vptr.A | a | _vptr.B | b | _vptr.C | c | d
  • A *pa = &d;B pb = &d;Cp c= &d;都指向d的起始地址&d = 0x7fffffffdbf0。假如d类里实现的虚函数都放在A的虚函数表里,没有实现的放在被继承的基类里面

数据成员布局

  1. 普通成员变量的存储顺序是按照在类中定义的顺序从上到下来的
  2. 类定义中public、private、protected的数量不影响类对象的sizeof
  3. 边界调整,字节对齐

相关内容

热门资讯

CBA联赛总决赛工作会召开 力... 转自:中国体育报5月5日,中国篮球协会在京召开了2024-2025赛季CBA联赛总决赛工作会议。本次...
遗嘱中有错别字或修改痕迹还有效... 随着公民法治意识逐渐增强,很多人会选择通过订立遗嘱来处理自己的身后事。在实践中,遗嘱人在书写遗嘱时,...
李娇娇,新职明确 编辑丨余晖辽宁省葫芦岛市连山区人民政府官方网站“政府领导”页面更新显示,李娇娇已任葫芦岛市连山区委副...
全球客商用订单书写对中国的信心 来源:@华夏时报微博 【#全球客商用订单书写对中国的信心...
科大国创回购股份前夕:前十大股... 2025年4月27日,科大国创软件股份有限公司召开第四届董事会第二十四次会议和第四届监事会第二十四次...
美股异动 | 中概股逆势走高 ... 周二,纳斯达克中国金龙指数涨近1%,中概股逆势走高,有道(DAO.US)涨超3%,阿里巴巴(BABA...
官宣:40多国近百名军官访华 非洲中青年军官代表团访华。应中国国防部邀请,5月6日至15日,由埃及、莫桑比克、坦桑尼亚、肯尼亚等4...
方寸实体聚微光 体彩公益汇大爱   从街角的方寸之地到城市的温暖坐标,甘肃体彩实体店正以创新之姿展现公益内涵,让每家实体店不仅是幸运...
电广传媒文旅板块“五一”揽客5... 今年“五一”假期,电广传媒文旅板块交出一份亮眼成绩单。旗下景区及酒店累计接待游客52.3万人次,同比...
*ST恒立:因未按规定期限披露... *ST恒立(维权)5月6日公告,公司于今日收到中国证监会下发的《立案告知书》。因公司未按规定期限披露...
中国铁物:公司与铁龙物流和中铁... 每经AI快讯,有投资者在投资者互动平台提问:请问贵公司与上市公司铁龙物流和中铁特货有铁路货运业务上的...
中国银行发布关于远离违法违规外... 格隆汇5月6日|目前,我国未批准任何机构在境内开展或代理开展外汇按金交易,擅自开展、参与外汇按金交易...
这家信托被托管!大股东突发公告... 近日,泛海控股股份有限公司(简称“泛海控股”)发布《关于不再将中国民生信托有限公司纳入公司合并范围的...
永善枇杷冷链直发申城,转内销绿... 转自:上观新闻“永善枇杷独具特色,果皮轻薄易剥,果肉细腻紧实,且甜度很高。在今年上海‘五五购物节’期...
肺炎康复指南:从急性期到恢复期...   肺炎是一种常见的呼吸道疾病,对于患者来说,科学的康复步骤对于身体的恢复至关重要。下面,为大家介绍...
堪称恐怖片!民进党翻车了 民进党当局日前推出的一条“大罢免”宣传片翻车了。视频中,一段小朋友怒斥父亲不关心“大罢免”的剧情引发...
汤姆猫控股股东质押超98%股份 5月6日,浙江金科汤姆猫文化产业股份有限公司(汤姆猫,300459.SZ)发布关于股东部分股份质押的...
通裕重工实控人将变更为山东省国... 通裕重工5月7日开市起复牌。5月6日晚公告,珠海港集团及其控股股东与国惠资本签署协议,珠海港集团将6...
【环球财经】巴西财政部长与美国... 新华财经圣保罗5月6日电(记者杨家和) 巴西财政部长费尔南多·阿达当地时间4日与美国财政部长斯科特·...
香港外汇基金首季投资收入672... 来源:中国新闻网 中新社香港5月6日电 香港金融管理局6日公布,香港外汇基金2025年首季度投资收入...