1、前言

什么是字节对齐呢?现代计算机中的内存空间都是按字节(byte)划分的,从理论上讲似乎任何类型的变量的访问都可以从任何地址开始,但是实际情况是在访问特定变量的时候经常需要在特定的内存地址进行访问,因此,就需要各种类型数据按照一定的规则在空间上排列,而不是顺序地一个接一个地排放,这就是字节对齐。

2、字节对齐的好处

各个硬件平台对存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定的地址开始存取,比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构上编程必须保证字节对齐。其它平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失,例如某些平台每次读都是从偶地址开始,如果一个int类型(32位系统)变量存放在偶地址开始的地方,那么一个读周期就可以读出32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果进行高低字节的拼凑才能得到该32bit数据。

3、对齐的准则

有四个重要的基本概念,如下:

(1)数据类型自身的对齐值:char类型数据自身对齐值为1字节,short类型数据为2字节,int/float类型数据为4字节,double类型数据为8字节;

(2)结构体或类的自身对齐值:其成员中自身对齐值最大的哪个值;

(3)指定对齐值:#pragma pack(value)时指定对齐值value;

(4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中的较小者,即:

有效对齐值=min{自身对齐值,当前指定的pack值}。

其中,有效对齐值N是最终用来决定数据存放地址方式的值,有效对齐N表示“对齐在N上”,也就是该数据的“存放起始地址%N=0”。

其实字节对齐的细节和具体编译器实现密切相关,但是一般而言,满足下面三个准则:

(1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

(2)结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍,如需要时,编译器会在成员之间加上填充字节;

(3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如需要时,编译器会在最末一个成员之后加上填充字节。

对于上面的三条对齐准则说明如下:

第一条:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被这基本类型所整除的位置,并把这个地址作为结构体的首地址;

第二条:为结构体的一个成员开辟空间之间,编译器首先检查预开辟空间的地址相对于结构体首地址的偏移是否是本成员大小的整数倍,若是,则存放本成员,否则,在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求;

第三条:结构体的总大小是包括填充的字节的,结构体的最后一个成员满足上面两个对齐准则以外,还必须满足第三条准则,否则就必须在最后填充几个字节以满足最后一条准则。

4、结构体字节对齐示例

接下来给出一个结构体字节对齐的简单示例,并对其进行分析:

示例的代码如下所示:

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h> struct s {
char a;
short b;
char c;
int d;
char e[];
}; int main(int argc, char *argv[])
{
printf("sizeof(struct s):%zd\n", sizeof(struct s));
printf("offsets->a:%zd; b:%zd; c:%zd; d:%zd; e[0]:%zd; e[1]:%zd; e[2]:%zd\n",
offsetof(struct s, a), offsetof(struct s, b),
offsetof(struct s, c), offsetof(struct s, d),
offsetof(struct s, e[]), offsetof(struct s, e[]),
offsetof(struct s, e[])); exit(EXIT_SUCCESS);
}

使用gcc编译器进行编译,并运行,结果输出如下:

在测试代码中使用了offsetof这个宏,该宏能获取结构体中成员的偏移量,在运行结果中,我们可以看到,使用sizeof计算结构体的总大小为16字节,如果直接计算的话,char类型占1个字节,short类型占2个字节,int类型占4个字节,应该结构体大小为11个字节,为什么最终的大小是16字节呢?是由于用到了上面介绍的字节对齐的准则,下面对其进行分析:

首先,变量a为char类型占用1个字节,变量b为short类型,占用2个字节,根据准则2,需要在成员b和a之间填充1个字节,变量c为char类型,占用1个字节,变量d为int类型,占用4个字节,根据准则2,需要在成员d和c之间填充3个字节,数组e为char类型,占用3个字节,但是根据准则3,需要在最后填充1个字节,因此,结构体的总大小应为16字节。

5、更改对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对齐条件分配空间,一般地,可以通过下面的方法来改变缺省的对齐条件:

(1)使用伪指令#pragma pack(n):C编译器将按照n个字节进行对齐;

(2)使用伪指令#pragma pack():取消自定义的字节对齐方式;

另外,还有GCC的特有语法

(3)__attribute__((aligned(n))):让所作用的结构体成员对齐在n字节自然边界上,如果结构体中有成员的长度大于n,则按照最大成员的长度来对齐;

(4)__attribute__((packed)):取消结构体在编译过程中的优化对齐,按照实际占用的字节数进行对齐。

下面就更改对齐方式进行一些例子讲解:

使用#pragma pack()将结构体对齐方式进行更改,代码如下:

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h> #pragma pack(1)
struct s {
char a;
short b;
char c;
int d;
char e[];
};
#pragma pack() int main(int argc, char *argv[])
{
printf("sizeof(struct s):%zd\n", sizeof(struct s));
printf("offsets->a:%zd; b:%zd; c:%zd; d:%zd; e[0]:%zd; e[1]:%zd; e[2]:%zd\n",
offsetof(struct s, a), offsetof(struct s, b),
offsetof(struct s, c), offsetof(struct s, d),
offsetof(struct s, e[]), offsetof(struct s, e[]),
offsetof(struct s, e[])); exit(EXIT_SUCCESS);
}

使用gcc对代码进行编译后运行,结果如下:

使用#pragma pack(1)将字节对齐方式设置为1字节,相当于取消了默认的字节对齐方式,因此编译器将不再对变量之间进行字节填充,所以,使用sizeof()得到就是数据类型占用的实际大小,为11个字节。

当使用#pragma pack(2)将字节对齐方式设置为2字节时,结果又如何呢?

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h> #pragma pack(2)
struct s {
char a;
short b;
char c;
int d;
char e[];
};
#pragma pack() int main(int argc, char *argv[])
{
printf("sizeof(struct s):%zd\n", sizeof(struct s));
printf("offsets->a:%zd; b:%zd; c:%zd; d:%zd; e[0]:%zd; e[1]:%zd; e[2]:%zd\n",
offsetof(struct s, a), offsetof(struct s, b),
offsetof(struct s, c), offsetof(struct s, d),
offsetof(struct s, e[]), offsetof(struct s, e[]),
offsetof(struct s, e[])); exit(EXIT_SUCCESS);
}

使用gcc编译后,然后运行,结果如下: 

可以看到,结构体的总大小变成了14字节,分析如下:

变量a自身对齐值为1,指定对齐值为2,所以有效对齐值为1,假设结构体从0地址开始存储,符合0%1=0,变量b自身对齐值为2,指定对齐值为2,所以有效对齐值为2,从地址2开始存放,符合2%2=0,变量b和a之间需要填充1个字节,变量c自身对齐值为1,指定对齐值为2,有效对齐值为2,从地址4开始存储,符合4%2=0,变量d为int类型,自身对齐值为4,指定对齐值为2,有效对齐值为2,因此变量d和c之间填充1个字节,符合6%2=0,数组e依次类推,到最后一个成员时,由于指定对齐值为2字节,需要在最后填充1个字节,因此结构体的总大小为14字节。

当使用#pragma pack(8)将结构体指定对齐值为8时会怎么样呢?代码如下:

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h> #pragma pack(8)
struct s {
char a;
short b;
char c;
int d;
char e[];
};
#pragma pack() int main(int argc, char *argv[])
{
printf("sizeof(struct s):%zd\n", sizeof(struct s));
printf("offsets->a:%zd; b:%zd; c:%zd; d:%zd; e[0]:%zd; e[1]:%zd; e[2]:%zd\n",
offsetof(struct s, a), offsetof(struct s, b),
offsetof(struct s, c), offsetof(struct s, d),
offsetof(struct s, e[]), offsetof(struct s, e[]),
offsetof(struct s, e[])); exit(EXIT_SUCCESS);
}

使用gcc编译,并运行,结果如下:

可以看到结构体的总大小为16字节,而且并不是按照8字节进行对齐,而是按照4字节进行对齐,因为下面这个对齐准则:

有效对齐值=min{自身对齐值,当前指定的pack值}

所以,上面的结构体将按照4字节进行对齐。

6、小节

本文主要简单介绍了C语言中结构体的字节对齐,对其基本的对齐准则以及一些具体例子进行了描述。

参考:

http://www.baike.com/wiki/%E5%AD%97%E8%8A%82%E5%AF%B9%E9%BD%90

https://www.jianshu.com/p/f69652c7df99

https://www.cnblogs.com/clover-toeic/p/3853132.html

C语言字节对齐分析的更多相关文章

  1. [转]C语言字节对齐问题详解

    C语言字节对齐问题详解 转载:https://www.cnblogs.com/clover-toeic/p/3853132.html 引言 考虑下面的结构体定义: typedef struct{ ch ...

  2. C语言字节对齐问题详解

    引言 考虑下面的结构体定义: typedef struct{ char c1; short s; char c2; int i; }T_FOO; 假设这个结构体的成员在内存中是紧凑排列的,且c1的起始 ...

  3. C语言字节对齐问题详解(对齐、字节序、网络序等)

    首先说明一下,本文是转载自: http://www.cnblogs.com/clover-toeic/p/3853132.html 博客园用的少,不知道怎么发布转载文章,只能暂时这样了. 引言 考虑下 ...

  4. C语言字节对齐问题详解【转】

    引言 转自:http://www.cnblogs.com/clover-toeic/p/3853132.html 考虑下面的结构体定义: 1 typedef struct{ 2 char c1; 3 ...

  5. C ~ C语言字节对齐

    1. 什么是对齐? 现代计算机中内存空间都是按照字节(byte)划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型 ...

  6. C语言字节对齐

    转自:http://blog.csdn.net/21aspnet/article/details/6729724 文章最后本人做了一幅图,一看就明白了,这个问题网上讲的不少,但是都没有把问题说透. 一 ...

  7. C语言字节对齐 __align(),__attribute((aligned (n))),#pragma pack(n)

    转载地址 : http://blog.csdn.net/21aspnet/article/details/6729724 一.概念    对齐跟数据在内存中的位置有关.如果一个变量的内存地址正好位于它 ...

  8. C语言字节对齐 __align(),__attribute((aligned (n))),#pragma pack(n)【转】

    转自:https://www.cnblogs.com/ransn/p/5081198.html 转载地址 : http://blog.csdn.net/21aspnet/article/details ...

  9. C++中的字节对齐分析

    struct A { int a; char b; short c; }; struct B { char a; int b; short c; }; #pragma pack(2) struct C ...

随机推荐

  1. git commit 统计

    git log --author="username" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; lo ...

  2. ALBERT+BiLSTM+CRF实现序列标注

    一.模型框架图 二.分层介绍 1)ALBERT层 albert是以单个汉字作为输入的(本次配置最大为128个,短句做padding),两边分别加上开始标识CLS和结束标识SEP,输出的是每个输入wor ...

  3. Mybatis映射文件标签(关于sql)

    Mybatis映射文件 1.接口的全限定名和映射文件的namespace一致 <mapper namespace="com.offcn.dao.UserDao"> 2. ...

  4. ASP.NET Core MVC上传文件

    使用模型绑定上传小文件 HTML代码: <form method="post" enctype="multipart/form-data" asp-con ...

  5. docker在linux上的安装

    docker安装在liunx环境上,我电脑用的是ubuntu系统的,需要下载对应系统的docker,我下载的是社区版,对着官方的命令敲就好了, 地址是:https://docs.docker.com/ ...

  6. DF1协议简述

    DF1协议 1.    概述 可编程控制器(PLC)因编程方便,抗干扰能力强,被广泛应用于各种领域.DF1协议是AB公司可编程控制器系统广泛支持的数据链路层通信协议,各系列可编程控制器及装有RSLin ...

  7. 在系统下文件上传报错:The temporary upload location [/tmp/tomcat.xxx/work/Tomcat/localhost/ROOT] is not valid

    线上的系统中长时间不访问时不能上传文件了,出现如下错误: 2019-03-11 23:37:42.741 ERROR 66505 --- [nio-8081-exec-3] o.a.c.c.C.[.[ ...

  8. 超强的Lambda Stream流操作

    原文:https://www.cnblogs.com/niumoo/p/11880172.html 在使用 Stream 流操作之前你应该先了解 Lambda 相关知识,如果还不了解,可以参考之前文章 ...

  9. HDU5952 dfs+剪枝

    题目分析: 对于给出的n个点和m条边,求这个图的完全联通子图的数量(每次查询的子图的大小为s),对于本题而言,很容易想到的是dfs暴力和这个点相连的所有的点,并且判断这个图是否是度为s 的完全联通子图 ...

  10. ELK安装过程中一些注意的地方

    安装流程比较简单,只需要下载安装包,解压安装包,修改配置文件,然后启动组件即可,但还是遇到一些小问题,这里做一下记录. 各个组件版本号需要保持一样,例如都使用7.1.1版本 es不能以root账户启用 ...