动态内存规划
malloc
free
calloc
realico
1.malloc和free
void* malloc (size_t size)
size_t size 是字节数
-
malloc的返回值类型为void*一般要强制类型转化
-
如果参数size为0,malloc的行为是标准是未定义的,取决于编译器
void free (void* ptr)
-
free是用来释放动态开辟内存的,
-
如果参数ptr指向的空间不是动态开辟的,拿free函数的行为是未定义的
-
如果参数ptr是NULL指针,则函数什么事都不做
代码实例:
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
int arr[10] = { 0 };
int* p = (int*)malloc(40);
if (p == NULL) { //判断是否开辟成功
printf("%s\n", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++) {
p[i] = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
free(p); //没有free时,程序退出时系统会自动回收空间
p = NULL; //p置为空是因为: free后p还指向那块空间,防止p成为野指针
return 0;
}
2.calloc
void* calloc (size_t num,size_t size); //输入要开辟额个数和大小
calloc会将数据初始化为0
calloc = malloc + memset
代码实例:
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL) {
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++)
printf("%d ", p[i]); //打印出0 0 0 0 0 0 0 0 0 0
free(p);
p = NULL;
return 0;
}
3.realloc
realloc可以做到对动态开开辟内存大小的调整
原理:如果原本的空间后有足够的空间会直接在后面扩展,若是不够,会将之前的内容拷贝下来放在一片足够大的空间进行扩容,返回新的空间地址,并释放掉原来的空间
代码示例:
int main()
{
int* p = (int*)malloc(40);
if (NULL == p) {
printf("%s\n", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++) {
*(p + i) = i + 1;
}
int* p1 = (int*)realloc(p, 80); //给p加上40个字节,也就是80个
if (p1 == NULL) {
printf("%s\n", strerror(errno));
return 1;
}
p = p1;
for (int i = 10; i < 20; i++) {
*(p + i) = i + 1;
}
for (int i = 0; i < 20; i++)
printf("%d ", p[i]);
return 0;
}
realloc实现malloc功能: int p = realloc(NULL,40);
4.常见错误
1.对空指针的解引用操作
#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
//可能出现错误的方法
int main()
{
int* p = (int*)malloc(40);
*p = 20; //这里要是p为空指针就会出问题
printf("%d", *p);
return 0;
}
//正确写法(需要判断p是否为空)
int main()
{
int* p = (int*)malloc(40);
if(p == NULL){
printf("%s",strerror(errno));
return 1;
}
*p = 20;
printf("%d", *p);
return 0;
}
2.对动态内存空间的越界访问
int main()
{
int* p = (int*)malloc(sizeof(int) * 10); //开辟十个int类型的空间
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
for (int i = 0; i <= 10; i++)
{
p[i] = i; //之分配了4个字节,也就是10个整形,但是for分配了11个整形
}
free(p);
p = NULL;
return 0;
}
3.对非动态内存的free释放
int main()
{
int a = 10;
int* p = &a;
*p = 20;
//....
free(p); //对不是动态开辟的空间进行了释放
p = NULL;
return 0;
}
4.使用free释放动态开辟的部分空间,没有全部释放
int main()
{
int* p = (int*)malloc(40);
if (p == NULL) {
printf("%s", strerror(errno));
return 1;
}
for (i = 0; i < 10; i++) {
*p = i;
p++; //p已经不是原来的p,p的位置进行了移动
}
free(p); //这里free的p不是开始位置,只对p释放了一部分
p = NULL;
return 0;
}
5.对同一块内存的多次释放(一旦释放后要指向空指针,这样再次释放不会产生太大影响)
6.动态内存开辟后的忘记释放
5.面试题讲解
1.
void GetMemory(char* p) {
p = (char*)malloc(100);
//内存泄露
//对p的修改不会影响str,开辟空间之后没有人可以找到
}
void Test(void) {
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world"); //str是空指针,解引时会奔溃
printf(str);
}
int main()
{
Test();
return 0;
}
修改方式
(1).
char* GetMemory(char* p) {
p = (char*)malloc(100);
return p;
}
void Test(void) {
char* str = NULL;
str = GetMemory(str);
if(str == NULL)
return;
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
(2).
void GetMemory(char** p) {
*p = (char*)malloc(100);
}
void Test(void) {
char* str = NULL;
GetMemory(&str);
if(str == NULL)
return;
strcpy(str, "hello world"); //str是空指针,解引用是会奔溃
printf(str);
free(str);
str = NULL;
}
2.
int* f1(void)
{
int x = 10;
return (&x); //野指针问题,函数调用结束时x会被销毁
}
3.
int *f2(void)
{
int* ptr;
*ptr = 10; //野指针问题,ptr没有进行初始化,p放进去之后就是一个随机位置(随机访问问题)
return ptr;
}
4.
char* GetMeory(void) {
char p[] = "hello world"; //局部变量调用之后会被释放
return p;
}
void Test(void) {
char* str = NULL;
str = GetMeory(); //str虽然是p的地址,但是变量被销毁,是野指针
printf(str);
}
int main() {
Test();
return 0;
}
修改:
const char* GetMeory(void) {
const char* p = "hello world";
//const 是常量,"hello world"存储在只读数据区,作用范 围是整个程序运行周期
return p;
}
void Test(void) {
const char* str = NULL;
str = GetMeory();
printf(str);
}
int main() {
Test();
return 0;
}
5.
void Test(void) {
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
//野指针问题,str只是进行类free,将空间还给了系统,但是str没有置空仍然指向之前的地址
if (str != NULL) {
strcpy(str, "world");
//这里对str指向的地址空间进行赋值,但是空间已经还给里系统,就会形成野指针
printf(str);
}
int main()
{
Test();
return 0;
}
//因此释放后要记得将指针指向NULL
通俗解释: 可以将str看作是一个男生,他知道女朋友家的地址,这时候他可以访问她家,但是free后就相当于两人分手了,但是男生还记得女生家的地址,但这时候再去访问就是不合法的
柔性数组
c99中,结构中的对某一个元素允许是未知大小的数组,这就叫做柔性数组成员
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员,也可以写成int a[]
}type_a
1.柔性数组的特点
- 结构体中柔性数组的成员前必须有一个其他成员
- sizeof返回的这种结构的大小不包括柔性数组的内存
- 包含柔性数组成员的结构有malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,一适应荣幸数组的预期大小(直接使用struct st s;这种方式是错误的)
2.柔性数组的使用
typedef struct s
{
int i ;
int arr[]; //柔性数组成员
}s;
int main() {
s* ps = (s*)malloc(sizeof(struct s) + 40); //40是给arr开辟的
if (ps == NULL) {
printf("%s", strerror(errno));
return 1;
}
ps->i = 100;
for (int i = 0; i < 10; i++) {
ps->arr[i] = i;
}
for (int i = 0; i < 10; i++) {
printf("%d ", ps->arr[i]);
}
//数组大小可以改变,因此叫柔性数组
s* ps2 = (s*)realloc(ps, sizeof(s) + 80); //对arr扩容了40个字节
if (ps2 != NULL) {
ps = ps2;
}
free(ps);
ps = NULL;
return 0;
}
不使用柔性数组
//需要malloc两次
//释放两次
struct s
{
int n;
int* arr;
};
int main()
{
struct s* st = (struct s*)malloc(sizeof(struct s));
if (st == NULL)
{
printf("%s", strerror(errno));
return 1;
}
st->n = 100;
st->arr = (int*)malloc(40);
if (st->arr == NULL) {
printf("%s", strerror(errno));
return 1;
}
for (int i = 0; i < 10; i++) {
st->arr[i] = i;
}
for (int i = 0; i < 10; i++)
printf("%d", st->arr[i]);
free(st->arr);
free(st);
st = NULL;
return 0;
}
3.柔性数组的优势
- 1.方便内存释放
只需要释放一次
- 2.有利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片化