内存对齐的初步讲解

C语言结构体对齐,超详细,超易懂 结构体对齐规则 结构体的第一个成员总是存放在结构体变量开辟的空间的起始地址 其它成员变量要存储在一个名叫对齐数的整数倍的地址 结构体总大小为最大对齐数的整数倍,结构体内每一个变量都会产生一个对齐数,取其最大的对齐数 如果是嵌套结构体,那么嵌套的结构体存储在嵌套结构体的成员的最大对齐数的整数倍地址处,包含嵌套结构体的结构体大小为自己成员的对齐数和嵌套结构体的成员的对齐数的最大对齐数的整数倍 阅读详情
一.内存对齐的初步讲解
内存对齐可以用一句话来概括:
“数据项只能存储在地址是数据项大小的整数倍的内存位置上”
例如int类型占用4个字节,地址只能在0,4,8等位置上。
例1:
#include <stdio.h>
struct xx{
        char b;
        int a;
        int c;
        char d;
};
int main()
{
        struct xx bb;
        printf("&a = %p\n", &bb.a);
        printf("&b = %p\n", &bb.b);
        printf("&c = %p\n", &bb.c);
        printf("&d = %p\n", &bb.d);
        printf("sizeof(xx) = %d\n", sizeof(struct xx));
        return 0;
}
执行结果如下:
&a = ffbff5ec
&b = ffbff5e8
&c = ffbff5f0
&d = ffbff5f4
sizeof(xx) = 16
会发现b与a之间空出了3个字节,也就是说在b之后的0xffbff5e9,0xffbff5ea,0xffbff5eb空了出来,a直接存储在了0xffbff5ec, 因为a的大小是4,只能存储在4个整数倍的位置上。打印xx的大小会发现,是16,有些人可能要问,b之后空出了3个字节,那也应该是13啊?其余的3个 呢?这个往后阅读本文会理解的更深入一点,这里简单说一下就是d后边的3个字节,也会浪费掉,也就是说,这3个字节也被这个结构体占用了.
可以简单的修改结构体的结构,来降低内存的使用,例如可以将结构体定义为:
struct xx{
        char b; 
        char d;
        int a;          
        int c;                  
};
这样打印这个结构体的大小就是12,省了很多空间,可以看出,在定义结构体的时候,一定要考虑要内存对齐的影响,这样能使我们的程序占用更小的内存。
二.操作系统的默认对齐系数
每 个操作系统都有自己的默认内存对齐系数,如果是新版本的操作系统,默认对齐系数一般都是8,因为操作系统定义的最大类型存储单元就是8个字节,例如 long long(为什么一定要这样,在第三节会讲解),不存在超过8个字节的类型(例如int是4,char是1,long在32位编译时是4,64位编译时是 8)。当操作系统的默认对齐系数与第一节所讲的内存对齐的理论产生冲突时,以操作系统的对齐系数为基准。
例如:
假设操作系统的默认对齐系数是4,那么对与long long这个类型的变量就不满足第一节所说的,也就是说long long这种结构,可以存储在被4整除的位置上,也可以存储在被8整除的位置上。
可以通过#pragma pack()语句修改操作系统的默认对齐系数,编写程序的时候不建议修改默认对齐系数,在第三节会讲解原因
例2:
#include <stdio.h>
#pragma pack(4)
struct xx{
        char b;
        long long a;
        int c;
        char d;
};
#pragma pack()
int main()
{
        struct xx bb;
        printf("&a = %p\n", &bb.a);
        printf("&b = %p\n", &bb.b);
        printf("&c = %p\n", &bb.c);
        printf("&d = %p\n", &bb.d);
        printf("sizeof(xx) = %d\n", sizeof(struct xx));
        return 0;
}
打印结果为:
&a = ffbff5e4
&b = ffbff5e0
&c = ffbff5ec
&d = ffbff5f0
sizeof(xx) = 20
发现占用8个字节的a,存储在了不能被8整除的位置上,存储在了被4整除的位置上,采取了操作系统的默认对齐系数。
三.内存对齐产生的原因
 


