本文继续对《C和指针》的第11章进行解读,这一章的内容非常简单,主要介绍了4个和动态内存分配相关的系统API,怎样用好这几个API进行动态内存分配及管理才是我们需要掌握的。废话少说,干就完了!
void *malloc(size_t size);
malloc在heap上动态分配size大小的内存,如果分配成功,则返回一个指向这块内存的指针;如果失败,则返回NULL。
作为一个c语言的程序员,一个好的编码习惯是在malloc后,立即编写free的代码,以免后面忘记,最终是程序出现内存泄漏的问题。
void free(void *ptr);
即释放掉前面malloc(或者其他动态内存分配函数)分配的内存,ptr表示被释放内存的地址。
按照惯例,先来一个示例程序来展示这个两个接口的简单使用。demo1.c演示了一个16字节长度的动态内存的申请与释放。
#include
#include
#define BLK_SIZE (16)
int main(int argc, char** argv)
{
void *p = malloc(BLK_SIZE); // 申请16字节的内存
if (NULL == p) // 判断是否申请成功
{
// 申请失败提示
printf("allocate dynamic memory failed\n");
return -1;
}
// 申请成功,则打印成功提示
printf("allocate dynamic memory successfully, size is %d\n", BLK_SIZE);
// 在实际的工程代码中,通常在释放前会对指针进行判空
if (NULL != p)
{
free(p);
p = NULL;
printf("free dynamic memory successfully\n");
}
return 0;
}
我们知道,数组的大小是在编译时就确定了,而动态内存的大小是在运行时确定的。下面这个示例就说明了这一点。
#include
#include
#define BLK_SIZE (16)
int main(int argc, char** argv)
{
int i = 0;
// 由程序运行时通过命令行参数指定动态内存的大小
int dy_size = atoi(argv[1]);
//const int st_size = 16;
//char arr[st_size] = {0};
//需要注意,在定义数组时,在给定数组的大小时不能使用变量,否则编译器会报编译错误信息
//error: variable-sized object may not be initialized
char arr[BLK_SIZE] = {0};
// 申请动态内存
char *p = (char*)malloc(dy_size);
if (NULL == p)
{
printf("allocate dynamic memory failed\n");
return -1;
}
printf("allocate dynamic memory successfully, size is %d\n", dy_size);
// 给数组的每个元素赋值
for (i = 0; i < BLK_SIZE; ++i)
arr[i] = i;
// 给动态内存的每个元素赋值
for (i = 0; i < dy_size; ++i)
*(p+i) = i;
// 打印数组的所有元素
printf("print all elements of array:\n");
for (i = 0; i < BLK_SIZE; ++i)
printf("arr[%d] = %d\n", i, arr[i]);
// 打印动态内存的所有元素
printf("print all elements of dynamic memory:\n");
for (i = 0; i < dy_size; ++i)
printf("mem[%d] = %d\n", i, p[i]);
if (NULL != p)
{
free(p);
p = NULL;
printf("free dynamic memory successfully\n");
}
return 0;
}
我们来看看两次运行的结果:(第一次命令行参数指定动态内存的大小为8,第二次命令行参数指定动态内存的大小为16)
$ ./demo2 8
allocate dynamic memory successfully, size is 8
print all elements of array:
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4
arr[5] = 5
arr[6] = 6
arr[7] = 7
arr[8] = 8
arr[9] = 9
arr[10] = 10
arr[11] = 11
arr[12] = 12
arr[13] = 13
arr[14] = 14
arr[15] = 15
print all elements of dynamic memory:
mem[0] = 0
mem[1] = 1
mem[2] = 2
mem[3] = 3
mem[4] = 4
mem[5] = 5
mem[6] = 6
mem[7] = 7
free dynamic memory successfully
$ ./demo2 16
allocate dynamic memory successfully, size is 16
print all elements of array:
arr[0] = 0
arr[1] = 1
arr[2] = 2
arr[3] = 3
arr[4] = 4
arr[5] = 5
arr[6] = 6
arr[7] = 7
arr[8] = 8
arr[9] = 9
arr[10] = 10
arr[11] = 11
arr[12] = 12
arr[13] = 13
arr[14] = 14
arr[15] = 15
print all elements of dynamic memory:
mem[0] = 0
mem[1] = 1
mem[2] = 2
mem[3] = 3
mem[4] = 4
mem[5] = 5
mem[6] = 6
mem[7] = 7
mem[8] = 8
mem[9] = 9
mem[10] = 10
mem[11] = 11
mem[12] = 12
mem[13] = 13
mem[14] = 14
mem[15] = 15
free dynamic memory successfully
void *calloc(size_t nmemb, size_t size);
calloc和malloc的作用类似,也是分配动态内存,但有两点不同
demo3.c 演示了calloc的使用,以及calloc的会初始化内存为0的特性,同时也展示了malloc分配的内存是随机值。
#include
#include
#define BLK1_SIZE (160)
#define BLK_SIZE (16)
void myfree(void *p)
{
if (NULL != p)
{
free(p);
p = NULL;
printf("free dynamic memory successfully\n");
}
}
int main(int argc, char** argv)
{
int n = 0;
int i = 0;
int *p1 = NULL;
int *p11 = NULL;
int *p2 = NULL;
p1 = (int*)malloc(BLK1_SIZE*sizeof(int));
if (NULL == p1)
{
printf("malloc failed\n");
return -1;
}
printf("\np1 malloc(%p) successfully, size is %ld\n", p1, BLK1_SIZE*sizeof(int));
// 给这个大块内存赋值
for (i = 0; i < BLK1_SIZE; ++i)
p1[i] = i;
// 打印动态内存p1的所有元素
printf("print all elements of p1 memory(%p):\n", p1);
for (i = 0; i < BLK1_SIZE; ++i)
{
printf("%03d ", p1[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
myfree(p1);
printf("\n\n");
n = 0;
p11 = (int*)malloc(BLK_SIZE*sizeof(int));
if (NULL == p11)
{
printf("malloc failed\n");
return -1;
}
printf("p11 malloc(%p) successfully, size is %ld\n", p11, BLK_SIZE*sizeof(int));
printf("print all elements of p11 memory(%p):\n", p11);
for (i = 0; i < BLK_SIZE; ++i)
{
printf("%03d ", p11[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
printf("\n");
myfree(p11);
n = 0;
p2 = (int*)calloc(BLK_SIZE, sizeof(int));// 16个元素,每个元素占4个字节
if (NULL == p1)
{
printf("calloc failed\n");
return -1;
}
printf("calloc(%p) successfully, size is %ld\n", p2, BLK_SIZE*sizeof(int));
printf("print all elements of calloc memory:\n");
for (i = 0; i < BLK_SIZE; ++i)
{
printf("%03d ", p2[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
printf("\n");
myfree(p2);
return 0;
}
绿色区域是第一次通过malloc申请了160个int大小的内存,并将这160个元素赋值(从0到159),然后将其释放。
蓝色区域是再次通过malloc申请了16个int大小的内存,然后直接打印这块内存的值,发现这块内存的值是随机的,并没有被初始化。另外,p11的地址为p1上次申请的内存地址,也说明,p1释放后,p11申请内存时会复用之前的内存空间。
红色区域是通过calloc申请了16个int大小的内存,然后直接打印这块内存的值,发现这块内存的值都被初始化为0。
说明,打印中的内存的地址每次执行都不同。
这个例子说明了,对于malloc申请的内存,一种好的习惯是将这块内存进行初始化(使用memset)。
void *realloc(void *ptr, size_t size);
realloc可以扩大或缩小一块内存;若用于扩大,则原先内存的内容不变,新增的内存追加到原内存后,新内存是没有初始化的;若用于缩小,原内存尾部的冗余空间将被释放。如果原内存块的大小不能被改变,那么将会重新分配一块正确大小内存,并把原内存的内容拷贝到新的内存块上来,这种情况下就需要使用realloc返回的指针,原先的指针就不能再使用了,原内存会被自动释放。
如果ptr为NULL,realloc等价于malloc。
#include
#include
#define BLK_SIZE (16)
int main(int argc, char** argv)
{
int i = 0;
int n = 0;
char *p1 = NULL;
char *p2 = NULL;
size_t cap = 0;
p1 = (char*)malloc(BLK_SIZE);
if (NULL == p1)
{
printf("allocate dynamic memory failed\n");
return -1;
}
printf("p1(%p) malloc successfully, size is %d\n", p1, BLK_SIZE);
for (i = 0; i < BLK_SIZE; ++i)
p1[i] = i;
// 打印动态内存p1的所有元素
printf("print all elements of p1 memory(%p):\n", p1);
for (i = 0; i < BLK_SIZE; ++i)
{
printf("%03d ", p1[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
printf("\n");
// 扩容
cap = BLK_SIZE<<1;
p2 = realloc((void*)p1, cap);
if (NULL == p2)
{
printf("allocate dynamic memory failed\n");
return -1;
}
printf("p2(%p) malloc successfully, size is %lu\n", p2, cap);
n = 0;
printf("print all elements of p1 memory(%p):\n", p1);
for (size_t i = 0; i < cap; ++i)
{
printf("%03d ", p2[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
free(p2);
return 0;
}
从demo4的打印信息可以看出,扩容后,原内存中的数据不变。
最后,再介绍一个动态内存压缩的示例
#include
#include
#define BLK_SIZE (16)
int main(int argc, char** argv)
{
int i = 0;
int n = 0;
char *p1 = NULL;
char *p2 = NULL;
size_t cap = 0;
p1 = (char*)malloc(BLK_SIZE);
if (NULL == p1)
{
printf("allocate dynamic memory failed\n");
return -1;
}
printf("p1(%p) malloc successfully, size is %d\n", p1, BLK_SIZE);
for (i = 0; i < BLK_SIZE; ++i)
p1[i] = i;
// 打印动态内存p1的所有元素
printf("print all elements of p1 memory(%p):\n", p1);
for (i = 0; i < BLK_SIZE; ++i)
{
printf("%03d ", p1[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
printf("\n");
// 缩小
cap = BLK_SIZE>>1;
p2 = realloc((void*)p1, cap);
if (NULL == p2)
{
printf("allocate dynamic memory failed\n");
return -1;
}
printf("p2(%p) malloc successfully, size is %lu\n", p2, cap);
n = 0;
printf("print all elements of p1 memory(%p):\n", p1);
for (size_t i = 0; i < cap; ++i)
{
printf("%03d ", p2[i]);
n++;
if (n % BLK_SIZE == 0)
printf("\n");
}
printf("\n");
free(p2);
return 0;
}
好了,动态内存分配就介绍这么多。