C语言基础 动态内存规划知识点总结

动态内存规划

  1. malloc
  2. free
  3. calloc
  4. 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.有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值