百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

C++数据类型对齐、对齐规则

wptr33 2025-05-27 18:08 19 浏览

C++元素对齐探讨

探讨内容与目标

探讨C++元素的对齐方式,并以sizeof()类型返回值,测试是否理解正确。

重点留意后面的结果分析,有经验总结哦!!!

操作环境

测试环境为Windows 10,Visual Studio 2015.

一些基本知识

各元素类型的sizeof,以及其相应的对齐大小需要掌握。

data alignment 数据对齐

data padding 数据填充

区分3个概念:结构体对齐大小,基本元素对齐大小,预设对齐大小

结构体对齐大小,是align(A)之后得到的大小,是本次讨论重点研究的对象,也是sizeof()以及padding的最终因素。本次讨论一般设为x。

基本元素对齐大小,就是int之类的默认对齐大小,一般而言,就是基本元素的字节数。它们以一个整体出现的话,就不受#pragma pack()指令的影响。如果,基本元素在结构体内的话,受#pragma pack()影响。本次讨论,谈及基本元素对齐大小都是指的前者。本次讨论一般设为y。

预设对齐大小,就是#pragma pack()指令的预设值,一般为1,2,4,8,16。本次讨论一般设为z。

本次研究的主题,其实质就是x = f(y1, ... ,yn, z)的函数关系。

一些函数介绍

align() //计算元素类型对齐大小

align()用于计算某个元素类型的对齐大小x,注意不是元素。

比如:

struct A
{
 char a;
 int b;
 short c;
};
cout << alignof(A) << endl; //ok 4
A a;
cout<<alignof(a)<<endl; //error

#pragma pack() //设置预设对齐大小

#pragma pack()是编译器的预处理命令

可用的预设对齐大小是1、2、4、8(默认)、16;

相应的设置方法

项目->属性->配置属性->c/c++->代码生成->结构成员对齐->进行设置

调用#pragma pack(n)。eg:#pragma pack(4); //设置预设的对齐大小为4

#pragma pack() //设置预设对齐大小为默认值, 一般为8
#pragma pack(4) //设置预设对齐大小为4

1.offsetof(type, var) //求type内的var的起始地址相对于type的起始地址的偏移量

struct A
{
 char a;
 int b;
 short c;
};
//计算结构体元素a的起始地址相对于A的起始地址的偏移量
cout << offsetof(A, a) << endl; //ok 0
cout << offsetof(A, b) << endl; //ok 4

测试代码

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct A1
{
 char c;
};
struct A2
{
 char c1;
 char c2;
 int a;
};


struct A3
{
 char c1;
 int a;
 char c2;
};

struct A4
{

};

struct A5
{
 char c;
 double d;
 char c1[7];
};

//设定预设对齐大小为2
#pragma pack(2)
struct A6
{
 char a;
 int b;
 //short c;
};

struct A7
{
 char b;
};

#pragma pack(4)
struct A8
{
 A6 c1;
 A7 d1;
};

//恢复默认预设大小--8
#pragma pack()
struct A9
{
 char c;
 struct A9_inner
 {
 short ss;
 }a9in;
 double d;
};

struct A10
{
 char c;
 struct A10_inner
 {
 short ss;
 double dd;
 }a10in;
 double d;
};

//即使声明预设对齐大小为2,int位域还是进行32位扩展,而不是16位
#pragma pack(2)
struct A11
{
 short s;
 char c;
 int a1 : 1;
 int a2 : 4;
 int a3 : 7;
};
#pragma pack()

struct A12
{
 short s;
 char c;
 long long a1 : 1;
 long long a2 : 4;
 long long a3 : 7;
};

//位域超过一个整体时,首次超过的那个位域字段需要考虑对齐
struct A13
{
 unsigned a : 19;
 unsigned b : 11;
 //19+11+4 = 34 > 32 因此,c应该在下一个int里面
 unsigned c : 4;
 //同理,4 + 29 = 33 > 32 存在偏移
 unsigned d : 29;
 char index;
};



class Base
{

};

class C1
{
 char c;
 Base b;
};

class C2
{
 char c;
 Base b;
 int a;
};

class C3 :public Base
{
 char c;
};

class Base1
{

 char c;
 int a;
};

class C4 :public Base1
{
 char c1;
 double d;
};

class C41
{
 Base1 b;
 char c1;
 double d;
};

class C5 :public Base1
{
 char c1;
 int a;
 double d;
};

class C51
{

 char c1;
 Base1 b;
 int a;
 double d;
};

class Base2
{
public:
 virtual ~Base2(){ }
};

class C6
{
 char c;
 Base2 b;
};

class C7
{
 char c;
 Base2 b;
 int a;
};

class C8 :public Base2
{
 char c;
};

class Base3
{
 char c;
public:
 virtual ~Base3()
 {
 }
};

