Skip to content

Commit

Permalink
Improve c++_object_model
Browse files Browse the repository at this point in the history
  • Loading branch information
selfboot committed Apr 2, 2024
1 parent a8c4be6 commit a291d00
Show file tree
Hide file tree
Showing 3 changed files with 12 additions and 34 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
46 changes: 12 additions & 34 deletions source/_drafts/c-object-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,23 +75,17 @@ int main() {

用 GDB 打印 temp 对象以及成员变量的地址,发现内存布局和前面不带方法的完全一样。整个对象 size 依然是 16,a 和 b 的内存地址分布也是一致的。那么**新增加的成员方法存储在什么位置**?成员方法中又是**如何拿到成员变量的地址**呢?

### 成员方法的内存布局
### 成员方法内存布局

可以在 GDB 里面打印下成员方法的地址,如下图所示。

![成员方法的存储地址](https://slefboot-1251736664.file.myqcloud.com/20240329_c++_object_model_method_addr.png)

回忆下 Linux 中进程的内存布局,其中**文本段(也叫代码段)是存储程序执行代码的内存区域**,通常是只读的,以防止程序在运行时意外或恶意修改其执行代码。前面 setB 方法地址 `0x5555555551d2` 是否位于程序的文本段内呢?打印下进程的内存布局,如下
回忆下 Linux 中进程的内存布局,其中**文本段(也叫代码段)是存储程序执行代码的内存区域**,通常是只读的,以防止程序在运行时意外或恶意修改其执行代码。这里 setB 方法地址 `0x5555555551d2` 就是位于程序的文本段内,可以在 GDB 中用 `info target` 验证一下

```
$ cat /proc/2325196/maps
555555554000-555555555000 r--p 00000000 fe:01 554821 /root/demo/object_memory/basic_method
555555555000-555555556000 r-xp 00001000 fe:01 554821 /root/demo/object_memory/basic_method
555555556000-555555557000 r--p 00002000 fe:01 554821 /root/demo/object_memory/basic_method
...
```
![成员方法存储在 text 段](https://slefboot-1251736664.file.myqcloud.com/20240401_c++_object_model_method_gdb_target.png)

其中 `555555555000-555555556000` 位于可执行文件 `basic_method` 映射的范围内,setB 的地址落在这个区间内。这里权限标记(r-xp)中的 x 明确表示这个段是可执行,可执行的映射通常包含代码段。至此前面第一个问题有了答案,成员方法存储在进程的文本段,添加成员方法不会改变类实例对象的内存布局大小,**它们也不占用对象实例的内存空间**
其中 .text 段的地址范围是 `0x0000555555555060 - 0x0000555555555251`,setB 刚好在这个范围内。至此前面第一个问题有了答案,成员方法存储在进程的文本段,添加成员方法不会改变类实例对象的内存布局大小,**它们也不占用对象实例的内存空间**

### 成员变量寻址

Expand Down Expand Up @@ -157,7 +151,7 @@ public:

void setB(double value) {
b = value; // 直接访问成员变量b
secret(b);
secret(b);
}
private:
int c;
Expand All @@ -178,7 +172,7 @@ int main() {
编译之后,通过 GDB,可以打印出所有成员变量的地址,发现这里**私有变量的内存布局并没有什么特殊地方,也是依次顺序存储在对象**中。私有的方法也没有特殊地方,一样存储在文本段。整体布局如下如:
![]()
![带 private 成员的内存布局](https://slefboot-1251736664.file.myqcloud.com/20240401_c++_object_model_method_private.png)
那么 **private 是在运行期还是编译期进行可见性控制的**呢?首先编译期肯定是有保护的,这个很容易验证,我们无法直接访问 temp.c ,或者调用 secret 方法,因为直接会编译出错。
Expand Down Expand Up @@ -218,27 +212,15 @@ int main() {
### 静态成员
静态成员变量在类的所有实例之间共享,**不管你创建了多少个类的对象,静态成员变量只有一份数据**。
静态成员变量在类的所有实例之间共享,**不管你创建了多少个类的对象,静态成员变量只有一份数据**。静态成员变量的生命周期从它们被定义的时刻开始,直到程序结束。静态成员方法不依赖于类的任何实例来执行,主要用在工厂方法、单例模式的实例获取方法、或其他与类的特定实例无关的工具函数。
下面以一个具体的例子,来看看静态成员变量和静态成员方法的内存布局以及实现特点。
```c++
#include <iostream>
class Basic {
public:
int a;
double b;
void setB(double value) {
b = value; // 直接访问成员变量b
secret(b);
}
private:
int c;
double d;
void secret(int temp) {
d = temp + c;
}
// ...
public:
static float alias;
static void show() {
Expand All @@ -248,21 +230,17 @@ public:
float Basic::alias = 0.233;
int main() {
Basic temp;
temp.a = 10;
temp.setB(3.14);
// ...
temp.show();
return 0;
}
```


## 继承类的内存布局
## 类继承的内存布局

### 不带虚函数的继承
### 带有虚函数的继承


## 地址空间布局随机化

前面的例子中,如果用 GDB 多次运行程序,对象的**虚拟内存地址每次都一样**,这是为什么呢?
Expand Down

0 comments on commit a291d00

Please sign in to comment.