int val = 20; //在栈空间上开辟四个字节
char arr[10] = {0}; //在栈空间上开辟10个字节的连续空间
开辟空间的方式有两个特点:
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态内存开辟了。
下面我们来讲述三种我们常见的动态内存开辟函数
void* malloc (size_t size);
函数功能
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己
来决定。如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
参数
size是内存块的大小,以字节为单位。
是无符号整数类型-size_t
返回值
void*
成功时,指向函数分配的内存块的指针。
这个指针的类型总是 ,它可以被强制转换为所需的数据指针类型,以便被解引用。
如果函数未能分配请求的内存块,则返回空指针。
函数free,专门是用来做动态内存的释放和回收的
void free (void* ptr);
函数功能
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
参数
指向以前使用 或 分配的内存块的指针
返回值
没有
malloc开辟空间是在堆区开辟空间
#include
#include
int main()
{
int* p= (int*)malloc(40);//我们要开辟的空间是整形的,所以我们把它强制转换成int型,这个可以根据自己的需求来
int* ptr = p;//保留指向malloc开辟空间的地址
if (ptr == NULL)//判断ptr是否是空指针
{
printf("%s\n", strerror(errno));
//把错误码记录到错误码的变量中,然后再编译,打印错误信息
}
int i = 0;
for (i = 0; i < 10; i++)
{
*ptr = i;
ptr++;
}
free(p);//释放开辟的空间
p = NULL;//p置为空指针,让p不再是野指针
return 0;
}
看完这段代码可能会有许多疑问,为啥要保留指向malloc开辟的空间?
当我们用malloc申请空间后,我们要对申请的空间进行释放,如果不释放内存,内存可能就会越占越少以至于不够,所以我们需要free来释放空间,free的参数是刚开始指向空间的起始地址,也就是p指针,如果是参数是ptr,就不能释放开辟的空间,因为ptr++已经改变了ptr原来的地址,所以我们要保留原来的指针对它进行释放。为啥要把p置为NULL呢?,如果我们不把p置为NULL,p里面还是存放着原来的地址,但是p所指向的空间已经被释放,这样p就会变成一个野指针,如果继续访问,就会非法访问,为了防止这样我们就会把它置为NULL.
void* calloc (size_t num, size_t size);
函数功能
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
参数
num:要分配的元素数。
size:每个元素的大小,单位字节。
返回值
成功时,指向函数分配的内存块的指针。
这个指针的类型总是 ,它可以被强制转换为所需的数据指针类型,以便被解引用。
如果函数未能分配请求的内存块,则返回空指针。
#include
#include
int main()
{
int* p = (int*)calloc(10, sizeof(int));
//开辟10个元素的空间,每个元素的大小为int型
if (p == NULL)
{
printf("%s\n", strerror(errno));
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
void* realloc (void* ptr, size_t size);
函数功能
更改 所指向的内存块的大小
该函数可能会将内存块移动到新位置(该位置的地址由函数返回)。
参数
ptr :是要调整的内存地址
如果ptr为空指针(NULL),这是类似于malloc的功能
size:内存块的新大小,以字节为单位。
是无符号整数类型
返回值
指向重新分配的内存块的指针,该块可能与原来位置相同,也可以是新位置。
这个指针的类型是,它可以被强制转换为所需的数据指针类型,以便被解引用
情况1:原有空间之后有足够大的空间,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化,函数返回原来内存的地址。
情况2:原有空间之后没有足够大的空间
原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用,把旧空间的数据拷贝到新空间,释放旧空间的地址。这样函数返回的是一个新的内存地址。
#include
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
//增加空间
int* ptr = (int*)realloc(p, 80);
//如果开辟失败,返回的是NULL,要重新设置一个指针
//如果满足条件,把ptr赋给p指针
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
for (i = 10; i < 20; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 20; i++)//打印元素
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果开辟空间失败,p的值是NULL,对p进行解引用就会有问题
free(p);
}
所以我门要对p进行判断是否为空指针
#include
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
return 1;
int i = 0;
for (i = 0; i < 20; i++)
{
printf("%d ", *(p + i) = i);
}
free(p);
p = NULL;
return 0;
}
这里我们原本是开辟了20个字节,也就是5个整形的大小,但是在循环时,往后打印了20个整数,超出了我们的访问权限,也就是越界访问,原本只能访问5个整形的大小,你直接访问了20个整形的大小,会造成程序报错,这个是跟数组在内存中连续存放一致
#include
int main()
{
int num = 10;//非动态内存开辟空间
int* p = #
free(p);
p = NULL;
return 0;
}
num是非动态内存开辟空间,不能使用free对它进行空间释放。free的使用是与malloc calloc realloc配合进行使用
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
return 0;
for (int i = 0; i < 5; i++)
{
*p = i;
p++;
}
free(p);
p = NULL;
return 0;
}
在free释放的时候,p指向的不再是动态内存空间的起始位置。如果要释放malloc开辟的空间,我们要从开辟空间的起始位置开始进行释放,如果p不是空间的起始位置,我们就不能释放空间或是释放空间的一部分。
#include
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
return 0;
for (int i = 0; i < 5; i++)
{
*(p + i) = i;
}
free(p);
p = NULL;
free(p);//重复释放空间
return 0;
}
对一次开辟空间只需要free一次,一次动态空间开辟对应一次free释放空间。
#include
int* get_memory()
{
int* p = (int*)malloc(40);
return p;
}
int main()
{
int* ptr = get_memory();
//使用
return 0;
}
函数会返回动态开辟空间的地址,我们要对空间进行释放,否则会导致内存泄漏,上述代码中,malloc开辟空间,free没有释放空间。
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。- 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
配方式类似于链表。- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
代码演示
//表示1
#include
struct s
{
int a;
char ch;
int arr[0];//柔性数组成员
};
//表示2
struct s
{
int a;
char ch;
int arr[];//柔性数组成员
};
上述有两种柔性数组的表示方法,有时候其中一种表示,编译器无法编译,就需要表示另一种方法
1.结构中的柔性数组成员前面必须至少一个其他成员。
2.sizeof 返回的这种结构大小不包括柔性数组的内存。
3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大
小,以适应柔性数组的预期大小。
代码1
#include
#include
struct S
{
int n;
float s;
int arr[];//柔性数组成员
};
int main()
{
//为结构体成员开辟空间,同时为柔性数组预留空间
struct S* ps = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 4);
if (ps == NULL)
return 1;
ps->n = 100;
ps->s = 6.5;
int i = 0;
for (i = 0; i < 4; i++)
{
scanf("%d",&(ps->arr[i]));
}
printf("%d %f\n", ps->n, ps->s);
for (i = 0; i < 4; i++)
{
printf("%d ", ps->arr[i]);
}
//增加柔性数组的空间 ,从16个字节增加到40个字节
struct S*ptr=(struct S*)realloc(ps, sizeof(struct S) + sizeof(int) * 10);
if (ptr == NULL)
{
return 1;
}
else
{
ps = ptr;
}
//然后在对新开辟的空间进行使用
//释放空间
free(ps);
ps = NULL;
return 0;
}
在对结构体开辟空间时,.sizeof 返回的这种结构大小不包括柔性数组的内存,也就是没有对柔性数组开辟空间,所以我们要在堆中为结构体开辟空间时,要另外给柔型数组预留一块空间,当我们想增加柔性数组的空间,我们就可以通过realloc来为柔性数组增加空间,这样我们就可以控制它的内存空间,使它可大可小,这也体现出它的柔性。
同样我们也可以设计一下结构体,一样可以实现上面的功能。
代码2
#include
#inlucde<stdlib.h>
struct S
{
int n;
float s;
int *arr;//最后一个成员是指针
};
int main()
{
//结构体开辟空间
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
return 1;
ps->n = 100;
ps->s = 5.5f;
//为结构体中的arr开辟空间
int* ptr = (int*)malloc(4 * sizeof(int));
if (ptr == NULL)
{
return 1;
}
else
{
ps->arr = ptr;
}
int i = 0;
for (i = 0; i < 4; i++)
{
scanf("%d", &(ps->arr[i]));
}
printf("%d %f\n", ps->n, ps->s);
for (i = 0; i < 4; i++)
{
printf("%d ", ps->arr[i]);
}
//释放空间
free(ps);
ps = NULL;
free(ps->arr);
ps->arr = NULL;
return 0;
}
这里第一个malloc为结构体中的三个成员都开辟了空间,所以这里我们不用另外开辟出一块空间预留出来给第三个成员,第二个malloc是对arr进行增加内存空间,所以他们内存空间不是连续的,而柔性数组的所在的结构体空间是连续的。
从上面的两个代码,可以看出代码1的好处
1.方便内存释放
从上述的代码1中,结构体的内存及其成员的内存是一次性分配好了,所以只需要返回一个结构体指针,只用一次free就可以把所有的内存给释放了。
而代码2中,第一个malloc对结构体做了第一次内存分配,第二次malloc又对结构体中的arr做了一次内存分配,所以我们到最后要free两次,才能把所有的内存给释放了
2.有利于访问速度
代码1中结构体的内存是连续存放的,而代码2中的结构体不是连续存放,这样就会导致内存之间的间隙,从而降低访问速度。