在需要计算结构体大小的时候,涉及到的一个问题就是其对齐模数

  计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。

  也就是说对齐模数就是这个数据类型占用的空间大小。

  关于结构体长度的计算,我查了一下,有两种理解的方式:

方式一:

  当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1(类型S强于T)的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。

struct A{

    short a; //k为1

    int b; //k为4

    char c; //k为1

    double d; //k为8

}a;

   ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。那么结构体本身也有对齐要求,ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松,可以更严格。

  也就是说,结构体的模数应该是字段中最强字段类型模数的整数倍。

 所以上面的代码对应的内存布局图应该是这样:

  int类型强于sort类型,则int变量的首地址应该是4的倍数;double的类型强于char,double的首地址就应该是8的倍数。

  这里,padding是填充区,注意,填充区就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。只有当结构体中的成员一种类型S的对齐模数与另一种类型T的对齐模数不一致的时候,才可能产生填充区。

在上面的结构体中,a与b之间填充了两个空间,b的类型强于c,所以b到c不需要填充空间,c的前面已经占用了8个字节,而c本身还要有1个字节的空间即位置这时到了9,所以c到d还要填充7个空间,即:

打印地址:

所以:结构体a占用的内存空间大小应该是24。

但而如果把结构体A的位置改变一下:

struct B{
short a; //模数为2
char c; //模数为1
int b; //模数为4
double d; //模数为8
}b;

那么其对应的内存分布发生了改变:

打印结果:

综上,可以得出:

  1. 先计算变量。结构体中变量的位置,必须是对齐模数的整数倍,不是整数倍则会分配填充区进行填充。
  2. 再计算结构体。结构体的长度是对齐模数和填充区的和。

但此时,还要考虑到结构体自身的对齐模数(结构体也是基本数据类型)。他的模数是#pragma pack定义的模数与结构体内部最大的基本数据类型成员长度中数值较小者。结构体的长度应该是该模数的整数倍。

如下面的示例

struct B{
int b; //模数为4
char a; //模数为1
}b;

此时,结构体中的变量的总长度为5,按上面的想法,得出此结构体的长度应该是最强类型int的倍数,则为4 * 2 = 8:

是的,就是8,但是,如果加上编译选项#pragma pack预处理指令:

#pragma pack(push, 2)
struct B{
int b; //模数为4
char a; //模数为1
}b;
#pragma pack(pop)

此时成员b的对其模数应该以2为主,a的是1,小于2,则继续以1的对齐模数为主。但结构体的模数k应该是编译编译选项与结构体内部数据类型最强的成员长度中数值中的较小者,这里是2,所以a的长度就变成了6(2的倍数):

  在实际开发中,通过指定/Zp编译选项或者在代码中用#pragma pack指令来更改编译器的对齐规则。比如指定/Zpn(VC7.1中n可以是1、2、4、8、16)就是告诉编译器最大对齐模数是n。

  在这种情况下,所有小于等于n字节的基本数据类型的对齐规则与默认的一样,但是大于n个字节的数据类型的对齐模数被限制为n。如果n = 1,那么结构体的大小就是各个字段的大小之和,在Moses中定义结构体的地方随处可见,这样做可以减少结构体所占用的内存空间。

#pragma pack(push, 1)
struct B{
int b; //模数为4
char a; //模数为1
}b;
//此时结构体变量b的长度就为5
#pragma pack(pop)

方式二:

  1. 先计算变量。根据对齐模数计算结构体变量中的起始地址,必须是对齐模数的整数倍,不是整数倍会自动补齐。
  2. 再计算结构体。根据结构体的对齐模数计算结构体的大小。结构体的对齐模数是#pragma pack定义的模数与结构体内部最大的基本数据类型成员长度中数值较小者。结构体的长度应该是该模数的整数倍。(但这里要注意如果有预处理指令定义时,变量的模数如果大于n,则按n的对齐规则)

按照这种方式:

struct A{
short a; //模数为2
int b; //模数为4
char c; //模数为1
double d; //模数为8
}a;

1.先计算结构体中成员的地址:

  a:地址为0,是其模数2的倍数,所以不再补齐。

  b:如果不对齐,则地址为2。不是模数的整数倍,应补齐2个空间(其实也就是方式一中的填充区),地址变成4。

  c:此时地址为8,是1的整数倍,不再补齐。

  d:如果不对齐,则地址为9。所以应补齐8个空间,地址变成16

此时结构体中成员占据的空间为24

通过打印地址也可验证:

2.计算结构体模数:

此时没有定义编译器指令,所以,结构体模数应该是其成员中最大占用空间类型的模数,也就是8,其大小为24,是8的倍数,所以结构体不再对齐。

对于这两种方式,我觉得方式二可能更好理解一些,但是要注意考虑到编译指令的问题,最后得出:

1.当没有定义编译指令n时:

  结构体长度= 结构体内成员对齐后占用的空间之和(两两对比);

  之后还需验证结构体地址是否是成员最强类型变量模数k的的整数倍。

2.当定义了编译指令n时:

  如果类型强度小于n那么还是按照成员的对齐规则。如果是大于n则需按照n的对齐规则。

  同样在之后还是要验证结构体地址是否是对齐模数的整数倍。如果不是,则结构体模数同样也要进行对齐。

