当前位置: 首页 > 编程日记 > 正文

(转)C语言字节对齐

图片可以在下面的博客中看到. 

转自:http://blog.csdn.net/bigloomy/article/details/6633008 

可能有不少读者会问,字节对齐有必要拿出来单独写一篇博客嘛?我觉得是很有必要,但是它却是被很多人所忽视的一个重点。那么我们使用字节对齐的作用和原因是什么呢?由于硬件平台之间对存储空间的处理上是有很大不同的,一些平台对某些特定类型的数据只能从某些特定地址开始存取,如通常有些架构的CPU要求在编程时必须保证字节对齐,否则访问一个没有进行字节对齐的变量的时候会发生错误。而有些平台可能没有这种情况,但是通常的情况是如果我们编程的时候不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如我们操作一个int型数据,如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,两个周期读取出来的字节我们还要对它们进行高低字节的拼凑才能得到该int型数据,从而使得我们的读取效率较低,这也从侧面反映出了一个问题,就是我们很多时候是在牺牲空间来节省时间的。

可能看了上面的讲解你还是不太明白,那我们再来看一次什么是字节对齐呢? 我们现在的计算机中内存空间都是按照字节来进行划分的,从理论上来讲的话似乎对任何类型的变量的访问可以从任何地址开始,然而值得注意的就是,实际情况下在访问特定变量的时候经常在特定的内存地址访问,从而就需要各种类型的数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

按照预先的计划安排,这次应该是写《C语言的那些小秘密之链表(三)》的,但是我发现如果直接开始讲解linux内核链表的话,可能有些地方如果我们不在此做一个适当的讲解的话,有的读者看起来可能难以理解,所以就把字节对齐挑出来另写一篇博客,我在此尽可能的讲解完关于字节对齐的内容,希望我的讲解对你有所帮助。

在此之前我们不得不提的一个操作符就是sizeof,其作用就是返回一个对象或者类型所占的内存字节数。我们为什么不在此称之为sizeof()函数呢?看看下面一段代码:
view plain
#include <stdio.h>

void print()
{
printf("hello world!\n");
return ;
}
void main()
{
printf("%d\n",sizeof(print()));
return ;
}

这段代码在linux环境下我采用gcc编译是没有任何问题的,对于void类型,其长度为1,但是如果我们在vc6下面运行的话话就会出现illegal sizeof operand错误,所以我们称之为操作符更加的准确些,既然是操作符,那么我们来看看它的几种使用方式:

1、sizeof( object ); // sizeof( 对象 );

2、 sizeof( type_name ); // sizeof( 类型 );

3、sizeof object; // sizeof 对象; 通常这种写法我们在代码中都不会使用,所以很少见到。

下面来看段代码加深下印象:
view plain
#include <stdio.h>

void main()
{
int i;
printf("sizeof(i):\t%d\n",sizeof(i));
printf("sizeof(4):\t%d\n",sizeof(4));
printf("sizeof(4+2.5):\t%d\n",sizeof(4+2.5));
printf("sizeof(int):\t%d\n",sizeof(int));
printf("sizeof 5:\t%d\n",sizeof 5);
return ;
}

运行结果为:


view plain
sizeof(i): 4
sizeof(4): 4
sizeof(4+2.5): 8
sizeof(int): 4
sizeof 5: 4
Press any key to continue

从运行结果我们可以看出上面的几种使用方式,实际上,sizeof计算对象的大小也是转换成对对象类型的计算,也就是说,同种类型的不同对象其sizeof值都是一样的。从给出的代码中我们也可以看出sizeof可以对一个表达式求值,编译器根据表达式的最终结果类型来确定大小,但是一般不会对表达式进行计算或者当表达式为函数时并不执行函数体。如:
view plain
#include <stdio.h>
int print()
{
printf("Hello bigloomy!");
return 0;
}
void main()
{
printf("sizeof(print()):\t%d\n",sizeof(print()));
return ;
}

运行结果为:


view plain
sizeof(print()): 4
Press any key to continue

从结果我们可以看出print()函数并没有被调用。

接下来我们来看看linux内核链表里的一个宏:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

对这个宏的讲解我们大致可以分为以下4步进行讲解:

1、( (TYPE *)0 ) 0地址强制 "转换" 为 TYPE结构类型的指针;

2、((TYPE *)0)->MEMBER 访问TYPE结构中的MEMBER数据成员;

