C语言中的内存压缩技术

前言

在整个研究生阶段我都在参与一个LTE协议栈实现的项目,在这个项目中,我们利用一个自己编写的有限状态机框架将协议栈中每一层实现为一个内核模块。我们知道,在编写内核代码时需要考虑内存的使用情况,如果使用内存超出了限制会导致Linux内核崩溃。在我负责的模块中,有的结构体定义非常庞大(几k到十几k),这么大的结构体对于内核编程来说显然有点太大了(在使用内核栈时,有可能造成内核栈溢出),另外,这些结构体可能会通过空中接口传给对等端。于是我们不得不想办法来压缩结构体的内存。

C中的内存对齐和填充

首先来看一段C语言声明变量的代码:(以下假设均在32位机上)

char *p;
char c;
int x; 

如果要问声明这三个变量所占的内存大小,估计很多人会回答4+1+4=9字节。真正的答案应该是4+1+3+4=12bytes,其中多出来的3字节是内存对齐时的填充。C编译为了加快读取速度,对变量所存的地址是有要求的:char型所占内存为1字节,因此char型可以存放在任何地址上;而整形和指针在32位机上只能存放在以4为倍数的地址上;同理,short型只能存放在偶数地址上。因此上面的代码中,指针变量p肯定存放在以4为倍数的地址上,而为了使整形变量x也存放在以4为倍数的地址上,必须在char型变量c后面填充3字节。

那么,我们就可以想到如何减少变量所占内存————改变声明顺序。

char *p;
int x;
char c;

我们再来看看三个变量所占的内存大小为:4+4+1=9。因此,要减少内存的占用,需要先声明所占内存较大的变量即可(当然,是在影响代码的可读性的前提下)。

结构体中的内存对齐和填充

我们都知道结构体的内存分布有一个特点:为了加快访问速度,结构体中的内存是以最宽的变量大小对齐的。比如:

struct foo1 {
    char *p;
    char c;
    long x;
};

因为结构体以4字节对齐,那么结构体中的所有变量的地址都为4的倍数,因此这个结构体实际上相当于:

struct foo1 {
    char *p;
    char c;
    char padding[3];
    long x;
};

上面这点可能是大家都知道的,另外大家也都知道结构体的地址是结构体中第一个变量的地址。那么另外一个问题来了:

struct foo1 {
    char *p;
    char c;
};

请问struct foo1[2]所占内存大小是多少? 大家可能都能猜到答案是2*(4+4)=16字节了。我再来详细解释一下。
结构体数组的第一个元素foo1[0]的地址是结构体中的第一个变量即p的地址,而p的地址只能为4的倍数;那么同理,结构体数组的第二个元素foo1[1]的地址也是结构体中的第一个变量即p的地址,而p的地址也只能为4的倍数,因此很显然结构体foo1实际上相当于:

struct foo1 {
    char *p;
    char c;
    char padding[3];
};

最后,值得注意的是,嵌套的结构体中所有的变量也必须以最宽变量大小对齐。

gcc中的__attribute__ ((packed))

如果你读过内核源码,你很可能已经见过这种形式定义结构体和联合了,我们称之为紧凑型结构体/联合体。 __attribute__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,但是请注意:这是编译器相关的指令。
我们来看看效果:

#include <stdio.h>
int main()
{
    struct foo1
    {
        int x;
        char y;
    }__attribute__ ((__packed__));

    struct foo2
    {
        int x;
        char y;
    };

    printf("sizeof struct foo1: %d\n", sizeof(struct foo1)); //5
    printf("sizeof struct foo2: %d\n", sizeof(struct foo2)); //8
    return 0;
}

当然,也不一定有效,因为这还与结构体变量声明顺序等有关。
另外,gcc中的__attribute__机制还有许多其他的有趣功能,详细内容请查阅gcc文档。

20141208更新

今天发现在某些情况下我们可以利用内存对齐这个特性。我们知道在C语言中,带指针参数的函数无法判断输入参数是否是有效的指针,有一些简单的方法来“解决”这个问题。

  1. 如果参数为结构体指针,那么可以为结构体指针增加一个位字段来表示其类型,并且封装malloc函数,在malloc之后为类型字段置位,在使用指针前,检查该位来判断指针是否有效。
  2. 第二种方法就是利用内存对齐的特性来判断,比如结构体指针的值(即地址值)由于对齐要求必须为8的倍数,那么我们可以判断这个地址是否为8的倍数,不是8的倍数就是非法指针。

参考文献

[1]http://blog.csdn.net/yuwen_dai/article/details/17784109
[2]http://www.cnblogs.com/longdouhzt/archive/2012/11/15/2771351.html

