malloc/calloc/realloc
malloc
void *malloc(size_t size) 分配所需的内存空间,并返回一个指向它的指针。
size -- 内存块的大小,以字节为单位。
实例
int* p = (int*)malloc(sizeof(int) * 10);
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ",* (p + i));
}printf("\n");
free(p);
p = NULL;
创建了10个int型大小的空间,并循环赋值然后打印。该空间使用完成后最好把空间释放掉(free),如果不释放,在后面代码运行时,这块空间一直存在,造成空间浪费。free释放空间时,指针p必须指向头地址,如果执行过p++,再进行free,是不能成功的
运行结果:
calloc
void *calloc(size_t n, size_t size);和malloc的区别就是calloc创建的空间中为0。参数是两个参数,第一个是元素个数,第二个是这个元素是几个字节大小。
n -- 要被分配的元素个数。 size -- 元素的大小。
realloc
realloc是用来管理动态内存的大小,重新调整之前调用malloc或calloc所分配的内存块的大小。例如:上面创建了40个字节,10的整型的内存块,如果此时内存不够用,就需要使用realloc追加。参数是追加后的总长度。
int* ptr = realloc(p, sizeof(int) * 20);//追加10的整形
//为了使代码可读性高,最好原来使用的p,现在还是使用p。
p = ptr;
int i = 0;
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
追加内存注意事项:
因为有可能p地址后面的内存已经使用或者不够追加的大小,
- 如果p地址后面的大小足够追加的大小,则在p后面继续追加,返回p
- 如果p地址后面的大小不足追加的大小,则会创建一块新的内存,并拷贝p的数据,释放原来的内存空间
- 有可能创建的内存过大,返回空指针,使用原来的指针接收返回的指针,可能会使p指向空,为了不让原数据丢失,最好使用新的变量ptr来接收返回的指针。为了使代码可读性高,最好原来使用的指针p。
动态创建内存的例题:
- 运行完Test函数出现什么现象?有什么问题?
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test()
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
解析:
- 首先GetMemory,传入的是str,属于值传递,没有传入str的地址,所以GetMemory里面申请的空间是属于p的,而str还是NULL。
- 空指针进行strcpy就出现程序崩溃
- 其次,没有free,可能存在内存泄漏问题
- str是以值传递的形式给p,p是GetMemory的形参,只在GetMemory内有效,等GetMemory函数返回后,动态内存尚未释放,并且找不到了。
- 在GetMemory内创建的内存,在函数运行结束时,把内存地址返回出去,这样是可以找到的,可以释放。
下面是正确代码(两种方法):
void GetMemory1(char** p)
{
*p = (char*)malloc(100);
}
void Test1()
{
char* str = NULL;
GetMemory1(&str);
strcpy(str, "hello world");
printf("%s\n",str);
free(str);
str = NULL;
}
char* GetMemory2(char* p)
{
p = (char*)malloc(100);
return p;
}
void Test2()
{
char* str = NULL;
str = GetMemory2(str);
strcpy(str, "hello world");
printf("%s\n", str);
free(str);
str = NULL;
}
2. 运行完Test3函数出现什么现象?有什么问题?
char* GetMemory3(void)
{
char p[] = "hello world";
return p;
}
void Test3(void)
{
char* str = NULL;
str = GetMemory3();
printf(str);
}
int main()
{
Test3();
return 0;
}
解析:
- 在GetMemory中创建的静态变量是存储在栈区内,函数运行结束就会还给操作系统,所以在Test3中使用str接收并打印,存在非法地址访问的问题。
- 如果局部变量使用static修饰,这个变量就会存储在静态区,是可以返回地址的。malloc创建的空间在堆区,是可以返回的。
正确代码:
char* GetMemory4(void)
{
static char p[] = "hello world";
return p;
}
void Test4(void)
{
char* str = NULL;
str = GetMemory4();
printf(str);
}
3. 运行完Test5函数出现什么现象?有什么问题?
void GetMemory5(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test5()
{
char* str = NULL;
GetMemory1(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test5();
return 0;
}
解析:
这个是可以正常运行,只是没有释放动态内存,存在内存泄露问题。
4. 运行完Test6函数出现什么现象?有什么问题?
void Test6()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test6();
return 0;
}
运行结果:
解析:
str的动态内存已经释放,但是str不是空指针,里面还存放着地址,但是地址不能再使用,再使用str进行拷贝,造成非法访问地址的问题。应该把str释放后,str=NULL。此时就不会拷贝world,没有错误。
动态创建内存结构体与柔性数组:
结构体内存块创建
struct S {
int a;
int num[];
};
int main()
{
struct S s;
printf("%d\n", sizeof(s));
struct S* s1 = (struct S*)malloc(sizeof(struct S) + 6 * sizeof(int));
s1->a = 10;
for (int i = 0; i < 6; i++)
{
s1->num[i] = i;
}
}
柔性数组:
- 结构体中的最后一个元素如果是数组的话,那么他就是柔性数组
- 柔性数组可以不用定义大小
柔性数组特点:
- 结构中的柔性数组成员前面必须至少一个其他成员
- sizeof返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
代码解析:
创建了一块内存块,大小是sizeof(struct S) + 6 * sizeof(int),通过打印结构体的大小可以看出来,结构体的大小为4,说明char数组的大小不包括在结构体大小中。因为这个数组就是柔性数组。 内存块的大小就是4+6*4=28,定义了num数组长度为6。
运行结果:
结构体中数组空间追加(追加4的int型的大小):
struct S* ptr = realloc(s1, 44);
if (ptr != NULL)
{
s1 = ptr;
}
for (int i = 6; i < 10; i++)
{
s1->num[i] = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", s1->num[i]);
}printf("\n");
free(s1);
s1 = NULL;
运行结果:
使用指针实现柔性数组类似的功能
struct D {
int a;
int* ptr;
};
int main()
{
struct D* d = (struct D*)malloc(sizeof(struct D));
d->ptr = (int*)malloc(sizeof(int) * 6);
d->a = 10;
int i = 0;
for (i = 0; i < 6; i++)
{
d->ptr[i] = i;
}
int* pp = realloc(d->ptr, 10 * sizeof(int));
if (pp == NULL)
{
return 0;
}
else
{
d->ptr = pp;
}
for (i = 6; i < 10; i++)
{
d->ptr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", d->ptr[i]);
}printf("\n");
free(d->ptr);
free(d);
d = NULL;
}
解析:
使用指针也可以实现上面的功能,都可以实现结构体中数组的变大变小,但是有很大区别:
- 柔性数组创建的内存是连续的,第二种方式创建了两次空间,这两次空间是不连续的。
- 第二种方式需要释放的次数多
- 多次创建空间,会导致内存中使用多块空间,空间和空间直接产生内存碎片,会产生多个内存碎片
- 连续空间访问效率更高。
内存空间的访问速度:
硬盘->内存->缓存->寄存器
内存的读写速度较慢,而cpu读取非常快,所以,当cpu读取数据的时候,内存会把这个数据附近的数据放到缓存的里面,缓存又会把数据放到寄存器里面。所以当cpu要处理下一个数据的会很快。而当处理的数据不是下一个连续的。就会去内存中重新找数据。