内存对齐是操作系统为了快速访问内存而采取的一种策略,简单来说,就是为了放置变量的二次访问。操作系统在访问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。如果没有内存对齐时,为了读取一个变量是,会产生总线的二 次访问。
例如假设没有内存对齐,结构体xx的变量位置会出现如下情况:
struct xx{
        char b;         //0xffbff5e8
        int a;            //0xffbff5e9       
        int c;             //0xffbff5ed      
        char d;         //0xffbff5f1
};
操作系统先读取0xffbff5e8-0xffbff5ef的内存,然后在读取0xffbff5f0-0xffbff5f8的内存,为了获得值c,就需要将两组内存合并,进行整合,这样严重降低了内存的访问效率。(这就涉及到了老生常谈的问题,空间和效率哪个更重要?这里不做讨论)。
这样大家就能理解为什么结构体的第一个变量,不管类型如何,都是能被8整除的吧(因为访问内存是从8的整数倍开始的,为了增加读取的效率)!


内存对齐的问题主要存在于理解struct等复合结构在内存中的分布。
首先要明白内存对齐的概念。
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。
这个k在不同的cpu平台下,不同的编译器下表现也有所不同。比如32位字长的计算机与16位字长的计算机。这个离我们有些远了。我们的开发主要涉及两大平台,windows和linux(unix),涉及的编译器也主要是microsoft编译器(如cl),和gcc。
内存对齐的目的是使各个基本数据类型的首地址为对应k的倍数,这是理解内存对齐方式的终极法宝。另外还要区分编译器的分别。明白了这两点基本上就能搞定所有内存对齐方面的问题。
不同编译器中的k:
1、对于microsoft的编译器,每种基本类型的大小即为这个k。大体上char类型为8,int为32,long为32,double为64。
2、对于linux下的gcc编译器,规定大小小于等于2的,k值为其大小,大于等于4的为4。
明白了以上的说明对struct等复合结构的内存分布就应该很清楚了。
下面看一下最简单的一个类型:struct中成员都为基本数据类型,例如:
struct test1
{
char a;
short b;
int c;
long d;
double e;
};
在windows平台,microsoft编译器下:
假设从0地址开始,首先a的k值为1,它的首地址可以使任意位置,所以a占用第一个字节,即地址0;然后b的k值为2,他的首地址必须是2的倍数,不能是1,所以地址1那个字节被填充,b首地址为地址2,占用地址2,3;然后到c,c的k值为4,他的首地址为4的倍数,所以首地址为4,占用地址4,5,6,7;再然后到d,d的k值也为4,所以他的首地址为8,占用地址8,9,10,11。最后到e,他的k值为8,首地址为8的倍数,所以地址12,13,14,15被填充,他的首地址应为16,占用地址16-23。显然其大小为24。
这就是 test1在内存中的分布情况。我们建立一个test1类型的变量,a、b、c、d、e分别赋值2、4、8、16、32。然后从低地址依次打印出内存中每个字节对应的16进制数为:
2 0 4 0 8 0 0 0 10 0 0 0 0 0 0 0 0 0 0 0 0 0 40 40
验证:
显然推断是正确的。
在linux平台,gcc编译器下:
假设从0地址开始,首先a的k值为1,它的首地址可以使任意位置,所以a占用第一个字节,即地址0;然后b的k值为2,他的首地址必须是2的倍数,不能是1,所以地址1那个字节被填充,b首地址为地址2,占用地址2,3;然后到c,c的k值为4,他的首地址为4的倍数,所以首地址为4,占用地址4,5,6,7;再然后到d,d的k值也为4,所以他的首地址为8,占用地址8,9,10,11。最后到e,从这里开始与microsoft的编译器开始有所差异,他的k值为不是8,仍然是4,所以其首地址是12,占用地址12-19。显然其大小为20。
验证:
我们建立一个test1类型的变量,a、b、c、d、e分别赋值2、4、8、16、32。然后从低地址依次打印出内存中每个字节对应的16进制数为:
2 0 4 0 8 0 0 0 10 0 0 0 0 0 0 0 0 0 40 40
显然推断也是正确的。
接下来,看一看几类特殊的情况,为了避免麻烦,不再描述内存分布,只计算结构大小。
第一种:嵌套的结构
struct test2
{
char f;
struct test1 g;
};
在windows平台,microsoft编译器下:
这种情况下如果把test2的第二个成员拆开来,研究内存分布,那么可以知道,test2的成员f占用地址0,g.a占用地址1,以后的内存分布不变,仍然满足所有基本数据成员的首地址都为其对应k的倍数这一原则,那么test2的大小就还是24了。但是实际上test2的大小为32,这是因为:不能因为test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求,f成员后面需要填充一定数量的字节,不难发现,这个数量应为7个,才能保证test1的对齐。所以test2相对于test1来说增加了8个字节,所以test2的大小为32。
在linux平台,gcc编译器下:
同样,这种情况下如果把test2的第二个成员拆开来,研究内存分布,那么可以知道,test2的成员f占用地址0,g.a占用地址1,以后的内存分布不变,仍然满足所有基本数据成员的首地址都为其对应k的倍数这一原则,那么test2的大小就还是20了。但是实际上test2的大小为24,同样这是因为:不能因为test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求,f成员后面需要填充一定数量的字节,不难发现,这个数量应为3个,才能保证test1的对齐。所以test2相对于test1来说增加了4个字节,所以test2的大小为24。
第二种:位段对齐
struct test3
{
unsigned int a:4;
unsigned int b:4;
char c;
};
或者
struct test3
{
unsigned int a:4;
int b:4;
char c;
};
在windows平台,microsoft编译器下:
相邻的多个同类型的数(带符号的与不带符号的,只要基本类型相同,也为相同的数),如果他们占用的位数不超过基本类型的大小,那么他们可作为一个整体来看待。不同类型的数要遵循各自的对齐方式。
如:test3中,a、b可作为一个整体,他们作为一个int型数据来看待,所以test3的大小为8字节。并且a与b的值在内存中从低位开始依次排列,位于4字节区域中的前0-3位和4-7位
如果test4位以下格式
struct test4
{
unsigned int a:30;
unsigned int b:4;
char c;
};
那么test4的大小就为12个字节,并且a与b的值分别分布在第一个4字节的前30位,和第二个4字节的前4位。
如过test5是以下形式
struct test5
{
unsigned int a:4;
unsigned char b:4;
char c;
};
那么由于int和char不同类型,他们分别以各自的方式对齐,所以test5的大小应为8字节,a与b的值分别位于第一个4字节的前4位和第5个字节的前4位。
在linux平台,gcc编译器下:
struct test3
{
unsigned int a:4;
unsigned int b:4;
char c;
};
gcc下,相邻各成员,不管类型是否相同,占的位数之和超过这些成员中第一个的大小的时候,在结构中以k值为1对齐,在结构外k值为其基本类型的值。不超过的情况下在内存中依次排列。
如test3,其大小为4。a,b的值在内存中依次排列分别为第一个四字节中的0-3和4-7位。
如果test4位以下格式
struct test4
{
unsigned int a:20;
unsigned char b:4;
char c;
};
test4的大小为4个字节,并且a与b的值分别分布在第一个4字节的0-19位,和20-23位,c存放在第4个字节中。
如过test5是以下形式
struct test5
{
unsigned int a:10;
unsigned char b:4;
short c;
};
那么test5的大小应为4字节,a,b的值为0-9位和10-13位。c存放在后两个字节中。如果a的大小变成了20
那么test5的大小应为8字节。即
struct test6
{
unsigned int a:20;
unsigned char b:4;
short c;
};
此时,test6的a、b共占用0,1,2共3字节,c的k值为2,其实可以4位首位置,但是在结构外,a要以int的方式对齐。也就是说连续两个test6对象在内存中存放的话,a的首位置要保证为4的倍数,那么c后面必须多填充2位。所以test6的大小为8个字节。
关于位段结构的部分是比较复杂的。暂时我就知道这么多。
C语言——内存对齐总结 1、什么是内存对齐? 将程序中的每个数据单元安排在适当的位置上(这是编译器干的事) 2、需要内存对齐的原因 不是所有的硬件平台都能访问任意地址上的任意数据(某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常) 可以在相当大的程度上提供程序性能(以空间换时间) 3、内存对齐的主要应用范围 对于内存对齐问题,主要存在于struct和union等复合结构在内存中... 阅读详情

