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

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

前言

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

至于为什么要使用内存对齐,这是一个比较复杂的问题,简单来说就是提高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


目录
相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
20天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
31 3
|
1月前
|
存储 编译器 C语言
C语言:数组名作为类型、作为地址、对数组名取地址的区别
在C语言中,数组名可以作为类型、地址和取地址使用。数组名本身代表数组的首地址,作为地址时可以直接使用;作为类型时,用于声明指针或函数参数;取地址时,使用取地址符 (&),得到的是整个数组的地址,类型为指向该类型的指针。
|
1月前
|
编译器 程序员 C语言
深入C语言:动态内存管理魔法
深入C语言:动态内存管理魔法
|
1月前
|
存储 C语言
C语言:设置地址为 0x67a9 的整型变量的值为 0xaa66
在C语言中,可以通过指针操作来实现对特定地址的访问和赋值。要将地址为 0x67a9 的整型变量值设为 0xaa66,可以先定义一个指向该地址的指针,并通过该指针对该内存位置进行赋值操作。需要注意的是,直接操作内存地址具有一定风险,必须确保地址合法且可写。代码示例应考虑字节序及内存对齐问题。
|
1月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
1月前
|
C++
析构造函数就是为了释放内存,就是在局部指针消失前释放内存,拷贝构造函数就是以构造函数为模块,在堆里面新开一块,同一个变量在堆里面的地址
本文讨论了C++中构造函数和析构函数的作用,特别是它们在管理动态内存分配和释放中的重要性,以及如何正确地实现拷贝构造函数以避免内存泄漏。
37 2
|
1月前
|
存储 安全 NoSQL
driftingblues9 - 溢出ASLR(内存地址随机化机制)
driftingblues9 - 溢出ASLR(内存地址随机化机制)
35 1
|
1月前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
22天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0