一.内存对齐的初步解说
内存对齐能够用一句话来概括:
“数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上”
比如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个字节。
关于位段结构的部分是比較复杂的。临时我就知道这么多。

windows和Linux内存的对齐方式的更多相关文章

  1. linux内存使用计算方式

    Linux开机后,使用top命令查看,4G物理内存发现已使用的多大3.2G,占用率高达80%以上: Mem: 3889836k total, 3341868k used, 547968k free, ...

  2. 【转】进程间通信方式总结(windows 和linux)

    平时看的书很多,了解的也很多,但不喜欢总结,这不昨天面试的时候被问到了进程间通信的方式,因为没有认真总结过,所以昨天答得不是特别好.现在将linux和windows的进程间通信方式好好总结一下.    ...

  3. Windows与Linux下进程间通信技术比较

    一般我们写的程序都是以单个进程的方式来运行的,比较少涉及到多进程.特别是在windows下,因为Windows是按照线程来分配CPU时间片的,线程是最小的调度单位,所以在Windows下更多的用到多线 ...

  4. 开发问题(一)在windows和linux端口占用问题

    前言 今天在MyEclipse中使用tomcat发现tomcat端口8080竟然被占用了,所以就找了一下解决办法共参考! 在网络程序的调试过程中,经常发生一些出乎意料的事情,比如创建一个TCP服务失败 ...

  5. C语言中内存对齐方式

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

  6. Windows内存管理和linux内存管理

    windows内存管理 windows 内存管理方式主要分为:页式管理,段式管理,段页式管理. 页式管理的基本原理是将各进程的虚拟空间划分为若干个长度相等的页:页式管理把内存空间按照页的大小划分成片或 ...

  7. 关于arm处理器 内存编址模式 与 字节对齐方式 (转)

    转自:http://bavon.bokee.com/5429805.html 在x86+Linux上写的程序,在PC机上运行得很好.可是使用ARM的gcc进行交叉编译,再送到DaVinci目标板上运行 ...

  8. windows与linux之间文件的传输方式总结(转)

    当然,windows与linux之间文件的传输的两种方式有很多,这里就仅仅列出工作中遇到的,作为笔记: 方法一:安装SSH Secure Shell Client客户端 安装即可登录直接拖拉到linu ...

  9. linux与windows 通过SecureCRT进行文件传输方式

    linux与windows 通过SecureCRT进行文件传输方式 方式一:lrzsz是一款在Linux里可代替ftp上传和下载的程序.(小文件推荐,以4G为界限) # rz -bash: rz: c ...

随机推荐

  1. ssh 免密及加密远程脚本实现

    windows_host文件路径:C:\Windows\System32\drivers\etc ssh-copy-id -i ~/.ssh/id-rsa.pub root@xxxxxxx 免密验证操 ...

  2. AngularJS初接触

    todo.json [ { "action": "Buy Flowers", "done": false }, { "action ...

  3. 130.C++经典面试题 52-100

  4. Dom4j 查找节点或属性

    Dom4j  查找节点或属性 例如 1 查找下面xml中的student节点的age属性, xpathstr="/students/student/@age"; 2 查找下面xml ...

  5. 最长上升子序列(LIS)nlogn模板

    参考https://www.cnblogs.com/yuelian/p/8745807.html 注意最长上升子序列用lower_bound,最长不下降子序列用upper_bound 比如123458 ...

  6. ASP.NET中的几种弹出框提示

    B/S不像C/S那样一个MessageBox就可以弹出提示框,不过可以通过js的“Alert”来弹出消息,或者通过一些变种的js方法.下面我给大家介绍几种,希望大家喜欢. 四种弹出框代码: prote ...

  7. [Python] Manipulate Data with Dictionaries in Python

    Dictionaries may be familiar to you as hash maps. In this lesson, you will learn how to create them, ...

  8. matlab中tic和toc使用方法

    tic和toc用来记录matlab命令运行的时间.  tic用来保存当前时间,而后使用toc来记录程序完毕时间. 两者往往结合使用,使用方法例如以下: 程序代码: tic  operations  t ...

  9. modSecurity规则学习(一)——配置文件

    环境:modSecurity3.0,nignx1.13.8 modSecurity配置文件 1.nginx.conf server { listen ; modsecurity on; //启动mod ...

  10. html --- rem 媒体查询

    rem是一种相对长度单位,参考的基准是<html>标签定义的font-size. viewport 做移动端的h5,通常会在HTML文件中指定一个<meta>标签: <m ...