C语言中的内存压缩技术的更多相关文章

  1. C语言中的内存分配与释放

    C语言中的内存分配与释放 对C语言一直都是抱着学习的态度,很多都不懂,今天突然被问道C语言的内存分配问题,说了一些自己知道的,但感觉回答的并不完善,所以才有这篇笔记,总结一下C语言中内存分配的主要内容 ...

  2. C语言中的内存管理

    开始陆续的发一下唐老师视频的笔记吧,顺便带一些正冲哥书的的内容.不能一下都发出来,因为内容发多了自己也受不了,而且发的都是学习视频时候的一些笔记,可能会有一些问题不是很清晰. 先说一下C语言中的内存管 ...

  3. C语言中的内存对齐

    最近看了好多,也编了好多C语言的浩强哥书后的题,总觉的很不爽,真的真的好怀念linux驱动的代码,好怀念那下划线,那结构体,虽然自己还很菜. 同时看了一遍陈正冲老师的C语言深度剖析,收益很多,又把唐老 ...

  4. C语言中堆内存的开辟和释放与内存处理函数

    C语言动态分配内存,malloc的出现就是来弥补静态内存分配的缺点 比如说我们在定义数组的时候,数组的长度必须是一个常量,不能改变的值,假如我事先定义了数组,一旦业务需求发生改变,那么这个数组就不能再 ...

  5. C语言中的内存相关问题

    内存是用来存储数据与程序的,对我们写程序来说非常重要.所以内存对程序来说几乎是本质需求.越简单的程序需要越少的内存,而越庞大越复杂的程序需要更多的内存. 注意:在嵌入式系统中有ROM和RAM两类内存, ...

  6. c 从语言中的内存管理

    在C里,内存管理是通过专门的函数来实现.另外,为了兼容各种编程语言,操作系统提供的接口通常是 C 语言写成的函数声明(Windows 本身也由C和汇编语言写成). 1 分配内存 malloc 函数 需 ...

  7. c语言中的内存分配malloc、alloca、calloc、malloc、free、realloc、sbr

    C语言跟内存分配方式 (1) 从静态存储区域分配.内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在.例如全局变量,static变量. (2) 在栈上创建.在执行函数时,函数内局部变 ...

  8. C语言中动态内存的分配(malloc,realloc)

    动态内存分配:根据需要随时开辟,随时释放的内存分配方式.分配时机和释放时机完全由程序员决定,由于没有数据声明,这部分空间没有名字.无法像使用变量或数组那样通过变量名或数组名引用其中的数据,只能通过指针 ...

  9. C Memory Layout C语言中的内存布局

    在C语言中,内存的主要分为下列几部分: 1. Text/Code Segment 文本/代码区 2. Initialized Data Segments 初始化的数据区 3. Uninitialize ...

随机推荐

  1. htop查看系统负载

    htop 是 Linux 系统中的一个互动进程查看器,可以让用户进行交互式操作,可横向或纵向滚动浏览进程列表,支持鼠标操作.用户可以在安装 htop 来监控服务器的负载. 01.下载 https:// ...

  2. jquery audio player

    <!DOCTYPE html><html lang="en"> <head>  <meta charset="utf-8&quo ...

  3. Could not obtain connection metadata

    用hibernate连接数据库出现错误 2010-3-16 17:23:39, 093 [main] WARN [org.hibernate.cfg.SettingsFactory] - Could ...

  4. iOS开发之拖动图片

    步骤:1.首先创建一个single view application 2.然后添加一个新的cocoa touch class的类 3.添加的类遵守<UIGestureRecognizerDele ...

  5. bzoj4229: 选择

    Description 现在,我想知道自己是否还有选择. 给定n个点m条边的无向图以及顺序发生的q个事件. 每个事件都属于下面两种之一: 1.删除某一条图上仍存在的边 2.询问是否存在两条边不相交的路 ...

  6. log4net--帮助程序员将日志信息输出到各种目标(控制台、文件、数据库等)的工具

    1. log4net库是Apache log4j框架在Microsoft .NET平台的实现,是一个帮助程序员将日志信息输出到各种目标(控制台.文件.数据库等)的工具. 2. Log4net的结构如下 ...

  7. WPF控件中可以用来记录数据用的属性总结

    今天遇到一个问题需要为每个控件tooltip根据不同情况显示多组数据,于是需要用到控件的某个属性暂存这个数据.好吧,现在我找到了3个属性可用,给自己做个备忘. 1.Tag 这个属性是object类型 ...

  8. 【python】中文的输出,打印,文件编码问题解决方法

    直接在python中输入中文的字符串会报编译错误SyntaxError: Non-ASCII character,因为python文件默认编码方式是ASCII.如果想要打印中文字符,有两种方式: 1. ...

  9. struts2笔记03-ActionContext

    1.概念 ActionContext是action的上下文,它包括action执行所需要的对象.struts2对每一个action都会创建一个新的ActionContext实例,同Action一样,是 ...

  10. STL之Vector(不定长数组)

    vector是同一种对象的集合,每一个对象都有一个相应的整数索引值.和string对象一样,标准库将负责管理与存储元素相关的类存. 引入头文件 #include<vector> 1.vec ...