【C++深度解析】35、C++对象模型(上:对象内存布局)

1 对象内存布局

class 是一种特殊的 struct

  • class 与 struct 遵循相同的内存对齐规则
  • class 中的成员函数与成员变量分开存放(每个对象有独立的成员变量,所有对象共享类中的成员函数)

编程实验:对象内存布局

//  35-1.cpp
#include<iostream>
using namespace std;
class A
{
    int i;
    int j;
    char c;
    double d;
public:
    void print()
    {
        cout << "i = " << i << ", "
        << "j = " << j << ", "
        << "c = " << c << ", "
        << "d = " << d << endl;
    }
};
struct B
{
    int i;
    int j;
    char c;
    double d;
};
int main()
{
    A a;
    cout << "sizeof(A) = " << sizeof(A) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;
    
    a.print();
    B* p = reinterpret_cast<B*>(&a);		// 用于指针类型间的强制转换
    p->i = 1;
    p->j = 2;
    p->c = 'c';
    p->d = 3.5;
    a.print();
    return 0;
}
  • 类中成员函数与成员变量分开存放,使用 sizeof 求解的是成员变量的内存大小。class 与 struct 遵循相同的内存对齐规则。根据内存对齐规则,A 和 B 的大小都是 24 字节。
  • 将结构体 B 类型的指针指向类对象 a,重新解释内存中的数据。类中的数据和结构体中数据的分布是完全一样的,所以我们可以通过结构体指针修改类对象中的数据。

编译运行:

$ g++ 35-1.cpp -o 35-1
$ ./35-1
sizeof(A) = 24
sizeof(B) = 24
i = -879428416, j = 21866, c = �, d = 6.95316e-310
i = 1, j = 2, c = c, d = 3.5

运行时的对象退化为结构体的形式,可以通过内存地址直接访问成员变量,访问权限关键字在运行时失效,只在编译时有效。

2 调用成员函数本质

  • 类的成员函数位于代码段中
  • 调用成员函数时对象地址作为参数隐式传递,成员函数通过对象地址访问成员变量
  • C++ 语法规则隐藏了对象地址的传递过程

下面我们通过编程实验证明,当调用成员函数时对象地址作为参数隐式传递,成员函数通过对象地址访问成员变量

先看一段简单的代码

// 35-2.cpp
#include<iostream>
using namespace std;
class Demo
{
public:
    Demo(int i, int j) : mi(i), mj(j) {}
    int getI() { return mi; }
    int getJ() { return mj; }
    int add(int value)
    {
        return mi + mj + value;
    }
private:
    int mi;
    int mj;
};
int main()
{
    Demo d(1, 2);
    cout << "sizeof(d) = " << sizeof(d) << endl;
    cout << "d.getI() = " << d.getI() << endl;
    cout << "d.getJ() = " << d.getJ() << endl;
    cout << "d.add(3) = " << d.add(3) << endl;
    return 0;
}

上面的代码很简单,就是对象访问成员函数,由于隐藏了对象地址的传递过程,我们看不到调用成员函数时隐式传递对象地址。

下面我们用 C 语言模拟上面的过程,显示的传递对象地址,以明白对象调用成员函数的过程。

// ClassDemo.h
#ifndef _CLASSDEMO_H_
#define _CLASSDEMO_H_
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
#endif
// ClassDemo.c
#include<malloc.h>
#include"ClassDemo.h"
struct ClassDemo
{
    int mi;
    int mj;
};
Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
    if (ret != NULL)
    {
        ret->mi = i;
        ret->mj = j;
    }
    return ret;
}
int Demo_GetI(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi;
}
int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mj;
}
int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi + obj->mj + value;
}
void Demo_Free(Demo* pThis)
{
    free(pThis);
}
// 35-3.c
#include<stdio.h>
#include"ClassDemo.h"
int main()
{
    Demo* d = Demo_Create(1, 2);
    printf("d.mi = %d\n", Demo_GetI(d));
    printf("d.mj = %d\n", Demo_GetJ(d));
    printf("Add(3) = %d\n", Demo_Add(d, 3));
    Demo_Free(d);
    return 0;
}

函数 Demo_Create、Demo_GetI、Demo_GetJ、Demo_Add 和 Demo_Free 的参数是void 的类型的指针,通过强制类型转换为结构体的指针,就可以操作结构体的成员,这些函数相当于成员函数,这里我们需要显示的传递对象的地址。

C++ 语法规则隐藏了对象地址的传递过程,这里我们使用 C 语言显示的传递对象地址模拟 C++ 对调用成员函数。

$ gcc 35-3.c ClassDemo.c -o 35-3
$ ./35-3
d.mi = 1
d.mj = 2
Add(3) = 6

3 小结

1、C++中类对象在内存布局上和结构体相同
2、成员变量和成员函数分开存放
3、调用成员函数时对象地址作为参数隐式传递

发布了298 篇原创文章 · 获赞 181 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/happyjacob/article/details/104399534