一、结构的存储分配
在座各位,肯定都明白机器内存分配对程序的运行效率有多重要。今天,首先我们就来梳一遍结构的存储分配问题。编译器按照成员列表的顺序,依次给每个成员分配空间。只有当存储成员时满足正确的边界对齐要求时,成员之间才可能出现用于填充的额外空间。系统禁止编译器在一个结构的起始位置跳过几个字节来满足边界对齐的要求,因此所有结构的存储位置必须是结构中边界要求最严格的数据类型所要求的位置。
如何优化内存的利用率呢?
在声明中对结构的成员进行排列,让那些对边界要求最严格的成员最先出现。我们知道这种做法可以人为大限度的减少因边界对齐而带来的空间损失。如何?现在就举一个例子:
struct example1{
char a;
int b;//假设该机器整形长度为4
char c;
}
上例,每个结构占据十二个内存的字节空间,但实际只用6个。
struct example2{
int a;
char b;
char c;
}
example2只占用八个字节的内存空间,节省了33%。但在实际工程操作中我们完全有理由为了可读性和可维护性将结构内部更相关的成员放在一起。当程序创建成百上千个结构时,减少浪费就显得尤为重要。如果我们必须确定某个成员的实际位置,应该考虑边界对齐因素,此时很难使用sizeof操作符。这种情况下可以使用offsetof宏(定义于stddef.h).
offsetof( type , member )
type 是结构类型,member是你需要的成员名。这个表达式返回一个值,表示这个指定成员开始存储的位置距离结构开始存储的位置偏移几个字节。例如对上面的example1声明而言:
offsetof( struct example1 , b )
返回的是4.
二、动态存储分配
c函数库提供两个函数,malloc和free,分别用于执行动态存储分配和释放。两个函数原型如下所示,它们都在头文件stdlib.h中声明。
void *malloc( size_t size);
void free( void *pointer);
三、我们为什么使用动态内存分配
设想这样一个场景,一个旅馆有大量有限的客房,在旅游淡季的时候,暴露出我们第一个缺点:清洁阿姨要打扫大量的空房间,增大了酒店的维护成本。在旅游旺季的时候,旅馆爆满,此时暴露出我们第二个缺点,当大巴车运送来一车旅客的时候,我们痛苦的发现,没有足够的房间来安排此时一整辆车的旅客,要避免这种情况,我们可以把酒店建的足够大,但此时它的第一个缺点就加速恶化。解决这个方法逻辑很简单,只要在人们脑海中形成“客房永远不会满”这个概念,这就诱使他们不去选择建设有限容量的旅馆(典型例子见离散数学-布达佩斯大饭店)。我们可以将酒店的旅馆当作实际程序操作中的数组,建立一个永远不会溢出的数组非常诱人,即实现动态存储分配,那我们如何来完成这个永不溢出魔法呢?
现在我们解释原理,一个永远不会客满的旅店是什么样的?如何实现?我们通过借用的方法,维护一块居民地,当来客人的时候我们借用当地居民的空房间安排客人,客人离开的时候把房间的使用权还给居民,房间不停的借入和借出此时就很难产生没地方安排一车大巴旅客的问题。
同理,malloc和free函数一同维护一块内存池,当我们需要内存空间时,malloc函数就从系统内存为我们找到合适的空间并返回指针,当我们不需要此块内存时就可以用free函数释放将使用权交由系统。注意malloc函数是调用并返回指针的函数,如果这块内存的初始化非常重要,你要么自己动手对它进行初始化,要么使用calloc函数(下一板块描述)。
malloc函数的参数就是需要分配的字节数,假设我们需要得到分配一个结构需要的字节数,上面有一个offsetof函数,其返回值就是一个字节数(size_t)。malloc返回的类型是一个void * 的指针,一般要求你在转换时使用强制类型转换。如果操作系统无法向malloc提供一个合适的内存,它就返回一个null指针,所以在调用前确保函数返回的指针非空是一个非常重要的操作。 null空指针[^1]
free的参数要么是null,要么是之前从malloc、calloc或realloc返回的值。
[^1]:null空指针:NULL符号,定义与stdio.h,实际上的字面值为0.
四、calloc和realloc
calloc和realloc内存分配函数的原型如下:
void *calloc( size_t num_elements,
size_t element_size );
void *realloc( void *ptr , size_t new_size );
calloc和malloc用法相同,都用于内存分配,但calloc在返回之前把它初始化为0.另一个不同是calloc在请求内存分配时,包括元素的数量,和每个元素的字节数,它计算总的内存分配。
realloc,顾名思义就是重新分配内存块的大小,它并不以任何方式初始化这块新内存。
五、使用动态分配内存
这里有一个例子,它用malloc分配一块内存:
int *pi;
...
pi=malloc( 100);
if (pi==NULL){
printf("out of memory!\n");
exit(1);
}
如果指针分配成功,我们将具有指向100个字节的指针。因为pi为一个整形指针,所以在整形值为4的机器上,将被当作具有25个整形元素的数组。我们还可以用以下方法声明:
pi=malloc(25* sizeof(int));
既然你有了这块指针,如何使用这块内存呢?如你所见,你既可以使用指针,也可以使用下标:
int *pi2,i;
...
pi2=pi;
for( i=0;i<25;i++)
*pi2++ =0;
或者:
int i;
...
for( i=0;i<25;i+= 1)
pi[i] =0;
以上,动态分配内容完成,还有一些常见错误整理和实例,我们在以后的实际运用中再来仔细讨论。