首页 > 系统相关 >C/C++内存对齐原则

C/C++内存对齐原则

时间:2023-02-05 12:11:06浏览次数:50  
标签:struct int C++ char 内存 对齐 字节

C/C++内存对齐

what && why

当用户自定义类型时(struct 或 class),编译器会自动计算该类型占用的字节数。

C/C++ 为什么要内存对齐?我道行太浅,摘抄了网上的一个解释。

为了方便从内存中读取数据。假设没有内存对齐,在内存中存储一个 int 变量 x(占 4 字节),放在了地址 2-5 上。现在要读取 x 到寄存器中,CPU 知道读 int 一次应该读 4 字节,但是不会直接读地址 2-5(为什么不会?我也不知道啊!但是 CPU 有直接读 2-5 地址的功能,但它没有用起来),一次读出来,而是先读 0-3,再读 4-7,丢掉多余的字节。可以看到对齐后少读了一次内存,性能肯定得到提升了(我们知道 C/C++ 是追求极致性能的)。

举例

#include <iostream>

using namespace std;

// #pragma pack (1)

struct Test 
{
    int i1;
    char c;
    int i2;
    double d;
};


int main(int argc, char* argv[])
{
    cout << sizeof(Test) << endl;	// 24
    return 0;
}

如果没有内存对齐,Test 类型的大小应该是 4+1+4+8 = 17 字节,经过对齐后变成了 24 字节。

第 5 行注释就是设置内存对齐基数,取值一般是 1, 2, 4, 8,若该值为 1 则表示不对齐(不信就去掉注释再运行一次,输出肯定是 17)。

内存对齐原则

  1. 整体对齐基数 n:假设默认或通过#pragma pack ()设置的对齐基数是 i(现在机器一般都是 8,旧一些的应该是 4),struct 中“最大”成员所占用的字节数 j,则 n = min(i, j),也就是说这个 struct 类型最终的大小必须是 n 的倍数
  2. 成员对齐基数 k:它的计算方式是 k = min(sizeof(memberType), n)它要求每个成员的 offset 必须是 k 的倍数,第一个成员的 offset 为 0。比如一个 short 成员的 k = min(sizeof(short), n)

可以看出,当 i = 1 时就是不对齐;当 i >= j 时,i 不起作用。

操练一下

假设 n = 8

先进行成员对齐:

#include <iostream>
using namespace std;

struct Test 
{
    int i1;		// offset为0, 占用第0-3字节
    char c;		// 1 < 8, offset是1的倍数, 因此offset为4, 占用第4字节	
    int i2;		// 4 < 8, offset是4的倍数, 因此offset为8, 占用第8-11字节
    double d;	// 8 == 8, offset是8的倍数, 因此offset为16, 占用第16-23字节
    
    // 构造函数
    Test(int ii1, char cc, int ii2, double dd):
    	i1(ii1), c(cc), i2(ii2), d(dd) {}
};

// 来验证一下
int main(int argc, char* argv[])
{
    cout << sizeof(Test) << endl;
    Test *pt = new Test(1, 'a', 2, 1.25);  // 基地址
    unsigned char* ppt = (unsigned char*)pt;   // 强制类型转换, 按字节读 
    for (int i = 0; i < sizeof(Test); ++i) {
        printf("%x ", *(ppt + i));
    }
    cout << endl;
    // 1 0 0 0 61 f0 ad ba 2 0 0 0 d f0 ad ba 0 0 0 0 0 0 f4 3f
    return 0;
}

再进行整体对齐:这个 struct 类型所需字节为 24 字节,恰好是 n 的倍数,无须在尾部额外填充。

内存排列如下图所示:

image-20230205113740178

其中白色格子代表填充,其内容是不确定的。

按十六进制输出:1 0 0 0 61 f0 ad ba 2 0 0 0 d f0 ad ba 0 0 0 0 0 0 f4 3f

  • 可以看到前面 4 字节是 1 0 0 0,是 i1 = 1

  • 第 5 字节是 61,是 'a' 的十六进制 ASCII 码;

  • 然后 6-7 字节是填充的内容,不确定的;

  • 第 8-11 字节是 2 0 0 0,是 i2 = 2

  • 第 12 - 15 字节是填充的内容,不确定的;

  • 第 16-23 字节是 d = 1.25 的底层二进制表示(怎么算的我也忘了好久了,参考神书《CSAPP:深入理解计算机系统》即可找回记忆)。