3、&( ( (TYPE *)0 )->MEMBER)取出TYPE结构中的数据成员MEMBER的地址;

4、(size_t)(&(((TYPE*)0)->MEMBER))结果转换为size_t类型。

宏offsetof的巧妙之处在于将0地址强制转换为 TYPE结构类型的指针,TYPE结构以内存空间首地址0作为起始地址,则成员地址自然为偏移地址。可能有的读者会想是不是非要用0呢?当然不是,我们仅仅是为了计算的简便。也可以使用是他的值,只是算出来的结果还要再减去该数值才是偏移地址。来看看下面的代码:
view plain
#include <stdio.h>

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)4)->MEMBER)

typedef struct stu1
{
int a;
int b;
}stu1;

void main()
{
printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a)-4);
printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b)-4);
}

运行结果为:


view plain
offsetof(stu1,a): 0
offsetof(stu1,b): 4
Press any key to continue

为了让读者加深印象,我们这里在代码中没有使用0,而是使用的4,所以在最终计算出的结果部分减去了一个4才是偏移地址,当然实际使用中我们都是用的是0。

懂了上面的宏offsetof之后我们再来看看下面的代码:
view plain
#include <stdio.h>

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

typedef struct stu1
{
int a;
char b[1];
int c;
}stu1;

void main()
{
printf("offsetof(stu1,a):\t%d\n",offsetof(stu1,a));
printf("offsetof(stu1,b):\t%d\n",offsetof(stu1,b));
printf("offsetof(stu1,c):\t%d\n",offsetof(stu1,c));
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
}

运行结果为:


view plain
offsetof(stu1,a): 0
offsetof(stu1,b): 4
offsetof(stu1,c): 8
sizeof(stu1) : 12
Press any key to continue

对于字节对齐不了解的读者可能有疑惑的是c的偏移量怎么会是8和结构体的大小怎么会是12呢?因该是sizeof(int)+sizeof(char)+sizeof(int)=9。其实这是编译器对变量存储的一个特殊处理。为了提高CPU的存储速度,编译器对一些变量的起始地址做了对齐处理。在默认情况下,编译器规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。现在来分析下上面的代码,如果我们假定a的起始地址为0,它占用了4个字节,那么接下来的空闲地址就是4,是1的倍数,满足要求,所以b存放的起始地址是4,占用一个字节。接下来的空闲地址为5,而c是int变量,占用4个字节,5不是4的整数倍,所以向后移动,找到离5最近的8作为存放c的起始地址,c也占用4字节,所以最后使得结构体的大小为12。现在我们再来看看下面的代码:
view plain
#include <stdio.h>

typedef struct stu1
{
char array[7];
}stu1;

typedef struct stu2
{
double fa;
}stu2;

typedef struct stu3
{
stu1 s;
char str;
}stu3;

typedef struct stu4
{
stu2 s;
char str;
}stu4;

void main()
{
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
printf("sizeof(stu3) :\t%d\n",sizeof(stu3));
printf("sizeof(stu4) :\t%d\n",sizeof(stu4));
}

运行结果为:


view plain
sizeof(stu1) : 7
sizeof(stu2) : 8
sizeof(stu3) : 8
sizeof(stu4) : 16
Press any key to continue

分析下上面我们的运行结果,重点是struct stu3和struct stu4,在struct stu3中使用的是一个字节对齐,因为在stu1和stu3中都只有一个char类型,在struct stu3中我们定义了一个stu1类型的 s,而stu1所占的大小为7,所以加上加上接下来的一个字节str,sizeof(stu3)为8。在stu4中,由于我们定义了一个stu2类型的s,而s是一个double类型的变量,占用8字节,所以接下来在stu4中采用的是8字节对齐。如果我们此时假定stu4中的s从地址0开始存放,占用8个字节,接下来的空闲地址就是8,根据我们上面的讲解可知刚好可以在此存放str。所以变量都分配完空间后stu4结构体所占的字节数为9,但9不是结构体的边界数,也就是说我们要求分配的字节数为结构体中占用空间最大的类型所占用的字节数的整数倍,在这里也就是double类型所占用的字节数8的整数倍,所以接下来还要再分配7个字节的空间,该7个字节的空间没有使用,由编译器自动填充,没有存放任何有意义的东西。

当然我们也可以使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。接下来我们来看看一段代码。
view plain
#include <stdio.h>

#pragma pack (1) /*指定按1字节对齐*/

