关于c语言内存地址对齐的一点思考

前言

相信你们对内存对齐这个概念必定都比较熟悉,本文将介绍,如何利用内存对齐这一特性来作一些有意思的探索。html

至于为何要使用内存对齐,这是一个比较复杂的问题,简单来讲就是提升cpu access memory的性能,后续有时间就内存对齐这个问题,展开详细的探讨。linux

示例

首先来看一个简单的示例:算法

假设咱们如今要用c语言作一个简单的学生信息管理系统,学生结构体有三个基本属性,分别是年龄(0-100),性别(male:0, female:1),姓名(字符串大小10之内)。在编码以前,咱们须要对系统进行设计,而设计阶段最重要的莫过于数据结构。本题涉及的结构体很是简单,结构体student定义以下:编程

struct student {
    char age;
    char sex;
    char *name;
};

 

相信上面这个结构体是大多数人得出的结果,那么这个结构体的定义是否是最优的呢或者说是内存利用率是最高的呢?数组

分析

在具体的探讨以前,咱们先来介绍一下关于内存对齐的一个小知识点:若是某变量内存地址4字节对齐,则该地址的低2位必为0。这个应该比较好理解,由于4字节对齐,内存地址必须为4的倍数,因此低2位必然为0,不然不能知足要求。数据结构

在了解这个知识点以后,咱们再来对上面的student结构体作一点修改。性能

咱们定义一个字符数组name用来存放学生姓名,且该结构体4字节对齐,定义以下:优化

 char name[10] __attribute__ ((aligned(4))) = "hellooooo";

 

从上面的知识点,咱们知道字符数组name的低两位为0,换句话说,这两位是没有用到的,既然如此,咱们是否能够考虑利用这两位来作一些文章呢?编码

咱们对上面的student结构体作以下修改:spa

struct student{
    char age;
    unsigned long name_sex;
};

 

咱们将sex和name字段合二为一,用一个字段name_sex来表示,这样作是否可行呢?

答案是可行的。

#define stu_get_name(stu) ((char *)((stu.name_sex) & ~3))
#define stu_get_sex(stu) ((stu.name_sex) & 1)

char name[10] __attribute__ ((aligned(4))) = "hellooooo";

struct student stu;
stu.age = 10;
stu.name_sex = (unsigned long)name | 1;

printf("name: %s \n", stu_get_name(stu));
printf("sex: %d \n", stu_get_sex(stu));

 

咱们先定义了一个字符数组name且该数组内存地址4字节对齐,即低两位为0。接着咱们将该地址的第0位置1用来保存学生性别字段,而后赋值为student结构体的name_sex字段。

那么咱们如何获得student结构体的name字段的值呢?答案很简单,只须要将name_sex字段的低两位置0就能够获得咱们所须要的name字段值,而name_sex的第0位即((stu.name_sex) & 1)就是咱们student结构体中的sex字段,上述示例中,sex值为1,即性别为female。

至此,咱们利用内存地址对齐的特性,修改了咱们示例最早提出的student结构体。

本文中咱们利用4字节内存对齐的低两位为0这一特性,将其最低位用来存放学生性别,从而达到高效的利用内存。

总结

本文的重点并不在于介绍如何设计一个学生信息管理系统,示例中的结构体只是为了说明内存对齐的应用,借助学生信息管理系统这样的一个场景来介绍,咱们在设计结构体的时候,利用内存对齐的特性,能够更加灵活的设计咱们所须要的结构体,从而达到对内存的高效利用。

注1:如对内存对齐的应用感兴趣,可进一步参考linux内核中rbtree的设计,其rb_parent_color字段就是利用了内存对齐的特性,将结点的父结点parent以及该结点的颜色color两个字段合二为一。

注2:本空间《**思考》系列博文都是基于linux内核,用平实的语言和简单的示例,描述linux内核中一些比较有意思的设计,但愿可以和你们一块儿探索linux内核设计的奥秘。

注3:@中山野鬼 老师的两句点评很是精辟,受益不浅,和你们一块儿分享下,前辈老是可以一语道破个中玄机:

楼主记得,内存对齐的处理逻辑,必定要和计算逻辑分开。有关联的地方使用宏的方式就能够。不然之后你有苦头吃。并且会额外增长计算逻辑的复杂度。
有些事情不是底层能够帮你更好的处理的。一个简单的例子,你去设计一个数据结构,好比树吧,对节点的访问逻辑,一旦你固定,则不会有改变,可是每一个节点的存储空间的实际访问,则会根据存储方式的改变而改变,一般是用宏的方式,进行调整。这样的调整不会影响总体逻辑,可是会改变数据计算过程当中,对数据访问的存储空间
所谓内存对其,其实和内存申请没有关系,只是和具体对象(不是面向对象的对象)的寻址有关系。好比,你要对一个对象进行数据读取或者写入,你老是先要计算地址,而后进行访问。 而计算地址是根据逻辑来的。经过计算地址进行直接存储访问,则存在一个逻辑转换,确保每一个数据对齐。这里增长个宏,由此实现分离。 简单的例子,咱们逻辑上连续存储24位像素,假设(一般一行内不会如此)咱们但愿每一个像素的存储是32位对齐。那么你访问每一个像素,存在(x,y,z)三个变量,x,y是一个平面的列数,和行数,Z是层级数。 假设B是基地址。则以下操做 #define image_pixel_byte_size 4 #define get_bias(x,y,z) ((z) * X * Y + (y) * Y + x) #define get_store(B,n) ((BYTE)B + n * image_pixel_byte_size) #define get_pixel(p,x,y,z) get_store(p,get_bias(x,y,z)) 上面,实际内存对齐操做,是经过 get_store 的宏实现的。其实这里还存在逻辑,但逻辑中存在一个对齐的数值定义。 不一样过多介意宏里面有宏,实际编译,这些东西都会被优化掉。但对代码组织,是有很大帮助的。哈
除非是模板,不然类的化,会固化方法。这对逻辑的松耦合不能带来任何好处。设计,有时须要紧耦合,有时须要松耦合,其实判断他们该松仍是紧,要根据这个设计的来源是否存在关联断定。好比,数据的逻辑提取和实际数据的存储,一个来源业务要求的算法,一个来源于业务所运行的系统,所以须要松耦合,而在一个算法中的逻辑设计,则存在紧耦合。哈。这块,比较绕口令,须要实践体会。

注4:后续仍是要对本文的示例作一些修改,本文的示例的确很不恰当,不过仍是可以清晰的表达个人意思;

注5:本文的评论也值得你们阅读和思考,不少知识点要想完全的搞明白须要很是深厚的功底,面对别人的质疑你是否可以从原理上说明白,是一项挑战;

引用

【1】http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html

【2】http://stackoverflow.com/questions/381244/purpose-of-memory-alignment

【3】http://en.wikipedia.org/wiki/Data_structure_alignment

若是您对算法或编程感兴趣,欢迎扫描下方二维码并关注公众号“算法与编程之美”,和您一块儿探索算法和编程的神秘之处,给您不同的解题分析思路。

相关文章
相关标签/搜索