留下疑问

问:在自定义类型嵌套时,比如 Test1 嵌套正在 Test2 中,此时应该怎么进行内存对齐呢?

struct Test1 
{
    int i1;
    char c;
    int i2;
    double d;
    // 构造函数
    Test1(int ii1, char cc, int ii2, double dd):
    	i1(ii1), c(cc), i2(ii2), d(dd) {}
};

struct Test2
{
    Test1 t1;
    int x;
};

答:先计算 Test1 所占字节大小 sizeof(Test1),然后继续按照上述基本原则计算 Test2 即可。如果是多重嵌套,那就递归找到那个成员全都是基本类型的 struct 开始计算,然后回溯。

问:继承体系中如何进行内存对齐?

struct A
{
    int i;
    char c1;
};


struct B: public A
{
    char c2;
};


struct C: public B
{
    char c3;
};

答:我也不会!我郁闷了,在我 64 位 Windows 操作系统 + gcc8.1.0 和 ubuntu18.04 + gcc7.5.0 上的运行结果都是 12!

但是我参考的一篇博客说,他的结果是 8 或 16!C++ 内存对齐 - tenos - 博客园 (cnblogs.com)

博客里说根据编译器类型拥有两种方式:先继承后对齐先对齐后继承

但是我无论按哪种方式,#pragma pack ()取 4 或 8,排列组合 2*2=4 种可能,我都算不出来 12!但是我能算出 8 和 16!

希望有朋友可以解答我的疑惑,万分感谢。

最后

如果本文对你有帮助,请点个赞吧。

有任何疑问,欢迎评论和我一起讨论。

标签:struct,int,C++,char,内存,对齐,字节
From: https://www.cnblogs.com/zwjason/p/17093145.html

相关文章

  • 5.4节约内存的编程方式
    以图形用户界面(GUI,GraphicalUserInterface)为基础的Windows,可以说是一个巨大的操作系统。Windows的前身是MS-DOS操作系统,最初版本可以在128KB左右的内存上运行,而想要W......
  • 内存和磁盘的亲密关系——5.1不读入内存就无法运行
    1.存储程序方式指的是什么?在存储装置中保存程序,并逐一运行的方式2.通过使用内存来提高磁盘访问速度的机制称为什么?DiskCache(磁盘缓存)3.把磁盘的一部分作为假想内存来......
  • 5.3虚拟内存把磁盘作为部分内存来使用
    接下来说一下虚拟内存虚拟内存是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。通过借助虚拟......
  • 熟练使用有棱有角的内存——4.1内存的物理机制很简单
    1.有十个地址信号引脚的内存IC(集成电路)可以指定的地址范围是多少?答:用二进制数来表示的话是0000000000~1111111111(用十进制数来表示的话是0~1023)2.高级编程语言中的数......
  • 4.2内存的逻辑模型是楼房
    虽然内存的实体是内存IC,不过从程序员的角度来看,也可以把它假想成每层都存储着数据的楼房,并不需要过多地关注内存IC的电源和控制信号等。因此,之后的讲解中我们也同样会使用......
  • 4.4数组是高效使用内存的基础
    数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。指定索引后,就可以对该索引所对应地......
  • Effective Modern C++ 第六章 lambda 表达式
    lambda能做到的,手写也能做到,但是lambda实在是太方便了,以至于对C++开发产生了颠覆性的影响:常用场景STL_if算法族的谓词智能指针的自定义析构器线程API的条件......
  • 【C++ 泛型编程01:模板】函数模板与类模板
    【模板】除了OOP外,C++另一种编程思想称为泛型编程,主要利用的技术就是模板C++提供两种模板机制:函数模板和类模板函数模板函数模板作用建立一个通用函数,其函数......
  • C++函数总结
    1、fabs返回浮点数的绝对值,abs返回整数绝对值2、运算符重载之后排序比写一个排序函数排序要快structnode{intx,y,z;booloperator<(constnode&p)const{retur......
  • C++基础语法资料
     一、C++基本语法变量名是由字母、数字和下划线字符组成,并且开头不能是数字。大小写不同的变量是不同的。变量名不能是关键字,如for、if等都是不能作为变量名的C++基本......