typedef union stu1
{
char str[10];
int b;
}stu1;

#pragma pack () /*取消指定对齐,恢复缺省对齐*/

typedef union stu2
{
char str[10];
int b;
}stu2;

void main()
{
printf("sizeof(stu1) :\t%d\n",sizeof(stu1));
printf("sizeof(stu2) :\t%d\n",sizeof(stu2));
}

运行结果为:


view plain
sizeof(stu1) : 10
sizeof(stu2) : 12
Press any key to continue

现在来分析下上面的代码。由于之前我们一直都在使用struct,所以在这里我们特地例举了一个union的代码来分析下,我们大家都知道union的大小取决于它所有的成员中占用空间最大的一个成员的大小。由于在union stu1中我们使用了1字节对齐,所以对于stu1来说占用空间最大的是char str[10]类型的数组,,其值为10。为什么stu1为10而stu2却是12呢?因为在stu2的上面我们使用了#pragma pack () ,取消指定对齐,恢复缺省对齐。所以由于stu2其中int类型成员的存在,使stu2的对齐方式变成4字节对齐,也就是说,stu2的大小必须在4的对界上,换句话说就是stu2的大小要是4的整数倍,所以占用的空间变成了12。

到这儿本篇博客的内容就算是讲解完了,由于本人水平有限,博客中的不妥或错误之处在所难免,殷切希望读者批评指正。同时也欢迎读者共同探讨相关的内容,如果乐意交流的话请留下你宝贵的意见。

相关文章:

赌5毛钱,你解不出这道Google面试题

作者 | Kevin Ghadyani 译者 | 清儿爸 编辑 | Rachel 出品 | AI科技大本营&#xff08;ID&#xff1a;rgznai100&#xff09; 为了更了解其他人对软件工程的看法&#xff0c;我开始疯狂在 YouTube 上追 TechLead 的视频。在接下来的几天里&#xff0c;我为他在 Google 工作时…

【摄像头】摄像头IRCUT滤光片

1、IRCUT组成原理 IRCUT由两层滤光片组成&#xff0c;一片红外截止或吸收滤光片和一片全透光谱滤光片。 白天是红外截止滤光片工作&#xff0c;晚上是全透滤光片工作&#xff1a; 白天摄像头可以接收到人眼无法识别的红外线&#xff0c;会导致图像与肉眼所见有偏差&#xff0c…

修改Java-source版本

2019独角兽企业重金招聘Python工程师标准>>> pom.xml添加以下&#xff1a;<plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin>&l…

HDU 2519 新生晚会【求组合数】

Problem Description开学了&#xff0c;杭电又迎来了好多新生。ACMer想为新生准备一个节目。来报名要表演节目的人很多&#xff0c;多达N个&#xff0c;但是只需要从这N个人中选M个就够了&#xff0c;一共有多少种选择方法&#xff1f;Input数据的第一行包括一个正整数T&#x…

【摄像头】宽动态范围

1、什么是动态范围 简单的来说,就是摄像机拍摄的同一个画面内,能正常显示细节的最亮和最暗物体的亮度值所包含的那个区间。动态范围越大,过亮或过暗的物体在同一个画面中都能正常显示的程度也就越大。 根据百度百科,当在强光源(日光、灯具或反光等)照射下的高亮度区域及…

mysql format函数对数字类型转化的坑

原值param 1234.5678 format(param, 2) &#xff08;不建议&#xff09; 结果&#xff0c;字符串类型&#xff0c;123,4.57 会导致你图表char 生成失败&#xff0c;直接变0 convert(param, decimal(12,2))&#xff08;建议&#xff09; 结果, 数值类型 1234.57 cast(p…

打造AI产教融合共赢生态,微软亚洲研究院扩大开放了这些资源

2019年5月10日&#xff0c;由教育部国际合作与交流司、科学技术司指导&#xff0c;教育部中外人文交流中心主办&#xff0c; 微软亚洲研究院承办&#xff0c;信息技术新工科产学研联盟特别协办的“中国高校人工智能人才国际培养计划”2019国际人工智能专家论坛暨2019微软新一代…

Microsoft.NET框架程序设计--20 CLR寄宿、应用程序域、反射

应用程序域是CLR提供的一种旨在减少内存使用、提高系统系能的新型机制。而反射使得我们可以很容易使用自己活着第三方的类型来增强应用程序的功能&#xff0c;从而帮助我们设计出可动态扩展的应用程序。 1.元数据&#xff1a;.NET框架的基石 元数据描述了一个类型的字段和方法。…

