目录
[1. 默认的对齐规则:](#1. 默认的对齐规则:)
[2. 修改默认对齐数:](#2. 修改默认对齐数:)
[3. C++继承场景下的类的大小的计算:](#3. C++继承场景下的类的大小的计算:)
[1. 包含虚函数的类](#1. 包含虚函数的类)
[2. 包含成员函数的类](#2. 包含成员函数的类)
[4. 扩展:](#4. 扩展:)
1. 默认的对齐规则:
-
结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为0的地址处 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
-
对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值
-
Visual Studio 中默认的值为 8
-
Linux中 gcc 没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
-
结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
几个例子与结果:


cpp
struct ss1
{
char _c;
int _i;
int _i2;
};
struct ss2
{
int _i;
char _c;
};
struct ss3
{
int _i;
char _c;
double _d;
};
struct ss4
{
int _i;
char _c;
struct ss3 s;
double _d;
};

2. 修改默认对齐数:
cpp
#pragma pack(1) // 设置默认对齐数为1
#pragma pack() // 取消前面设置的默认对齐数
例子:

cpp
#pragma pack(1)
struct test
{
int _i;
char _c;
};
#pragma pack()
struct ss1
{
char _c;
int _i;
int _i2;
};
int main()
{
printf("%d\n", sizeof(struct test));
printf("%d\n", sizeof(struct ss1));
return 0;
}
在C++中,如果一个结构体(类)中,没有任何成员变量只有成员函数,他的大小是1,因为总要标识这个对象在哪~

3. C++继承场景下的类的大小的计算:
1. 包含虚函数的类
最后说一下在继承的场景下类的大小,我们直接用结果来解释原理~
cpp
struct base
{
int _i;
char _c;
virtual void testFunc()
{ }
};
struct drive : public base
{
double _d;
void testFunc()
{
}
};
struct base1
{
int _i;
char _c;
void testFunc()
{ }
};
struct drive1 : public base1
{
double _d;
};

这里为什么base的大小是12呢?
4 + 1 + 填充3 + vptr(32位下4字节/64位下8字节)
因为类中有一个虚函数,编译器会为对象隐式添加一个vptr虚函数指针,指向这个对象的虚函数表。
2. 包含成员函数的类
cpp
struct test
{
int _i;
char _c;
void testFunc()
{ }
};

这里说明了一个问题,this指针一定是不存储在对象中,而是在栈空间中~。
4. 扩展:
定义一个计算成员变量在类中偏移量的宏
cpp
#define OFFSETOF(TYPE, ELEM) ((int)(&(((TYPE*)0)->ELEM)))
cpp
struct testClass
{
int _i;
char _c;
double _d;
void testFunc()
{ }
};

做法:

比如,_c 是的地址是0x00000004,它的偏移量正好就是4~
疑问:
((TYPE*)0)->ELEM 不会野指针吗~?答案是不会的,现代的编译器十分智能,它如果检测到((TYPE*)0)->ELEM 之前还有一个&取地址符号,那么他就不会去访问这块空间,而仅仅是拿到他的地址。如果只写((TYPE*)0)->ELEM ,那么必然是会野指针的~~!