C语言中的内存压缩技术

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

posted @ 2014-11-21 00:12  野风鼓瑟震山岗  阅读(1005)  评论(0编辑  收藏  举报