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. 边界调整,字节对齐

相关内容

热门资讯

名人名言快!·谢谢 名人名言快!·谢谢如果你给我一个机会,我会给你一个惊喜
《赠汪伦》的赏析 《赠汪伦》的赏析要短但要精赠汪伦唐 李白 李白乘舟将欲行, 忽闻岸上踏歌声。 桃花潭水深千尺, 不及...
动若参商故事? 动若参商故事?古书左传上记载:在远古的时代,有一个叫高辛氏的人,又叫帝喾,他的儿子中,老大叫阏伯,还...
来月经时可以看恐怖片吗? 来月经时可以看恐怖片吗?听说不能看,为什么不能有没有科学的解释还有为什么不能吃辣月经可以防鬼,《尸气...
IBM公司的董事长兼总经理托马... IBM公司的董事长兼总经理托马斯·沃森(小沃森)在他所著《一个企业和它的信念》坚定地认为:()。IB...
孔子爷爷串词怎么写 孔子爷爷串词怎么写 主持人串词是吧, 串词不能太长孩子走的再远,也走不出妈妈的牵挂。母爱,是人世间...
劝人不发怒的成语? 劝人不发怒的成语?忍一时风平浪静,退一步海阔天空 冲动是魔鬼 喜伤心 怒伤肝 人生就像一场戏,因为有...
跪求经典玄幻小说,书荒中。 跪求经典玄幻小说,书荒中。正常点的,不是排行榜前几的,不太血腥,不太YY,不太幼稚,有点情节。。。合...
伊索寓言可以在喜马拉雅录音吗 伊索寓言可以在喜马拉雅录音吗 可以的。_部梢韵略叵猜砝_M收听。_兑了髟⒀浴肥鞘澜缟献罟爬稀⒆钗...
急求游戏家族和成员的名字(大唐... 急求游戏家族和成员的名字(大唐无双)?我们是一群潍坊的要在一起玩....不要太多的特殊符号。特殊符号...
通灵师真的可以看见灵魂吗? 通灵师真的可以看见灵魂吗?这个。 科学家都不知道 偏...
乔斯坦贾德的《玛雅》求解析。还... 乔斯坦贾德的《玛雅》求解析。还是没完全看明白。《苏菲的世界》和《纸牌的秘密》都能基本理解意思。小男孩...
克雷洛夫寓言好句好词十句 克雷... 克雷洛夫寓言好句好词十句 克雷洛夫寓言经典摘抄1、好句 (1)眼前的一切都使我感到新奇:高大的楼房...
陈好的专集都有什么歌啊? 陈好的专集都有什么歌啊?谢谢大家了1 风吹草动 2 因为 3 付出 4 自己 5 女人爱自己 6 幸...
盗墓之蛊墓杂记全文+番外 盗墓之蛊墓杂记全文+番外我是个写书的,却不知为何会钻到地下去遭罪。退一步说,男人虽爱冒险,但希望有美...
为什么四川的妹子特别漂亮? 为什么四川的妹子特别漂亮?1:漂亮,皮肤好。但四川妹子确实漂亮的比较多,皮肤好,时尚,大气。川妹子的...
电影《黑鹰坠落》中的歌曲有那些... 电影《黑鹰坠落》中的歌曲有那些?执行任务出发前,在机库的宿舍里那一段。袜颂咐超动感《我恨我爱你》英文...
达玛花是哪个地区的花 达玛花是哪个地区的花好像是西藏的达玛花"是西藏一种花的名字就是高山大杜鹃花,彝族叫索玛花。灌木,五月...
适合女性读的书籍都有哪些呢?麻... 适合女性读的书籍都有哪些呢?麻烦推荐推荐 不知道你对李脊嫌大筱懿了解不了解,最近我看的《气场哪里来》...
古人助人为乐的故事有哪些? 古人助人为乐的故事有哪些? 严植之救人急难:南朝梁天监年间,严植之在江边偶然看见一个人躺在地上,...