30.面向对象关联知识点-空类型所占字节数
30.1 知识点
知识回顾:C++ 基础中的结构体的内存占用相关知识点
使用
sizeof
可以有效地获取结构体的大小,包括成员变量和填充字节(为了满足特定的字节对齐要求)什么是字节对齐(内存对齐)
在计算机内存中,数据结构的起始地址需要满足特定的规则,以提高内存访问的效率
字节对齐确保数据在内存中的存储位置符合其类型的对齐要求,从而避免因不对齐访问而导致的性能损失或潜在的硬件异常
每种数据类型通常都有一个对齐要求
int
可能要求 4 字节对齐double
可能要求 8 字节对齐char
通常要求 1 字节对齐
为了满足对齐要求,编译器会在结构体成员之间插入填充字节。这些填充字节不会被我们直接访问,但它们确保每个成员的地址都是对齐的
合理的字节对齐可以提高 CPU 访问内存的效率。现代 CPU 通常会在特定的字节边界上进行读取和写入操作,对齐访问通常比非对齐访问快得多
对于结构体大小而言
结构体的字节占用大小通常是其最大成员对齐要求的倍数
编译器可能会在成员之间插入填充字节,以满足对齐要求。这些填充字节通常会放在成员之间或结构体末尾
规则:
- 每个成员变量的起始地址必须是其类型大小的倍数
- 整个结构体的大小是最大成员对齐要求的倍数
- 结构体嵌套时,外部结构体需要考虑内部结构体成员的最大对齐数
内存对齐的目的是提高内存访问性能,但会带来内存浪费的问题
结构体中变量顺序会影响内存占用大小,应按照以下规则排列变量:
- 按大小降序排列成员,占用空间大的类型放在前面
- 减少不必要的成员变量
- 使用更小的数据类型
- 等等…(其他优化手段以后再学)
类对象的内存占用
- 类对象的内存占用计算规则与结构体一致
- 同样可以通过
sizeof
获取其占用的字节数 - 注意:包含虚函数时,需要加上虚函数表指针的大小
空类型所占字节数
如果一个类型(类或结构体)中不声明任何成员变量,那么它相当于是一个空类型
使用
sizeof
计算其占用内存空间,将会得到 1 个字节- 主要原因:确保对象具有唯一地址
- C++ 中的对象需要内存地址,即使对象没有任何成员变量,它仍然需要一个唯一的内存地址
注意:当空类型被继承时,只要子类中有成员变量,则会优化掉空类型的空间,不会额外增加 1 字节
#pragma once
class X
{
};
#pragma once
#include "X.h"
class Y : public X
{
public:
int i;
};
#pragma once
class A
{
public:
virtual void Test3()
{
}
void Test()
{
}
void Test2()
{
}
};
#pragma once
#include "A.h"
class B : public A
{
public:
int i = 0;
};
cout << sizeof(X) << endl; // 1 X 没有任何成员,仍然需要一个唯一的内存地址
cout << sizeof(Y) << endl; // 4 Y 有一个 int 成员,占用 4 字节
cout << sizeof(A) << endl; // 8 A 存在虚函数,默认会有虚函数表指针
cout << sizeof(B) << endl; // 16 B 有一个 int 成员(4 字节),加上虚函数表指针(8 字节),对齐后取最大对齐数为 16
A a;
A a2;
cout << &a << endl; // 例如:0000008514B6F918
cout << &a2 << endl; // 例如:0000008514B6F938
输出示例
sizeof(X)
输出1
sizeof(Y)
输出4
sizeof(A)
输出8
sizeof(B)
输出16
- 两个空对象的地址不同,证明它们各自占有唯一地址
30.2 知识点代码
X.h
#pragma once
class X
{
};
Y.h
#pragma once
#include "X.h"
class Y :
public X
{
public:
int i;
};
A.h
#pragma once
class A
{
public:
virtual void Test3()
{
}
void Test()
{
}
void Test2()
{
}
};
B.h
#pragma once
#include "A.h"
class B :
public A
{
public:
int i = 0;
};
Lesson30_面向对象关联知识点_空类型所占字节数.cpp
#include <iostream>
using namespace std;
#include "X.h"
#include "Y.h"
#include "A.h"
#include "B.h"
int main()
{
#pragma region 知识回顾 C++基础中的结构体的内存占用相关知识点
//使用 sizeof 可以有效地获取结构体的大小,包括成员变量和填充字节(为了满足特定的字节对齐要求)
//什么是字节对齐(内存对齐)
// 在计算机内存中,数据结构的起始地址需要满足特定的规则
// 以提高内存访问的效率
// 字节对齐确保数据在内存中的存储位置符合其类型的对齐要求
// 从而避免因不对齐访问而导致的性能损失或潜在的硬件异常
//
// 每种数据类型通常都有一个对齐要求,即它在内存中存储时必须位于某个特定的字节边界上
// 比如:
// int可能要求4字节对齐
// double可能要求8字节对齐
// char通常要求1字节对齐
// 为了满足对齐要求,编译器会在结构体成员之间插入填充字节。
// 这些填充字节不会被我们直接访问,但它们确保每个成员的地址都是对齐的
// 合理的字节对齐可以提高 CPU 访问内存的效率。
// 现代 CPU 通常会在特定的字节边界上进行读取和写入操作,对齐访问通常比非对齐访问快得多
//
//对于结构体大小而言
//它的字节占用大小通常是其最大成员对齐要求的倍数
//编译器可能会在成员之间插入填充字节,以满足对齐要求
//填充字节通常会放在结构体的成员之间或结构体的末尾
//规则:
//1.每个成员变量的起始地址必须是其类型大小的倍数
//2.整个结构体的大小是最大成员对齐要求的倍数
//3.结构体嵌套时,外部结构体需要考虑内部结构体成员变量的字节最大数
//内存对齐的目的是提高内存的访问性能
//但是会带来浪费内存的问题
//结构体中变量类型的不同顺序会带来不同的内存占用大小
//因此我们应该按照以下的规则去排列变量
//1.按照大小降序排列成员,占用越大空间的变量类型放在前面
//2.减少不必要的成员变量
//3.使用更小的数据类型
//等等(有些手段以后再学)
#pragma endregion
#pragma region 知识点一 类对象的内存占用
//类对象的内存占用计算规则和结构体一致
//同样可以通过sizeof获取其占用的字节数
//注意:
// 包含虚函数时,需要加上虚函数表指针的大小
#pragma endregion
#pragma region 知识点二 空类型所占字节数
//如果一个类型(类或结构体)中不声明任何成员变量
//那么它相当于是一个空类型
//这时我们单独用sizeof计算其占用内存空间,将会得到1个字节
//主要原因:
//确保对象具有唯一地址
// C++中的对象需要内存地址,即使对象没有任何成员变量,它仍然需要一个唯一的内存地址
//注意:
//当空类型被继承时,只要子类中有成员变量,则会优化掉空类型的空间,不会加1
cout << sizeof(X) << endl;//1 X没有任何成员 仍然需要一个唯一的内存地址 1个字节
cout << sizeof(Y) << endl;//4 Y有一个int成员 4个字节
cout << sizeof(A) << endl;//8 A存在虚函数 默认会有虚函数表指针
cout << sizeof(B) << endl;//16 B有一个int成员4个字节 B虚函数指针8字节因为内存对齐取最大 所以是16
A a;
A a2;
cout << &a << endl;//0000008514B6F918
cout << &a2 << endl;//0000008514B6F938
#pragma endregion
}
30.3 练习题
以下两个结构体 A 和 B 在内存占用上是否一样?为什么?
struct Empty {};
struct A {
Empty e;
int x;
};
struct B : Empty {
int x;
};
参考答案: 不相同
原因分析:
Empty
是一个空类型,但 作为成员变量 时,仍然会占用 1 个字节的空间。这是为了确保每个对象都有唯一的地址,防止多个成员变量的地址重叠,违反了 C++ 对象模型的基本原则。结构体 A 的内存占用为 8 字节:
Empty e
占 1 字节;int x
占 4 字节;- 为满足内存对齐的要求,整体需要补齐为 8 字节(通常 4 字节对齐);
- 所以最终
sizeof(A) = 8
。
结构体 B 的内存占用为 4 字节:
Empty
被作为基类继承,由于空基类优化(Empty Base Optimization,EBO),不会占用空间;int x
占 4 字节;- 所以
sizeof(B) = 4
。
结论:
结构体 A 和 B 在内存占用上不一样,A 为 8 字节,B 为 4 字节。
这种差异主要是因为结构体成员需要保留空间,而空基类可以通过编译器优化掉空间。
如下代码中,输出结果是什么?解释你的答案。
class Empty {};
int main() {
Empty arr[10];
std::cout << sizeof(arr) << std::endl;
}
参考答案: 输出结果为 10
原因分析:
Empty
是一个空类,但在 C++ 中,一个对象必须拥有唯一的地址。为了满足这个要求,空类型对象的大小被规定为 1 字节。sizeof(Empty)
= 1
arr[10]
是一个Empty
类型的数组,长度为 10。因此
sizeof(arr)
=10 * sizeof(Empty)
=10 * 1
= 10
结论:
即使是空类类型数组,也必须分配每个元素独立的地址,最终 sizeof(arr)
为 10
。这也是 C++ 对内存模型严谨定义的体现。
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com