上手!深度学习最常见的26个模型练习项目汇总

作者&#xff1a;沧笙踏歌转载自AI部落联盟&#xff08;id&#xff1a;AI_Tribe&#xff09;今天更新关于常见深度学习模型适合练手的项目。这些项目大部分是我之前整理的&#xff0c;基本上都看过&#xff0c;大概俩特点&#xff1a;代码不长&#xff0c;一般50-200行代码&…

【EMC】电磁兼容性相关名词解释、基础知识

一、名词解释 1、EMC EMC&#xff08;Electro Magnetic Compatibility&#xff09;直译是“电磁兼容性”。意指设备所产生的电磁能量既不对其它设备产生干扰&#xff0c;也不受其他设备的电磁能量干扰的能力。 2、EMI——攻击力 EMI(Electro Magnetic Interference)直译为&…

定时任务 Cron表达式

Cron表达式是一个表示时间周期的字符串。 分为6或7个域&#xff0c;每一个域代表一个含义。 验证工具&#xff1a; http://cron.qqe2.com/Cron有如下两种语法格式&#xff1a; 格式1&#xff1a;秒分时天&#xff08;月&#xff09;月天&#xff08;星期&#xff09;年格式2&a…

C语言字符计算器

这又是以前的一篇文章&#xff0c;觉得有纪念价值。就发过来了。 去年暑假自己下了C语言实战105例&#xff0c;看了几个基础的&#xff0c;其中有一个是关于字符计算器的 我看起来蛮简单的&#xff0c;不过自己做起来我觉得还是做得少了&#xff0c;懵懵懂懂的。 现在想起那个觉…

一文看尽目标检测:从YOLO v1到v3的进化之路

本文转载自&#xff1a;http://www.mamicode.com/info-detail-2314392.html导语&#xff1a;如今基于深度学习的目标检测已经逐渐成为自动驾驶、视频监控、机械加工、智能机器人等领域的核心技术&#xff0c;而现存的大多数精度高的目标检测算法&#xff0c;速度较慢&#xff0…

【EMC】EMC屏蔽设计

1、屏蔽设计的基本原则&#xff1a; 蔽体结构简洁&#xff0c;尽可能减少不必要的孔洞&#xff0c;尽可能不要增加额外的缝隙&#xff1b;避免开细长孔&#xff0c;通风孔尽量采用圆孔并阵列排放。屏蔽和散热有矛盾时尽可能开小孔&#xff0c;多开孔&#xff0c;避免开大孔&am…

js控制表格隔行变色

只是加载时候隔行变一个颜色&#xff0c;鼠标滑动上去时候没有变化 <table width"800" border"0" cellpadding"0" cellspacing"0"> <tr><td>不变色</td></tr><tbody id"goaler"><tr…

jQuery实例——仿京东仿淘宝列表导航菜单

以前看着京东&#xff0c;淘宝的导航做的真好&#xff0c;真想哪一天自己也能做出来这么漂亮功能全的导航菜单。今天弄了一下午终于自制成功&#xff0c;主要使用jQuery和CSS&#xff0c;实现功能基本和京东一样。 功能介绍&#xff1a; 1、鼠标停留导航&#xff1b; 2、根据子…

【Ubuntu】使用过的ubuntu工具记录

1、UnixBench UnixBench性能测试&#xff0c;和windows的鲁大师差不多。 2、smartctl 测试磁盘性能 sudo apt install smartmontools 3、cpufrequtils cpu频率查看、设置工具集&#xff1a;cpufreq-inf、cpufreq-set sudo apt install cpufrequtils 4、stress cpu满负荷…

解救被困传销女演员 助人减肥找老婆 蚂蚁森林又现神功能

近日&#xff0c;一篇《女演员被传销组织拘禁30多天 竟因蚂蚁森林幸运逃离》的报道引发了全网热议。网友纷纷表示&#xff1a;蚂蚁森林功能强大&#xff0c;不仅能帮人减肥、找老婆&#xff0c;还能在关键时刻保命&#xff01; 珍惜偷你能量的好友 因为关键时刻能保命 据北京晨…

“智能+”时代,看见别人看不见的才是赢家

