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

相关内容

热门资讯

Python|位运算|数组|动... 目录 1、只出现一次的数字(位运算,数组) 示例 选项代...
张岱的人物生平 张岱的人物生平张岱(414年-484年),字景山,吴郡吴县(今江苏苏州)人。南朝齐大臣。祖父张敞,东...
西游西后传演员女人物 西游西后传演员女人物西游西后传演员女人物 孙悟空 六小龄童 唐僧 徐少华 ...
名人故事中贾岛作诗内容简介 名人故事中贾岛作诗内容简介有一次,贾岛骑驴闯了官道.他正琢磨着一句诗,名叫《题李凝幽居》全诗如下:闲...
和男朋友一起优秀的文案? 和男朋友一起优秀的文案?1.希望是惟一所有的人都共同享有的好处;一无所有的人,仍拥有希望。2.生活,...
戴玉手镯的好处 戴玉手镯好还是... 戴玉手镯的好处 戴玉手镯好还是碧玺好 女人戴玉?戴玉好还是碧玺好点佩戴手镯,以和田玉手镯为佳!相嫌滑...
依然什么意思? 依然什么意思?依然(汉语词语)依然,汉语词汇。拼音:yī    rán基本解释:副词,指照往常、依旧...
高尔基的散文诗 高尔基的散文诗《海燕》、《大学》、《母亲》、《童年》这些都是比较出名的一些代表作。
心在飞扬作者简介 心在飞扬作者简介心在飞扬作者简介如下。根据相关公开资料查询,心在飞扬是一位优秀的小说作者,他的小说作...
卡什坦卡的故事赏析? 卡什坦卡的故事赏析?讲了一只小狗的故事, 我也是近来才读到这篇小说. 作家对动物的拟人描写真是惟妙...
林绍涛为简艾拿绿豆糕是哪一集 林绍涛为简艾拿绿豆糕是哪一集第三十二集。 贾宽认为是阎帅间接导致刘映霞住了院,第二天上班,他按捺不...
小爱同学是女生吗小安同学什么意... 小爱同学是女生吗小安同学什么意思 小爱同学,小安同学说你是女生。小安是男的。
内分泌失调导致脸上长斑,怎么调... 内分泌失调导致脸上长斑,怎么调理内分泌失调导致脸上长斑,怎么调理先调理内分泌,去看中医吧,另外用好的...
《魔幻仙境》刺客,骑士人物属性... 《魔幻仙境》刺客,骑士人物属性加点魔幻仙境骑士2功1体质
很喜欢她,该怎么办? 很喜欢她,该怎么办?太冷静了!! 太理智了!爱情是需要冲劲的~不要考虑着考虑那~否则缘...
言情小说作家 言情小说作家我比较喜欢匪我思存的,很虐,很悲,还有梅子黄时雨,笙离,叶萱,还有安宁的《温暖的玄》 小...
两个以名人的名字命名的风景名胜... 两个以名人的名字命名的风景名胜?快太白楼,李白。尚志公园,赵尚志。
幼儿教育的代表人物及其著作 幼儿教育的代表人物及其著作卡尔威特的《卡尔威特的教育》,小卡尔威特,他儿子成了天才后写的《小卡尔威特...
海贼王中为什么说路飞打凯多靠霸... 海贼王中为什么说路飞打凯多靠霸气升级?凯多是靠霸气升级吗?因为之前刚到时确实打不过人家因为路飞的实力...
运气不好拜财神有用吗运气不好拜... 运气不好拜财神有用吗运气不好拜财神有没有用1、运气不好拜财神有用。2、拜财神上香前先点蜡烛,照亮人神...