本文的主要内容:
栈区: 我们知道栈区在函数被调时分配,用于存放函数的参数值,局部变量等值。在 windows 中栈的默认大小是1M,在 vs 中可以设置栈区的大小。在 Liunx 中栈的默认大小是 10M,在 gcc 编译时可以设置栈区的大小。
堆区: 程序运行时可以在堆区动态地请求一定大小的内存,并在用完之后归还给堆区。在 Liunx 系统中堆区的大小接近 3G。windows 下的大小大家可以自己测一下。
一般情况下我们需要大块内存或程序在运行的过程中才知道所需内存大小,我们就从堆区分配空间。
C 语言中动态内存管理的有四个函数:malloc,calloc,realloc,free,都需要引用 stdlib.h 或 malloc.h 文件。
malloc 向堆区申请一块指定大小的连续内存空间 。
void * malloc (size_t size); // typedef unsigned int size_t;
Allocate memory block //分配内存块
分配一个大小为size字节的内存块,返回指向该块开头的指针
新分配的内存块的内容未初始化,剩余值不确定。
如果大小为零,则返回值取决于特定的库实现(它可能是一个空指针),但返回的指针不应被取消引用。
参数
大小:内存块的大小,以字节为单位。size_t是一种无符号整数类型
返回值
成功时,指向函数分配的内存块的指针。
此指针的类型始终为void*,可以将其转换为所需的数据指针类型,以便取消引用。
如果函数未能分配请求的内存块,则返回空指针。
free
用来释放从 malloc , realloc, calloc 成功获取到的动态内存分配的空间。
void free (void* ptr);
Deallocate memory block // 释放内存块
以前通过调用malloc、calloc或realloc分配的内存块被释放,使其再次可用于进一步分配。
如果ptr不指向与上述函数一起分配的内存块,则会导致未定义的行为
如果ptri是空指针,则函数不执行任何操作。
请注意,此函数不会更改ptritself的值,因此它仍然指向相同(现在无效)的位置。
参数
ptr:指向以前分配给malloc的内存块的指针,callocor realloc。
void* calloc (size_t num, size_t size);
Allocate and zero-initialize array // 分配并使用零初始化连续内存空间
为num元素数组分配一块内存,每个元素的大小为字节长,并将其所有位初始化为零。
有效的结果是分配(num*size)字节的零初始化内存块。
如果大小为零,则返回值取决于特定的库实现(它可能是空指针,也可能不是空指针),但不应取消对返回指针的引用。
参数
num:要分配的元素数。
size : 每个元素的大小。
size_t是一种无符号整数类型。
返回值
成功时,指向函数分配的内存块的指针。
此指针的类型始终为void*,可以将其转换为所需的数据指针类型,以便取消引用。
如果函数未能分配请求的内存块,则返回空指针。
我们使用 malloc 或 calloc 申请了堆空间内存,但是程序在运行的过程中发现申请空间少了或多了如何处理呢?
void* realloc (void* ptr, size_t size);
Reallocate memory block // 重新分配内存块
更改ptr指向的内存块的大小。
该函数可以将内存块移动到新位置(其地址由函数返回)。
即使将内存块移动到新位置,内存块的内容也会保留到新旧大小中较小的一个。如果新大小更大,则新分配部分的值是不确定的。
如果ptr是空指针,则函数的行为类似于malloc,分配一个新的大小字节块,并将指针返回到其开头。
否则,如果大小为零,则先前在ptr分配的内存将被释放,就像调用了free一样,并返回空指针。
如果函数未能分配请求的内存块,将返回一个空指针,并且参数ptr指向的内存块不会被释放(它仍然有效,且其内容不变)。
参数
Ptr:指向以前使用malloc、calloc或realloc分配的内存块的指针。或者,这可以是一个空指针,在这种情况下,会分配一个新块(就像调用了malloc一样)。
size: 内存块的新大小,以字节为单位。size_t是一种无符号整数类型。
返回值
指向重新分配的内存块的指针,它可以与ptr相同,也可以是新位置。此指针的类型为void*,可以将其转换为所需的数据指针类型,以便取消引用。
空指针表示大小为零(因此ptr被解除分配),或者函数未分配存储(因此ptr指向的块未被修改)。
realloc 函数调整内存大小分为 3 种情况。
第一种情况: 后续未分配内存空间足够大,可以分配空间。图示如下:
第二种情况: 后续未分配内存空间不足够大,不能分配空间。图示如下:
第三种情况: 堆内存不足,扩展空间失败,realloc 函数返回 NULL。
示例代码:
#include
#include
int main()
{
int* p = NULL;
int* ip = (int*)malloc(sizeof(int) * 10);
if (NULL == ip) exit(EXIT_FAILURE);
// 处理程序
ip = (int*)realloc(ip, sizeof(int) * 1000);
// realloc 分配失败,返回 NULL ?
if (NULL == ip) exit(EXIT_FAILURE);
//正确处理方法
p = (int*)realloc(ip, sizeof(int) * 1000);
if (NULL == p)
{
// 扩充失败,
free(ip);
exit(EXIT_FAILURE);
}
else
{
ip = p;
// 处理程序
}
free(ip);
ip = NULL;
return 0;
}
结构体变量和内置类型都有局部 ,全局 ,动态生存期。
示例 1:
#include
#include
#include
struct Student
{
char s_name[20]; //姓名
int age; //年龄
float score; //成绩
};
struct Student g_studa; //全局
struct Student g_studb = { "tulun",13,145.5 };
int main()
{
struct Student studa; // 局部未初始化
struct Student studb = { "tulun",12,140.5 };
struct Student* sp = (struct Student*)malloc(sizeof(struct Student)); //?
struct Student* sp2 = (struct Student*)malloc(sizeof(*sp2)); //?
struct Student* sp3 = (struct Student*)malloc(sizeof(sp3)); //?
strcpy(sp->s_name, "tulun");
sp->age = 11;
sp->score = 143.5;
free(sp);
free(sp2);
free(sp3);
return 0;
}
示例:
struct Student
{
char s_name[20]; //姓名
int age; //年龄
float score; //成绩
struct Student* next; // 指向 Student 类型的指针变量属性;
};
我们可以一个个的动态申请 struct Student 类型的内存空间,然后用 next 指针把它们链接起来。
我们先看一个结构体的设计:
#define MAXLEN 1024
typedef struct kd_node
{
struct kd_node* left;
struct kd_node* right;
int dim;
unsigned long long data[MAXLEN]; // 数据
}kd_node;
在这段代码中,为了存储数据,申请了长度为 1024 的 unsigned long long 型数组。若是数据的长度远远小于MAXLEN,这样的设计,是及其浪费空间的。
C99 标准中给出了新的设计方法。通过柔性数组可以解决这个问题。
示例:
柔性数组是一种数组大小待定的数组。
在 C 语言中,可以使用结构体产生柔性数组,结构体的最后一个元素可以是大小未知的数组。
在 struct sd_node 结构体中 data,仅仅是一个待使用的标识符,不占用存储空间。所以 sizeof(struct sd_data) = 8
对于编译器而言, 数组名仅仅是一个符号, 它不会占用任何空间, 它在结构体中, 只是代表了一个偏移量, 代表一个不可
修改的地址常量!
假设我们在网络通信过程中使用的数据缓冲区, 缓冲区包括一个 num, size 和 data 字段, 分别标识数据的块号,长度和传输的数据, 我们设计思路如下:
我们从实际场景中应用来考虑他们的优劣,主要考虑点有:缓冲区空间的开辟, 释放和访问.
#define MAXSIZE 4096 // 4k
struct data_buffer
{
int num;
int size;
char data[MAXSIZE];
}; // 结构体的大小==>sizeof(int)+sizeof(size)+sizeof(char)*MAXSIZE;
使用定长数组作为数据缓冲区,为了避免造成缓冲区溢出,数组设计是大开小用,而实际使用过程中,达到MAXSIZE 长度的数据很少,那么多数情况下,缓冲区的大部分空间都浪费了,也会造成不必要的流量浪费。但是使用过程很简单, 数据空间的开辟和释放简单, 无需程序员考虑额外的操作。
如果你将上面的长度为 MAXSIZE 的定长数组换为指针, 每次使用时动态的开辟 CURSIZE 大小的空间, 那么就避免造成 MAXSIZE - CURSIZE 空间的浪费, 只浪费了一个指针域的空间。
struct data_buffer
{
int num;
int size;
char* data;
}
//结构体大小= sizeof(num)+sizeof(size)+sizeof(char*)
//在分配和释放内存时,都必须采用两步。
struct data_buffer* pbuff = (struct data_buffer*)malloc(sizeof(struct data_buffer));
if (NULL == pbuff) exit(EXIT_FAILURE);
pbuff->size = CURSIZE; // CURSIZE 假设是发送数据的长度。
pbuff->data = (char*)malloc(sizeof(char) * CURSIZE);
if (NULL == pbuff) exit(EXIT_FAILURE);
// 处理程序
free(pbuff->data);
free(pbuff);
pbuff = NULL;
使用指针结果作为缓冲区, 只多使用了一个指针大小的空间, 无需使用 MAXSIZE 长度的数组, 不会造成空间的大量浪费。但那是开辟空间时, 需要额外开辟数据域的空间, 施放时候也需要显示释放数据域的空间, 但是实际使用过程中, 往往在函数中开辟空间, 然后返回给使用者指向 struct point_buffer 的指针, 这时候我们并不能假定使用者了解我们开辟的细节, 并按照约定的操作释放空间, 因此使用起来多有不便, 甚至造成内存泄漏。
定长数组使用方便,但是却浪费空间。 指针形式只多使用了一个指针的空间,不会造成大量空间分浪费,但是使用起来需要多次分配,多次释放,那么有没有一种实现方式既不浪费空间, 又使用方便的呢?
柔性数组:
struct data_buffer
{
int num;
int size;
char data[];
};
//结构体大小 sizeof(struct data_buffer) = sizeof(num)+sizeof(size) = 8 字节
//使用的时候, 只需要开辟一次空间即可
int main()
{
// n 是数据长度
int n = strlen("tulun hello") + 1;
struct data_buffer* pbuff = (struct data_buffer*)malloc(sizeof(struct data_buffer) + sizeof(char) * n);
if (NULL == pbuff) exit(EXIT_FAILURE);
pbuff->num = 1;
pbuff->size = n;
memcpy(pbuff->data, "tulun hello", n);
// 处理程序
free(pbuff);
return 0;
}
可见,堆容易造成内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和内核态切换,内存申请的代价更为昂贵。所以栈在程序中应用最广泛,函数调用也利用栈来完成,调用过程中的参数、返回地址、栈基指针和局部变量等都采用栈的方式存放。所以,建议尽量使用栈,仅在分配大量或大块内存空间时使用堆。
最后使用栈和堆时应避免越界发生,否则可能程序崩溃或破坏程序堆、栈结构,产生意想不到的后果。