class C9 :public Base3
{
 char c1;
public:
 virtual void print(){ }
};

class Base4
{
public:
 virtual void print() = 0;
};

class C10 :public Base4
{
 char c;
public:
 virtual void print()
 {
 }
};

class Base5
{
 char c;
public:
 virtual void print() = 0;
};

class C11 :public Base5
{
 char c1;
public:
 virtual void print()
 {
 }
};

//测试开始标记
static void printStart()
{
 cout << "--------------------- test ---------------------\n";
}
//测试退出标记
static void printEndLine()
{
 cout << "--------------------- end ---------------------\n";
}
//测试基本元素的字节数和对齐大小
static void testBaseElement()
{
 printStart();
 cout << "char: " << sizeof(char) << "---" << alignof(char) << endl;
 cout << "int: " << sizeof(int) << "---" << alignof(int) << endl;
 cout << "short: " << sizeof(short) << "---" << alignof(short) << endl;
 cout << "long: " << sizeof(long) << "---" << alignof(long) << endl;
#pragma pack(2)
 //基本类型为一个整体时,不受预设对齐大小的约束
 cout << "long long: " << sizeof(long long) << "---" << alignof(long long) << endl;
#pragma pack()
 cout << "float: " << sizeof(float) << "---" << alignof(float) << endl;
 cout << "double: " << sizeof(double) << "---" << alignof(double) << endl;
 cout << "long double: " << sizeof(long double) << "---" << alignof(long double) << endl;
 void* p = NULL;
 cout << "pointer: " << sizeof(p) << "---" << alignof(char*) << endl;
 printEndLine();
}
//测试struct相关的字节数和对齐大小
static void testOnlyWithStruct()
{
 printStart();
 cout << "struct A1: " << sizeof(A1) << "---" << alignof(A1) << endl;
 cout << "struct A2: " << sizeof(A2) << "---" << alignof(A2) << endl;
 cout << "struct A3: " << sizeof(A3) << "---" << alignof(A3) << endl;
 cout << "struct A4: " << sizeof(A4) << "---" << alignof(A4) << endl;
 cout << "struct A5: " << sizeof(A5) << "---" << alignof(A5) << endl;
 cout << "struct A6: " << sizeof(A6) << "---" << alignof(A6) << endl;
 cout << "struct A6.b: " << sizeof(A6::b) << "---" << offsetof(A6, b) << endl;
 cout << "struct A7: " << sizeof(A7) << "---" << alignof(A7) << endl;
 cout << "struct A8: " << sizeof(A8) << "---" << alignof(A8) << endl;
 cout << "struct A9: " << sizeof(A9) << "---" << alignof(A9) << endl;
 cout << "struct A10: " << sizeof(A10) << "---" << alignof(A10) << endl;
 cout << "struct A11: " << sizeof(A11) << "---" << alignof(A11) << endl;
 cout << "struct A12: " << sizeof(A12) << "---" << alignof(A12) << endl;
 cout << "struct A13: " << sizeof(A13) << "---" << alignof(A13) << endl;
 printEndLine();
}
//测试不存在虚函数的类的字节数和对齐大小
static void testWithClassNonVirtual()
{
 cout << "class Base: " << sizeof(Base) << "---" << alignof(Base) << endl;
 cout << "class C1: " << sizeof(C1) << "---" << alignof(C1) << endl;
 cout << "class C2: " << sizeof(C2) << "---" << alignof(C2) << endl;
 cout << "class C3: " << sizeof(C3) << "---" << alignof(C3) << endl;
 cout << "class Base1: " << sizeof(Base1) << "---" << alignof(Base1) << endl;
 cout << "class C4: " << sizeof(C4) << "---" << alignof(C4) << endl;
 cout << "class C41: " << sizeof(C41) << "---" << alignof(C41) << endl;
 cout << "class C5: " << sizeof(C5) << "---" << alignof(C5) << endl;
 cout << "class C51: " << sizeof(C51) << "---" << alignof(C51) << endl;
}
//测试不存在虚函数的类的字节数和对齐大小
static void testWithClassVirtual()
{
 cout << "class Base2: " << sizeof(Base2) << "---" << alignof(Base2) << endl;
 cout << "class C6: " << sizeof(C6) << "---" << alignof(C6) << endl;
 cout << "class C7: " << sizeof(C7) << "---" << alignof(C7) << endl;
 cout << "class C8: " << sizeof(C8) << "---" << alignof(C8) << endl;
 cout << "class Base3: " << sizeof(Base3) << "---" << alignof(Base3) << endl;
 cout << "class C9: " << sizeof(C9) << "---" << alignof(C9) << endl;
 //alignof(Base4),alignof(Base5) 会报错, 不能实例化抽象类
 cout << "class Base4: " << sizeof(Base4) << "---" << endl;
 cout << "class C10: " << sizeof(C10) << "---" << alignof(C10) << endl;
 cout << "class Base5: " << sizeof(Base5) << "---" << endl;
 cout << "class C11: " << sizeof(C11) << "---" << alignof(C11) << endl;

}
//测试类相关的字节数和对齐大小,主要涉及继承和虚函数
static void testWithClass()
{
 printStart();
 testWithClassNonVirtual();
 cout << "\n------------------------------------------------\n" << endl;
 testWithClassVirtual();
 printEndLine();
}

