C++数据类型对齐、对齐规则
wptr33 2025-05-27 18:08 6 浏览
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 ---------------------
结果分析
分析维度
- 32位编译和64位编译器
- 编译器自己的对齐命令 #pragma pack()的影响
- C关注于结构体,C++专注于类继承和虚函数
- 位域的影响
- 内置结构体(或者类)对象的影响。
经验性总结
- 对于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”获取素材资料以及开发工具和听课权限哦!
相关推荐
- C++企业级开发规范指南(c++开发gui)
-
打造高质量、可维护的C++代码标准一、前言C++作为一门功能强大的系统级编程语言,被广泛应用于操作系统、游戏引擎、高性能服务器、数据库系统等领域。知名互联网公司(如Google、Microsoft、腾...
- C++|整型的最值、上溢、下溢、截断、类型提升和转换
-
整数在计算机内以有限字长表示,当超出最值(有限字长)时,需要截断(溢出,求模)操作。不同字长的整型具有不同的值域,混合运算时,需要类型提升和转换。1整形最值在<limit.h>中有整型的...
- C++|漫谈STL细节及内部原理(c++ std stl)
-
1988年,AlexanderStepanov开始进入惠普的PaloAlto实验室工作,在随后的4年中,他从事的是有关磁盘驱动器方面的工作。直到1992年,由于参加并主持了实验室主任BillWo...
- C++11新特性总结 (二)(c++11新特性 pdf)
-
1.范围for语句C++11引入了一种更为简单的for语句,这种for语句可以很方便的遍历容器或其他序列的所有元素vector<int>vec={1,2,3,4,5,6};f...
- C++ STL 漫谈(c++中的stl到底指的什么)
-
标准模板库(StandardTemplateLibrary,STL)是惠普实验室开发的一个函数库和类库。它是由AlexanderStepanov、MengLee和DavidRMusser在...
- C++学习教程_C++语言随到随学_不耽误上班_0基础
-
C++学习教程0基础学C++也可以,空闲时间学习,不耽误上班.2019年C语言新课程已经上线,随到随学,互动性强,效果好!带你征服C++语言,让所有学过和没有学过C++语言的人,或是正准备学习C++语...
- C++遍历vector元素的四种方式(c++ 遍历vector)
-
vector是相同类型对象的集合,集合中的每个对象有个对应的索引。vector常被称为容器(container)。C++中遍历vector的所有元素是相当常用的操作,这里介绍四种方式。1、通过下标访问...
- 一起学习c++11——c++11中的新增的容器
-
c++11新增的容器1:array当时的初衷是希望提供一个在栈上分配的,定长数组,而且可以使用stl中的模板算法。array的用法如下:#include<string>#includ...
- C++编程实战基础篇:一维数组应用之投票统计
-
题目描述班上有N个同学,有五位候选人“A,B,C,D,E”,请所有的同学投票并选举出班长,现在请你编写程序来他们计算候选人的得票总数,每位同学投票将以数字的形式投票“12345”分别代表五位候选人,...
- C++20 新特性(6):new表达式也支持数组大小推导
-
new表达式也支持数组大小推导在C++17标准中,在定义并初始化静态数组时,是可以忽略数组大小,然后通过初始化数据来推导数组的大小。但使用new来定义并初始化动态数组时,并不支持这种自动推导数组大...
- C++ 结构体(struct)最全详解(c++结构体用法)
-
一、定义与声明1.先定义结构体类型再单独进行变量定义structStudent{intCode;charName[20];charSex;intA...
- 自学 C++ 第 6 课 二维数组找最值
-
键盘输入一个m×n的二维数组,通过C++编程找出元素中的最大值,并输出其所在的位置坐标。例如,输入一个4×5的二维数组,数组元素分别为{{556623749},{578964563},...
- 从缺陷中学习C/C++:聊聊 C++ 中常见的内存问题
-
在写C/C++程序时,一提到内存,大多数人会想到内存泄露。内存泄露是一个令人头疼的问题,尤其在开发大的软件系统时。一个经典的现象是,系统运行了10天、1个月都好好的,忽然有一天宕机了:OOM(Out...
- C++开发者都应该使用的十个C++11特性(上)
-
在C++11新标准中,语言本身和标准库都增加了很多新内容,本文只涉及了一些皮毛。不过我相信这些新特性当中有一些,应该成为所有C++开发者的常规装备。你也许看到过许多类似介绍各种C++11特性的文章。下...
- 深度解读C/C++指针与数组(c++指针和数组的区别)
-
指针和数组是密切相关的。事实上,指针和数组在很多情况下是可以互换的。例如,一个指向数组开头的指针,可以通过使用指针的算术运算或数组索引来访问数组。今天我们就来聊一聊数组和指针千丝万缕的关系;一维数组与...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)