相关推荐

C 语言中如何进行内存对齐?

内存对齐是 C 语言中一个重要但容易被忽视的概念。理解内存对齐的规则和原理,能够帮助我们编写更高效、可移植的代码。通过使用指令或手动填充字节,可以在特定情况下控制结构体的内存布局,以满足性能和空间的要求。但在实际应用中,需要谨慎考虑各种因素,以达到最优的效果。🎉相关推荐📙C 语言百万年薪修炼课程【https://dwz.mosong.cc/cyyjc】 通俗易懂,深入浅出,匠心打磨,死磕细节,6年迭代,看过的人都说好。🍅博客首页-关注博主🎗️带你畅游技术世界,不错过每一次成长机会!

2401_86074221的博客 762

内存对齐

一.内存对齐的初步讲解内存对齐可以用一句话来概括:“数据项只能存储在地址是数据项大小的整数倍的内存位置上”例如int类型占用4个字节,地址只能在0,4,8等位置上。例1:#include struct xx{        char b;        int a;        int c;        char d;};int main(){        str

1278

点云直通滤波器:三维视觉数据预处理的原理、PCL实现与实战技巧

点云滤波是三维视觉与机器人感知中处理原始传感器数据的关键预处理步骤,其核心目标在于剔除噪声、分离背景并提取感兴趣区域,为后续的识别、分割与重建算法提供高质量输入。从原理上看,直通滤波器作为一种基于坐标轴阈值判别的空间裁剪方法,通过设定坐标范围实现高效的点云筛选,其计算复杂度为线性,具备极高的处理速度。在技术价值上,该滤波器因其简单、可控且计算高效的特点,成为众多项目数据流水线的首选预处理工具,尤其在去除地面与天花板、裁剪感兴趣区域等场景中不可或缺。在应用实践中,结合PCL(Point Cloud Libra

weixin_29062963的博客 257

C语言:内存字节对齐详解[转载]

一、什么是对齐,以及为什么要对齐:1. 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。2. 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址

秦白衣的技术专栏 2万+

具身智能数据采集的工程化指南:从时间同步到质量校验

具身智能的模型训练离不开高质量的多模态轨迹数据,而数据采集并非简单的传感器记录。真实场景中,相机图像、机械臂关节状态与力觉信息必须在时间轴上精确对齐,标定的漂移和格式混乱会让看似正常的数据无法用于训练。时间同步与标定一致性,是决定数据质量的核心技术环节。在机器人抓取、操作等工程应用中,一套包含多传感器同步、轻量记录脚本、自动化质量校验与版本管理的数据采集流程,能大幅降低数据生产的失败率。本文从工程视角拆解具身数据采集中的典型问题,并给出可落地的流程设计与校验方法,帮助团队构建可持续迭代的高质量数据集。

weixin_30603633的博客 510

扩视机器视觉—Halcon机器视觉48个实战项目六(检测定位角度)

此项目halcon检测检测定位角度问题,并显示。

qq_21516871的博客 1093

内存对齐的初步讲解--linux和windows平台下内存分配的差异

一.内存对齐的初步讲解 内存对齐可以用一句话来概括: “数据项只能存储在地址是数据项大小的整数倍的内存位置上” 例如int类型占用4个字节,地址只能在0,4,8等位置上。 例1: #include struct xx{         char b;         int a;         int c;         char d; };

linuxblack125125的专栏 2952

19-5 ACC 自适应巡航控制系统需求

本专题由深圳季连AIgraphX李博及团队出品,主要讲解ACC自适应巡航控制系统的开发与仿真测试。具体说来,包括ACC标准/法规分析、系统需求、系统架构、软件需求、软件架构、算法建模、验证场景分析、仿真实验搭建等内容。 本节主要讲ACC系统需求。

AIgraphX 1088

从入门到了解C++系列-----类与对象(上)

前言:本篇文章先初步的简单的了解类与对象,预计会有三篇文章熟悉类与对象。

2405_85784684的博客 1575

【JS】自定义上传组件,文件上传与回显,自定义进度条显示(基于element-plus与axios)

公司是做B 端产品的,有sass平台也有源码交付的项目,最近开发了很多小系统,这里记录一下开发遇到的问题。 需要定制一个上传组件,要求可以自定义文件存储路径,自定义可上传的文件类型,限制文件大小等,并且提供基础的文件回显功能,例如上传一张图片到服务器指定目录的`/file`文件夹下面,并且实现可以查看预览的功能。

余浩的博客 1161

Ubuntu下Charm-Crypto安装

经过一下午的努力终于是把Charm-Crypto给安上了,经过查询资料,大致有三种方案(两坑一可行)。 适用于我的电脑的是Ubuntu 18.04版本。 第一种方案是根据连接https://pypi.org/project/charm-crypto/的教程进行安装,但是该方案的优势在于下载快速,缺陷在于Ubuntu系统仅仅更新到13+版本。 第二种就是网络博客上的介绍,内容与第一种较类似,但是有16+,18+成功的例子,缺点就是bug太多,我大部分时间都耗在了这上边,而且还没有成功。 第三中是github上

Mr.Yi的博客 2284

【C++】类和对象(上)

本篇主要围绕以下来写内容: 1.面向过程和面向对象初步认识 2.类的引入 3.类的定义 4.类的访问限定符及封装 5.类的作用域 6.类的实例化 7.类的对象大小的计算 8.类成员函数的this指针

2303_77756141的博客 956

IPC-7351标准实战:如何用LPWizard_10快速生成PCB封装库(附避坑指南)

本文提供了一份详细的IPC-7351标准实战指南,手把手教你使用LPWizard_10软件快速生成符合行业标准的PCB封装库,并直接导入Cadence Allegro。文章深入解读了IPC-7351密度等级的选择逻辑,并重点分享了软件配置、QFN封装生成实战步骤,以及导出失败、焊盘异常等常见问题的避坑解决方案,旨在帮助硬件工程师提升设计效率和可靠性。

weixin_29207439的博客 153

类与对象(上)

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!提示:以下是本篇文章正文内容,下面案例可供参考// 类体:由成员函数和成员变量组成// 一定要注意后面的分号class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分 号不能省略。类体中内容称为类的成员:类中的变量称为。

2301_79585944的博客 1071

Java SSH框架学习

Java SSH框架学习

私信可回答各种问题~ 1789

点云焊缝提取

本文介绍了一个基于PCL库的点云处理流程,包含三个核心模块:MLS平滑处理通过多项式拟合修正点云位置;差异提取通过双向匹配定位源/目标点云的非重叠区域;统计过滤剔除离群噪声点。该流程采用"平滑-筛选-净化"的通用思路,将复杂算法封装为可配置接口,实现了工业级点云预处理。文章重点分析了各模块的原理、PCL接口调用及参数设计逻辑,同时指出参数调优、算法扩展等深入探索方向,展现了PCL在点云处理中的模块化优势与实用价值。

weixin_45196569的博客 250

C++ 类和对象(上)

声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。(即2. 可以将函数的定义放在 .h 文件中)类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

2301_79814410的博客 1045

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

从C++开始的编程生活(4)——类的定义、访问限定符、类域、类的实例化和this指针

本系列文章承接C语言的学习,需要有C语言的基础才能学会哦~ 第3篇主要讲的是有关于C++的类的定义、访问限定符、类域、类的实例化和this指针。 C++才起步,都很简单呢!

Elnaij的博客 813

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

《吃透 C++ 类和对象(上):封装、实例化与 this 指针详解》

本文详细介绍了C++中类和对象的核心概念,通过大量代码示例讲解了类定义、访问限定符、实例化、this指针等关键知识点。主要内容包括:1. 类定义的两种方式(class和struct)及其区别;2. 访问限定符(public/private/protected)的作用和使用规范;3. 类实例化的过程和对象内存分配原理;4. this指针的底层实现机制;5. 通过C与C++实现Stack的对比,展示面向对象编程的封装特性。文章包含多个测试题目和详细注释的代码示例,帮助读者深入理解类和对象这一C++核心概念,为后

会当凌绝顶,一览众山小 1833

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

类和对象(上)

C++的入门基础内容我已经介绍完啦,接着我们要在这些内容的基础上继续深入学习,本篇文章开始,我会将类和对象的内容分开写,因为类和对象的内容挺多,且不太好理解,希望能够帮助到大家!

2402_84433929的博客 1006

2.类与对象(上篇)

类体中内容称为。

Q2691463021的博客 724
上一篇: 如何在Linux操作系统查看文件创建时间
下一篇: linux下打印带有颜色的字体
夏天__
博客等级 码龄15年 42粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值