struct D1
{
 int size;
 //使用了非标准扩展;结构/联合中的零大小数组
 //这是C99的柔性数组Flexible Array,也就是变长数组
 char data[0]; //或者char data[]; 
};

struct D2
{
 //使用了非标准扩展;结构/联合中的零大小数组
 char data[0];
};

//不能直接定义一个数组大小为0的数组
//char data[0];
//测试柔性数组
static void testOther()
{
 printStart();
 cout << "class D1: " << sizeof(D1) << "---" << alignof(D1) << endl;
 cout << "class D2: " << sizeof(D2) << "---" << alignof(D2) << endl;
 printEndLine();
}

int main()
{
 freopen("out64.txt", "w", stdout);
 testBaseElement();
 cout << endl;
 testOnlyWithStruct();
 cout << endl;
 testWithClass();
 cout << endl;
 testOther();
 return 0;
}

测试结果

  • 32位编译器结果
--------------------- test ---------------------
char: 1---1
int: 4---4
short: 2---2
long: 4---4
long long: 8---8
float: 4---4
double: 8---8
long double: 8---8
pointer: 8---8
--------------------- end ---------------------
--------------------- test ---------------------
struct A1: 1---1
struct A2: 8---4
struct A3: 12---4
struct A4: 1---1
struct A5: 24---8
struct A6: 6---2
struct A6.b: 4---2
struct A7: 1---1
struct A8: 8---2
struct A9: 16---8
struct A10: 32---8
struct A11: 8---2
struct A12: 16---8
struct A13: 16---4
--------------------- end ---------------------
--------------------- test ---------------------
class Base: 1---1
class C1: 2---1
class C2: 8---4
class C3: 1---1
class Base1: 8---4
class C4: 24---8
class C41: 24---8
class C5: 24---8
class C51: 24---8
------------------------------------------------
class Base2: 8---8
class C6: 16---8
class C7: 24---8
class C8: 16---8
class Base3: 16---8
class C9: 24---8
class Base4: 8---
class C10: 16---8
class Base5: 16---
class C11: 24---8
--------------------- end ---------------------
--------------------- test ---------------------
class D1: 4---4
class D2: 1---1
--------------------- end ---------------------
  • 64位编译器
--------------------- test ---------------------
char: 1---1
int: 4---4
short: 2---2
long: 4---4
long long: 8---8
float: 4---4
double: 8---8
long double: 8---8
pointer: 8---8
--------------------- end ---------------------
--------------------- test ---------------------
struct A1: 1---1
struct A2: 8---4
struct A3: 12---4
struct A4: 1---1
struct A5: 24---8
struct A6: 6---2
struct A6.b: 4---2
struct A7: 1---1
struct A8: 8---2
struct A9: 16---8
struct A10: 32---8
struct A11: 8---2
struct A12: 16---8
struct A13: 16---4
--------------------- end ---------------------
--------------------- test ---------------------
class Base: 1---1
class C1: 2---1
class C2: 8---4
class C3: 1---1
class Base1: 8---4
class C4: 24---8
class C41: 24---8
class C5: 24---8
class C51: 24---8
------------------------------------------------
class Base2: 8---8
class C6: 16---8
class C7: 24---8
class C8: 16---8
class Base3: 16---8
class C9: 24---8
class Base4: 8---
class C10: 16---8
class Base5: 16---
class C11: 24---8
--------------------- end ---------------------
--------------------- test ---------------------
class D1: 4---4
class D2: 1---1
--------------------- end ---------------------

结果分析

分析维度

  1. 32位编译和64位编译器
  2. 编译器自己的对齐命令 #pragma pack()的影响
  3. C关注于结构体,C++专注于类继承和虚函数
  4. 位域的影响
  5. 内置结构体(或者类)对象的影响。

经验性总结

  • 对于32位编译器和64位编译器

除了指针大小和对齐方式不同,其他的都与32位遵循一样的规则

#pragma pack()

再次NOTE:这里只是预设的对齐大小,结构体不一定按这个大小进行对齐。

  • 结论1:

假设,无#pragma pack()情况下某结构体的对齐大小为x1;

设,存在#pragma pack(z)指令,某结构体的对齐大小为x; 则 x = min(x1, z);

证明:

已知,无#pragma()情况下某结构体的对齐大小为x1;预设对齐大小为z

