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. 连接Linux下 XAMPP集成环境中部署的禅道的数据库MariaDB

    用mysql数据库工具连接linuxmysql环境,但是会遇到连接失败的问题,如下所示: 这就需要涉及到另外的问题了,需要我们打开mysql的连接授权,具体的操作步骤如下: 1)在xshell里进入m ...

  2. JavaScript 动态脚本

    动态脚本,指的是在页面加载时不存在,但将来的某一个时刻通过修改DOM动态添加的脚本. <script type="text/javascript"> function ...

  3. App开发(Android与php接口)之:短信验证码

    最近和同学们一起开发一个自主项目,要用到短信验证码,在网上搜索了很久,看到一个推荐贴,提到了很多不错的短信服务商.经过测试,帖子中提到的服务商他们的短信到达率和到达速度也都不错.最后,由于经费问题,我 ...

  4. MySQL中/*!40100注释

    MySQL中的注释 MySQL中的注释有三种: # 注释内容 -- 注释内容 /* 注释内容*/ 但是,在导出的SQL文件中,也会看到类似如下内容的注释: CREATE DATABASE `blog` ...

  5. elecworks 图框管理器

    图框管理器中存储的是图纸模板(图框),新建图框的步骤如下: 1 数据库---图框管理器----新建 2 打开图框属性设置窗口,设置图框属性,设置好之后点击确定 3 右击图框图标---打开(进入图框绘制 ...

  6. Obj-C的hello,world 1

    不得不说,Obj-C所谓的中缀表达式真的蛮奇怪的,当无参或者只有一个参数时看起来还不错: //无参数的方法 -(void) say; [employee say]; //只有一个参数的方法 -(voi ...

  7. CentOS6.5下VNC Server远程桌面配置详解

    参考文献: (总结)CentOS Linux下VNC Server远程桌面配置详解 远程桌面连接工具VNC——license Key 我的下载地址为 太平洋下载 VNC连接黑屏的问题 centos 6 ...

  8. map,vector 等容器内容的循环删除问题(C++)

    map,vector 等容器内容的循环删除问题(C++) map,vector等容器的循环删除不能用普通的方法删除: for(auto p=list.begin();p!=list.end();p++ ...

  9. android 之ViewStub

    在开发应用程序的时候,经常会遇到这样的情况,会在运行时动态根据条件来决定显示哪个View或某个布局.那么最通常的想法就是把可能用到的View都写在上面,先把它们的可见性都设为View.GONE,然后在 ...

  10. 2019.03.29 bzoj5463: [APIO2018] 铁人两项(圆方树+树形dp)

    传送门 题意简述:给你一张无向图,问你满足存在从a−>b−>ca->b->ca−>b−>c且不经过重复节点的路径的有序点对(a,b,c)(a,b,c)(a,b,c) ...