1、什么是内存对齐

还是用一个例子带出这个问题,看下面的小程序,理论上,32位系统下,int占4byte,char占一个byte,那么将它们放到一个结构体中应该占4+1=5byte;但是实际上,通过运行程序得到的结果是8 byte,这就是内存对齐所导致的。

//32位系统
#include<stdio.h>
struct{
int x;
char y;
}s; int main()
{
printf("%d\n",sizeof(s); // 输出8
return 0;
}

现代计算机中内存空间都是按照 byte 划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐。

2、为什么要进行内存对齐

尽管内存是以字节为单位,但是大部分处理器并不是按字节块来存取内存的.它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度.

现在考虑4字节存取粒度的处理器取int类型变量(32位系统),该处理器只能从地址为4的倍数的内存开始读取数据。

假如没有内存对齐机制,数据可以任意存放,现在一个int变量存放在从地址1开始的连续四个字节地址中,该处理器去取数据时,要先从0地址开始读取第一个4字节块,剔除不想要的字节(0地址),然后从地址4开始读取下一个4字节块,同样剔除不要的数据(5,6,7地址),最后留下的两块数据合并放入寄存器.这需要做很多工作.

简单的说内存对齐能够提高 cpu 读取数据的速度,减少 cpu 访问数据的出错性(有些 cpu 必须内存对齐,否则指针访问会出错)

现在有了内存对齐的,int类型数据只能存放在按照对齐规则的内存中,比如说0地址开始的内存。那么现在该处理器在取数据时一次性就能将数据读出来了,而且不需要做额外的操作,提高了效率。

3、内存对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。

有效对其值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。

了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:

(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。

(3) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。

下面给出几个例子以便于理解:

//32位系统
#include<stdio.h>
struct
{
int i;
char c1;
char c2;
}x1; struct{
char c1;
int i;
char c2;
}x2; struct{
char c1;
char c2;
int i;
}x3; int main()
{
printf("%d\n",sizeof(x1)); // 输出8
printf("%d\n",sizeof(x2)); // 输出12
printf("%d\n",sizeof(x3)); // 输出8
return 0;
}

以上测试都是在Linux环境下进行的,linux下默认#pragma pack(4),且结构体中最长的数据类型为4个字节,所以有效对齐单位为4字节,下面根据上面所说的规则以s2来分析其内存布局:

首先使用规则1,对成员变量进行对齐:

sizeof(c1) = 1 <= 4(有效对齐位),按照1字节对齐,占用第0单元;

sizeof(i) = 4 <= 4(有效对齐位),相对于结构体首地址的偏移要为4的倍数,占用第4,5,6,7单元;

sizeof(c2) = 1 <= 4(有效对齐位),相对于结构体首地址的偏移要为1的倍数,占用第8单元;

然后使用规则2,对结构体整体进行对齐:

s2中变量i占用内存最大占4字节,而有效对齐单位也为4字节,两者较小值就是4字节。因此整体也是按照4字节对齐。由规则1得到s2占9个字节,此处再按照规则2进行整体的4字节对齐,所以整个结构体占用12个字节。

根据上面的分析,不难得出上面例子三个结构体的内存布局如下:

#pragma pack(n)

不同平台上编译器的 pragma pack 默认值不同。而我们可以通过预编译命令#pragma pack(n), n= 1,2,4,8,16来改变对齐系数。

例如,对于上个例子的三个结构体,如果前面加上#pragma pack(1),那么此时有效对齐值为1字节,此时根据对齐规则,不难看出成员是连续存放的,三个结构体的大小都是6字节。

如果前面加上#pragma pack(2),有效对齐值为2字节,此时根据对齐规则,三个结构体的大小应为6,8,6。内存分布图如下:

经过上面的实例分析,大家应该对内存对齐有了全面的认识和了解,在以后的编码中定义结构体时需要考虑成员变量定义的先后顺序了。

参考资料:
http://light3moon.com/2015/01/19/[%E8%BD%AC]%20%E5%86%85%E5%AD%98%E5%AF%B9%E9%BD%90/

C/C++内存对齐详解的更多相关文章

  1. C语言内存对齐详解(2)

    接上一篇:C语言内存对齐详解(1) VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式.VC 中提供了#pr ...

  2. C语言内存对齐详解(3)

    接上一篇:C语言内存对齐详解(2) 在minix的stdarg.h文件中,定义了如下一个宏: /* Amount of space required in an argument list for a ...

  3. C语言内存对齐详解

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

  4. [转]C++结构体|类 内存对齐详解

    内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址).访问数据(表现为CPU读取数据)的一种方式,包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐 . 为什么需要内存对齐?对 ...

  5. C语言内存对齐详解(1)

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

  6. 3.c语言结构体成员内存对齐详解

    一.关键一点 最关键的一点:结构体在内存中是一个矩形,而不是一个不规则形状 二.编程实战 #include <stdlib.h> #include <stdio.h> stru ...

  7. 【转载】图说C++对象模型:对象内存布局详解

    原文: 图说C++对象模型:对象内存布局详解 正文 回到顶部 0.前言 文章较长,而且内容相对来说比较枯燥,希望对C++对象的内存布局.虚表指针.虚基类指针等有深入了解的朋友可以慢慢看.本文的结论都在 ...

  8. Tomcat内存设置详解

    Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 JVM在 ...

  9. Tomcat内存溢出详解【转载】

    本文转载自 http://elf8848.iteye.com/blog/378805 Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryEr ...

随机推荐

  1. 查询是否sci或者ei收录

    1,如果查文章是不是被SCI检索:进入ISI Web of Knowledge,选择Web of Science 数据库,进行查询:2,如果查文章是不是被EI检索:进入EI Village, 选择Co ...

  2. collectd+infludb+grafana实现tomcat JVM监控

    前提条件:已安装好java环境,tomcat,influxdb和collectd.本文暂不提供以上内容的安装步骤 系统环境:centos7 原理:开启tomcat的jmx端口,使用collectd的c ...

  3. hive with as 语法

    简介 with...as...需要定义一个sql片段,会将这个片段产生的结果集保存在内存中, 后续的sql均可以访问这个结果集和,作用与视图或临时表类似. 语法说明 with...as...必须和其他 ...

  4. Unity射击实例讲解—主角创建

    前言: 经过三分钟的思考决定换个标题,这两天其实游戏制作进度推了大半了,加入了许多自我创作的素材,不过想一想用来讲解的实例不该这么花哨,决定还是参照我的一些教材做一些简单的示例不然要说的东西太多,本人 ...

  5. Flink任务暂停重启

    查看正在进行的任务 ./flink list 取消job并保存状态 ./flink cancel -s jobid 重启job ./flink run -s savepointPath -c 主类 x ...

  6. 赶紧收藏!Spring MVC 万字长文笔记,我愿奉你为王者笔记!

    Spring MVC Spring MVC是目前主流的实现MVC设计模式的企业级开发框架,Spring框架的一个子模块,无需整合Spring,开发起来更加便捷. 什么是MVC设计模式? 将应用程序分为 ...

  7. python简单爬去前程无忧信息招聘

    import sys reload(sys) sys.setdefaultencoding('utf-8') import requests import csv from BeautifulSoup ...

  8. C# IAsyncEnumerable Linq使用

    NET Core 3.0和C# 8.0最激动人心的特性之一就是IAsyncEnumerable<T>(也就是async流).但它有什么特别之处呢?我们现在可以用它做哪些以前不可能做到的事? ...

  9. Core3.0使用Caching.Memory

    前言 参考链接: 使用缓存:https://www.cnblogs.com/gygg/p/11275417.html 过期时间:https://www.cnblogs.com/maijin/p/704 ...

  10. [.NET] - 基础知识 - .NET Overview

    .NET Framework是有一个Framework Class Libray(FCL)和一个Common Language Runtim环境构成的,它 提供一个一致的面向对象的编程环境,而无论对象 ...