当x1<=z, x = x1; //案例A1 –也就是说预设对齐大小,不起任何作用,平时经常遇到的就是这个情况

当z

// align(A) = max(y1, y2, y3) = 4
struct A
{
 char a; //y1 = 1
 int b; //y2 = 4
 short c; //y3 = 2
};

对于其中,含有内嵌结构,则也类似

//易知: align(A6) = 4 align(A7) = 1
struct A6
{
 char a;
 int b;
 short c;
};
struct A7
{
 char b;
};
// align(A8) = max(y1, y2) = 4
struct A8
{
 A6 c1; //y1 = 4
 A7 d1; //y2 = 1
};

结论2:某个元素a(不管是结构体还是普通元素)的sizeof()大小一定是align(a)的整数倍。

注意很多人经常有个错误的言论,就是结构体的大小一定是数组中的最长字段的整数倍。

//注意这里sizeof(A)=6, 不是4(= sizeof(int))的倍数,但确实是2 (=align(A))的倍数。
#pragma pack(2)
struct A
{
 char a;
 int b;
};

————————————————
版权声明:本文为CSDN博主「lizi_stdio」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lizi_stdio/article/details/77203335

偏移量offset

结论3:结构体的内部元素的偏移量一定是min(其基本元素对齐大小, 预设对齐大小)的整数倍。

结构体的内部元素的偏移量是其基本元素对齐大小的整数倍。//这是错的

因此,算偏移量的时候,都是将地址按min(其基本元素对齐大小, 预设对齐大小)大小,想象成一段一段的。(这种做法比较慢,但是适合不熟悉对齐规则的童鞋)

struct A
{
 char a;
 int b; //b的偏移量为4,不是1. 4是4的倍数
};
//----------------------------
#pragma pack(2)
struct A
{
 char a;
 int b; //b的偏移量为2
};

字符填充padding

为了保证结论2,有时候需要做必要的填充,

struct A
{
 int a;
 char b; //b的偏移量为4 4是1的倍数
};
//表面上字符的大小是5,但不是4(=align(A))的倍数,需要做字符填充,类似如下结构体
struct A
{
 int a;
 char b; //b的偏移量为4 4是1的倍数
 char c[3];
};

关于位域的影响

结论4:不受结构体的真正对齐大小影响,将相应的位数按前面的类型说明符补齐。比如测试案例A11, A12

对于位域之和超过基本元素对齐大小的时候。从第一个开始超过的位域进行偏移,确保它的地址是min(其基本元素对齐大小, 预设对齐大小)大小。 比如测试案例A13

然后把它当成一个整的类型看就行。

struct A
{
 short s;
 int a1 : 4; //补齐int后面的28位,完全等效于一个int整体 
};

C++相关部分 (类跟结构等效)

结论5:对于一个空类,其默认sizeof()为1,align()为1;对于一个包含虚函数的空类,其默认sizeof()为4,align()为4

//详见测试案例Base, Base2

结论6:对于继承空基类的话,基类默认不占用空间;如果是包含成员对象的话,则该对象的大小是1。

其他部分的讨论,看看测试案例,看看C部分就行。

//详见测试案例C1, C3

参考资料

失传的C结构体打包技艺 里面主要介绍关于C语言方面的对齐知识

如何理解struct的内存对齐 虽然里面有些错的,但还是有参考价值

Wiki Data Structure Alignment 维基关于对齐的解释,里面的术语很专业

msdn #pragma pack()介绍

c/c++数据对齐

写作之外

如果有什么好的建议,或者有什么测试案例可以添加的欢迎留言交流。如果这篇写作对你有所帮助,请点帮忙点个赞,让我知道我的写作确实帮助到别人。如果没有帮助的话,请忽视前一句。欢迎指出不足。

最后,如果你想学C/C++可以私信小编“01”获取素材资料以及开发工具和听课权限哦!

相关推荐

oracle数据导入导出_oracle数据导入导出工具

关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...

继续学习Python中的while true/break语句

上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个...

python continue和break的区别_python中break语句和continue语句的区别

python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...

简单学Python——关键字6——break和continue

Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...

2-1,0基础学Python之 break退出循环、 continue继续循环 多重循

用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...

Python 中 break 和 continue 傻傻分不清

大家好啊,我是大田。...

python中的流程控制语句:continue、break 和 return使用方法

Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...

L017:continue和break - 教程文案

continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...

作为前端开发者,你都经历过怎样的面试?

已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...

面试被问 const 是否不可变?这样回答才显功底

作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...

2023金九银十必看前端面试题!2w字精品!

导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。...

前端面试总结_前端面试题整理

记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...

由浅入深,66条JavaScript面试知识点(七)

作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录...

2024前端面试真题之—VUE篇_前端面试题vue2020及答案

添加图片注释,不超过140字(可选)...

今年最常见的前端面试题,你会做几道?

在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...