结构体struts的长度的更多相关文章

  1. C# Struct结构体里数组长度的指定

    typedef struct Point{ unsigned short x; unsigned short y; }mPoint;//点坐标 typedef struct Line{ mPoint ...

  2. 失落的C语言结构体封装艺术

    Eric S. Raymond <esr@thyrsus.com> 目录 1. 谁该阅读这篇文章 2. 我为什么写这篇文章 3.对齐要求 4.填充 5.结构体对齐及填充 6.结构体重排序 ...

  3. Linux C 程序 预处理,结构体(13)

    C语言预处理,结构体 C语言预处理命令1.宏定义 1.无参数宏 #define 标识符 字符串 #代表本行是编译预处理命名 习惯上,宏定义大写 代替一个字符串,介绍重复书写某个字符串的工作量 有意义的 ...

  4. (转)失落的C语言结构体封装艺术

    目录1. 谁该阅读这篇文章 2. 我为什么写这篇文章 3.对齐要求 4.填充 5.结构体对齐及填充 6.结构体重排序 7.难以处理的标量的情况 8.可读性和缓存局部性 9.其他封装的技术 10.工具 ...

  5. 关于C语言中结构体中的结构体成员导致的字节对齐问题

    关于结构体的字节对齐是什么,就不赘述,再此附上一篇文章,介绍字节对齐:http://www.linuxsong.org/2010/09/c-byte-alignment/ 这里的结构体字节对齐的数据类 ...

  6. C++结构体中sizeof

    说明: 结构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题.这些问题在平时编程的时候也确实不怎么用到,但在一些笔试面试题目中出是常常出现,一.解释 现代计 ...

  7. C学习之结构体

    结构体(struct) 结构体是由基本数据类型构成的.并用一个标识符来命名的各种变量的组合,结构体中可以使用不同的数据类型. 1. 结构体说明和结构体变量定义 在Turbo C中, 结构体也是一种数据 ...

  8. C#中结构体定义并转换字节数组

    最近的项目在做socket通信报文解析的时候,用到了结构体与字节数组的转换:由于客户端采用C++开发,服务端采用C#开发,所以双方必须保证各自定义结构体成员类型和长度一致才能保证报文解析的正确性,这一 ...

  9. <摘录>字节对齐与结构体大小

    说明: 结 构体的sizeof值,并不是简单的将其中各元素所占字节相加,而是要考虑到存储空间的字节对齐问题.这些问题在平时编程的时候也确实不怎么用到,但在一 些笔试面试题目中出是常常出现,对sizeo ...

随机推荐

  1. Windows zabbix监控远程进程实现机制

    最近负责zabbix监控部署方面的工作,需要完成本地服务端监控远程虚拟机的运行状态(CPU.打开的进程等),与大家分享下我的实现方法. (1) 首先,需要实现记录zabbix客户端的进程的批处理:za ...

  2. jira任务批量操作示例

    1. 获取jira任务编号 使用字段提取工具获取编号,如WQBNEWSDLDL-347,WQBNEWSDLDL-348: 字段提取正则表达式:(?<2>[\[[](?<1>[A ...

  3. Apkplug 开发常见问题解答

    Android M (6.0) generatePackageInfo 偶现错误 出现这个现象之后会导致 BundleActivator.start() 方法不会被调用 6.0 方法签名 public ...

  4. Java网络编程之流——流、过滤器、阅读器和书写器

    Java的I/O建立于流(Stream)之上.输入流读取数据:输出流写入数据.所有的输出流都有相同的基本方法来写入数据,所有输入流也使用相同的基本方法来读取数据.在创建流之后,你通常可以忽略在读写时的 ...

  5. Java:批量插入、修改数据到数据库中的用法

    在java中使用JDBC实现批处理的对象一般是使用PrepareStatement对象. 如何使用: Class.forName("Oracle.jdbc.driver.OracleDriv ...

  6. mysqlroot密码忘记了,修改root密码

    1,停止MYSQL服务,CMD打开DOS窗口,输入 net stop mysql 2,在CMD命令行窗口,进入MYSQL安装目录 比如E:\Program Files\MySQL\MySQL Serv ...

  7. Pointcut is malformed: Pointcut is not well-formed: expecting 'identifier' at character position 0 ^

    错误提示: 解决方法:指定execution 在执行目标方法之前指定execution 例如: import org.aspectj.lang.annotation.Aspect; import or ...

  8. Oracle分页查询

    1.无ORDER BY排序的写法.(效率最高) 经过测试,此方法成本最低,只嵌套一层,速度最快!即使查询的数据量再大,也几乎不受影响,速度依然! sql语句如下: SELECT * FROM (Sel ...

  9. 改变this指针的apply,call,bind的区别

    apply.call 在 javascript 中,call 和 apply 都是为了改变某个函数运行时的上下文(context)而存在的,换句话说,就是为了改变函数体内部 this 的指向. Jav ...

  10. android 横向滚动条

    /*** * 横向滚动条,修改版,从左向右滚动,支持html代码和html里面的网络图片 */public class MarqueeView extends LinearLayout { priva ...