文档章节

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

算法与编程之美
 算法与编程之美
发布于 2013/10/09 19:53
字数 2037
阅读 7085
收藏 124

前言

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

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

示例

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

假设我们现在要用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结构体做如下修改:

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

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

 

© 著作权归作者所有

共有 人打赏支持
算法与编程之美
粉丝 291
博文 91
码字总数 100101
作品 0
成都
程序员
私信 提问
加载中

评论(40)

中山野鬼
中山野鬼

引用来自“EdwardLi”的评论

引用来自“中山野鬼”的评论

上面写错了一个地方。 ((BYTE *)B + .....

多谢解答! 我在实际工作中也有类似的应用。 不过感觉做成一个类是不是更直观些呢。呵呵

除非是模板,否则类的化,会固化方法。这对逻辑的松耦合不能带来任何好处。设计,有时需要紧耦合,有时需要松耦合,其实判断他们该松还是紧,要根据这个设计的来源是否存在关联判定。比如,数据的逻辑提取和实际数据的存储,一个来源业务要求的算法,一个来源于业务所运行的系统,因此需要松耦合,而在一个算法中的逻辑设计,则存在紧耦合。哈。这块,比较绕口令,需要实践体会。
EdwardLi
EdwardLi

引用来自“中山野鬼”的评论

上面写错了一个地方。 ((BYTE *)B + .....

多谢解答! 我在实际工作中也有类似的应用。 不过感觉做成一个类是不是更直观些呢。呵呵
中山野鬼
中山野鬼
上面写错了一个地方。 ((BYTE *)B + .....
中山野鬼
中山野鬼

引用来自“EdwardLi”的评论

引用来自“中山野鬼”的评论

楼主记得,内存对齐的处理逻辑,一定要和计算逻辑分开。有关联的地方使用宏的方式就可以。否则以后你有苦头吃。而且会额外增加计算逻辑的复杂度。

看懂了内存处理和计算逻辑分开部分,但是怎么使用宏来解决这个问题? 能否提供例子啊?

所谓内存对其,其实和内存申请没有关系,只是和具体对象(不是面向对象的对象)的寻址有关系。比如,你要对一个对象进行数据读取或者写入,你总是先要计算地址,然后进行访问。
而计算地址是根据逻辑来的。通过计算地址进行直接存储访问,则存在一个逻辑转换,确保每个数据对齐。这里增加个宏,由此实现分离。
简单的例子,我们逻辑上连续存储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 的宏实现的。其实这里还存在逻辑,但逻辑中存在一个对齐的数值定义。
不同过多介意宏里面有宏,实际编译,这些东西都会被优化掉。但对代码组织,是有很大帮助的。哈
EdwardLi
EdwardLi

引用来自“中山野鬼”的评论

楼主记得,内存对齐的处理逻辑,一定要和计算逻辑分开。有关联的地方使用宏的方式就可以。否则以后你有苦头吃。而且会额外增加计算逻辑的复杂度。

看懂了内存处理和计算逻辑分开部分,但是怎么使用宏来解决这个问题? 能否提供例子啊?
chao_83
chao_83

引用来自“do_mybest”的评论

引用来自“中山野鬼”的评论

引用来自“chao_83”的评论

引用来自“justin_cn”的评论

引用来自“chao_83”的评论

也不差那一位的空间, 这种用法太不直观了.
建议在问题的角度思考,而不是硬件的角度.

个人认为c程序员还是多应该从硬件的角度去思考问题,吝啬内存的使用。

我是指初学者和一般应用程序, 最终目的还是解决问题.
不要人为把问题复杂化, 底层的东西尽量交给编译器.
如果设计很底层的东西, 那我没意见.

有些事情不是底层可以帮你更好的处理的。一个简单的例子,你去设计一个数据结构,比如树吧,对节点的访问逻辑,一旦你固定,则不会有改变,但是每个节点的存储空间的实际访问,则会根据存储方式的改变而改变,通常是用宏的方式,进行调整。这样的调整不会影响整体逻辑,但是会改变数据计算过程中,对数据访问的存储空间

还是野鬼说的更实用

设计学生信息的时候就思考学生;设计树的时候就思考树.
避免内存对齐这种东西扰乱思路, 我就是这个意思.
do_mybest
do_mybest

引用来自“中山野鬼”的评论

引用来自“chao_83”的评论

引用来自“justin_cn”的评论

引用来自“chao_83”的评论

也不差那一位的空间, 这种用法太不直观了.
建议在问题的角度思考,而不是硬件的角度.

个人认为c程序员还是多应该从硬件的角度去思考问题,吝啬内存的使用。

我是指初学者和一般应用程序, 最终目的还是解决问题.
不要人为把问题复杂化, 底层的东西尽量交给编译器.
如果设计很底层的东西, 那我没意见.

有些事情不是底层可以帮你更好的处理的。一个简单的例子,你去设计一个数据结构,比如树吧,对节点的访问逻辑,一旦你固定,则不会有改变,但是每个节点的存储空间的实际访问,则会根据存储方式的改变而改变,通常是用宏的方式,进行调整。这样的调整不会影响整体逻辑,但是会改变数据计算过程中,对数据访问的存储空间

还是野鬼说的更实用
算法与编程之美
算法与编程之美

引用来自“中山野鬼”的评论

引用来自“chao_83”的评论

引用来自“justin_cn”的评论

引用来自“chao_83”的评论

也不差那一位的空间, 这种用法太不直观了.
建议在问题的角度思考,而不是硬件的角度.

个人认为c程序员还是多应该从硬件的角度去思考问题,吝啬内存的使用。

我是指初学者和一般应用程序, 最终目的还是解决问题.
不要人为把问题复杂化, 底层的东西尽量交给编译器.
如果设计很底层的东西, 那我没意见.

有些事情不是底层可以帮你更好的处理的。一个简单的例子,你去设计一个数据结构,比如树吧,对节点的访问逻辑,一旦你固定,则不会有改变,但是每个节点的存储空间的实际访问,则会根据存储方式的改变而改变,通常是用宏的方式,进行调整。这样的调整不会影响整体逻辑,但是会改变数据计算过程中,对数据访问的存储空间

谢谢野鬼, 受益匪浅。
算法与编程之美
算法与编程之美

引用来自“武超凡”的评论

楼主你好:
struct student {
char age;
char sex;
char *name;
};这个在x86_64上默认8字节对齐为16字节长,而
struct student{
char age;
unsigned long name_sex;
};也是16字节,你把sex和name合二为一不是增加了复杂度了吗?更不要说效率了吧,现在age独占8字节,浪费了7字节。
求解。。。

你说的很对,的确是这样的。本文的示例不太合适,本文意在表达利用内存对齐的特性来存储一些信息。
算法与编程之美
算法与编程之美

引用来自“robbins”的评论

这段示例代码在大小端的适配上是否会有问题?

本文涉及到的位运算如(char *)((stu.name_sex) & ~3)和你所说的c语言的结构体位域定义还是有很大区别的,结构体位域定义肯定涉及到big/little endian问题,但是本文提到的位运算不一样,先从内存中获取第一个operand(无论是big endian or little endian,最终都能正确获取该数值),然后再执行位运算的。本文提到的思路的确容易和结构体位域定义混淆。
关于c语言结构体成员变量访问方式的一点思考

前言 上篇博文(关于c语言结构体偏移的一点思考)对c语言中结构体偏移做了一些思考,发现博文中还有一些小的问题,没有描述的足够清楚,所以才萌生了本篇博文的想法。 为什么不直接将本篇博文作...

算法与编程之美
2013/06/27
0
11
关于c语言结构体偏移的一点思考

前言 相信大家在c语言程序开发的过程一定都使用过结构体,那么不知你对结构体中成员变量偏移这块是如何理解的?本文将和大家一起分享下,本人最近关于c语言中结构体偏移的一些思考和总结。 ...

算法与编程之美
2013/06/25
0
23
深入理解C语言结构体成员变量内存分配

欢迎点击「算法与编程之美」↑关注我们! 本文首发于微信公众号:"算法与编程之美",欢迎关注,及时了解更多此系列博客。 1 问题描述 在学习C语言的时候,我们都会频繁的接触到结构体,使用结...

算法与编程之美
04/05
0
0
C语言编程学习之意向不到的内存对齐问题

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
05/14
0
0
关于c语言等于运算符的一点思考

前言 前些天发表了一篇博文关于c语言内存地址对齐的一点思考,引起了大家比较热烈的讨论,的确在这篇文章中示例的选择不是很恰当,示例有很多不严谨的地方,博客的评论中得到了很多同学的指点...

算法与编程之美
2013/10/13
0
2

没有更多内容

加载失败,请刷新页面

加载更多

tomcat编译超过64k大小的jsp文件报错原因

  今天遇到一个问题,首先是在tomcat中间件上跑的web项目,一个jsp文件,因为代码行数实在是太多了,更新了几个版本之后编译报错了,页面打开都是报500的错误,500的报错,知道http协议返回...

SEOwhywhy
15分钟前
0
0
flutter http 请求客户端

1、pubspec文件管理Flutter应用程序的assets(资源,如图片、package等)。 在pubspec.yaml中,通过网址“https://pub.dartlang.org/packages/http#-installing-tab-”确认版本号后,将http(0...

渣渣曦
16分钟前
0
0
Django基本命令及moduls举例

一、Django基本命令 1.创建项目 django-admin.py startproject mysite 创建后的项目结构:- mysite - mysite #对整个程序进行配置 - init #导入包专用- settings ...

枫叶云
31分钟前
4
0
zabbix安装

rpm -ivh http://repo.webtatic.com/yum/el6/latest.rpm 安装jdk rpm -ivh (自行在网上下载rpm包) 安装php并修改相应参数 yum -y install php56w php56w-gd php56w-mysqlnd php56w-bcmath......

muoushi
32分钟前
3
0
MySQL自增属性auto_increment_increment和auto_increment_offset

MySQL的系统变量或会话变量auto_increment_increment(自增步长)和auto_increment_offset(自增偏移量)控制着数据表的自增列ID。 mysql> show tables;Empty set (0.00 sec)mysql> CREATE TA......

野雪球
今天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部