当科技、商业和社会均发生天翻地覆的变革&#xff0c;我们可以确定的是&#xff0c;“智能”时代的浪潮已掀起波澜。这将是智慧无处不在的时代&#xff0c;曾经无法解决的问题&#xff0c;都将在科技的发展下找到答案&#xff1b;这也是技术普惠万物的时代&#xff0c;创新型应…

CSS a控制超链接文字样式

超链接的代码<a href"http://www.divcss5.com/" target"_blank" title"关于div css的网站">DIVCSS</a>解析如下&#xff1a;href 后跟被链接地址目标网站地址这里是http://www.divcss5.com/target _blank -- 在新窗口中打开链接 _pa…

3分钟快速实现:9种经典排序算法的可视化

作者 | 爱笑的眼睛 来源 | 恋习Python&#xff08;ID:sldata2017&#xff09;最近在某网站上看到一个视频&#xff0c;是关于排序算法的可视化的&#xff0c;看着挺有意思的&#xff0c;也特别喜感。▼6分钟演示15种排序算法不知道作者是怎么做的&#xff0c;但是突然很想自己…

【Qt】Qt再学习(一):Application Example

1、QCommandLineParser 命令行解析类 常用接口 QApplication app(argc, argv);QCommandLineParser parser;parser.setApplicationDescription(QCoreApplication::applicationName());parser.addHelpOption(

沃森世界研讨会前瞻:AI服务 了解客户情绪

科技讯10月19日消息&#xff0c;据国外媒体报道&#xff0c;“沃森世界”研讨会(World of Watson)将于10月24日至27日在拉斯维加斯曼德勒湾举办&#xff0c;与会者将能够了解沃森目前的进展&#xff0c;并深入了解将来沃森将从事的一些令人兴奋的事情。10月14日一整日的会谈中&…

《人月神话》——外科手术队伍——笔记!

本章讨论了一个问题“如何在有意义的时间进度内创建大型的系统&#xff1f;” 软件经理测试出来的数据显示“经验和实际的表现没有相互的联系”。 *需要协作沟通的人员的数量影响着开发成本&#xff0c;因为成本的主要组成部分是相互的沟通和交流&#xff0c;以及更正沟…

直接上手!不容错过的Visual Studio Code十大扩展组件

作者 | David Neal译者 | 谭开朗&#xff0c;责编 | 屠敏转载自CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;各大平台与各种语言的开发人员都在使用Visual Studio Code&#xff0c;我对此感到惊讶。Stack Overflow发布的2019年开发者调查结果显示&#xff0c;VS Code占…

【Qt】Qt再学习(二):Bars Example(Q3DBars)

1、简介 Bars example显示了如何使用Q3DBars制作3D条形图&#xff0c;以及如何结合使用小部件来调整几种可调节的质量。该示例显示了如何&#xff1a; 使用Q3DBars和一些小部件创建应用程序 使用QBar3DSeries和QBarDataProxy将数据设置为图形 使用控件调整一些图形和系列属性…

记录错误信息的行数

1.try catch 记录错误信息的时候&#xff0c;如果报错了&#xff0c;我们只能粗略估算是什么错误&#xff0c;但如果能够具体知道是哪行错误的话&#xff0c;对错误的分析就能够快速定位问题。 2.只需要记录到错误的行号&#xff0c;就能快速定位问题。 3.ex.stackTrace 就可以…

android中PreferencesActivity的使用(一)

在使用android手机的时候&#xff0c;尤其是在操作软件设置时&#xff0c;我们经常见到这样的界面&#xff1a; 这是怎么来实现的的呢&#xff1f;其实android已经提供了相应的类和方法&#xff0c;当进行简单数据存储时&#xff08;比如&#xff1a;软件配置参数&#xff09;a…

吴恩达团队:神经网络如何正确初始化?

来源 | deeplearning.ai编译 | 刘静转载自图灵TOPIA(ID:turingtopia)初始化对训练深度神经网络的收敛性有重要影响。简单的初始化方案可以加速训练&#xff0c;但是它们需要小心避免常见的陷阱。近期&#xff0c;deeplearning.ai就如何有效地初始化神经网络参数发表了交互式文章…

【Qt】Qt再学习(三):Chart Themes Example(常用图表)

1、简介 该示例中展示了各种图表以及在不同内置主题下的外观。 2、使用到的类 QChart:图表抽象类,继承自QGraphicsWidget QChartView:显示图表窗口,继承自QGraphicsView QLineSeries:折线图 QAreaSeries:面积图 QStackedBarSeries:分段条状图 QScatterSeries:散点图…