C语言内存管理

1. 进程空间

1.1. 进程VS程序

程序:经源码编译后的可执行文件,可执行文件可以多次被执行。比如打开vscode
进程:程序加载到内存后开始执行,至执行结束,这样一段时间概念;比如每次打开vscode,都是一个进程,每次关闭vscode表示该进程结束
程序是静态概念,而进程是动态/时间概念

1.2. 进程空间

进程空间:可执行性文件被拉起以后,在内存中的分布情况
在这里插入图片描述

2. 栈内存

2.1. 栈存储的内容

栈中存放任意类型的变量,但必须是auto类型修饰的,即自动类型的局部变量。

2.2. 栈存储的特点

随用随开,用完即消。内存的分配和销毁系统自动完成,不需要人工干预。

2.3. 栈大小

栈的大小并不大,他的意义并不在于存储大数据,而在于数据交换。

2.4. 常见栈溢出案例

局部变量过多,过大或递归层次太多

void func(int n)
{
	if(n > 0)	
		func(--n);
	else 
		return;
}
int main() 
{	
	// char array[1024*1024*1024]; // 局部变量过多,多大
	// memcpy(array, "abcd", 5); //centos 避免优化
	func(1000); // 递归层次太多
	return 0;
}

3. 堆内存

3.1. 堆内存特点

堆内存可以存放任意类型的数据,但需要自己申请与释放

3.2. 堆大小

堆大小,大,但实际使用中,受限于实际内存的大小和内存是否连续性。
malloc是以字节为单位进行申请的

int main(void)
{
  char *p = (int *)malloc(1024 * 1024 * 1024); // 1G
  if (p == NULL)
  {
    printf("malloc error\n");
    return -1;
  }
  strcpy(p, "abcd");
  printf("over");
  free(p);
  return 0;
}

3.3. 堆内存的申请与释放

3.3.1. malloc

函数声明:void *malloc(size_t _Size)
所在文件:stdlib.h
函数功能:申请堆内存空间并返回,所申请的空间并未初始化,常见的初始化方法是memset字节初始化
参数:size_t,_size表示要申请的字符数
返回值:void *成功返回非空指针指向申请的空间,失败返回NULL

int main(void)
{
  int a;
  int *p = &a;
  a = 100;
  printf("*p = %d\n", a);
  int *pm = (int *)malloc(sizeof(int));
  if (pm == NULL)
    return -1;
  *pm = 100;
  printf("*pm = %d\n", *pm);
  int array[10];
  int *pa = array;
  pm = (int *)malloc(10 * sizeof(int));
  memset(pm, 0, 10 * sizeof(int));
  for (int i = 0; i < 10; i++)
  {
    printf("%d\n", pm[i]);
  }
  free(pm);
  return 0;
}

3.3.2. calloc

函数声明:void *calloc(size_t nmemb, size_t size);
所在文件:stdlib.h
函数功能:申请堆内存空间并返回,所申请的空间并未初始化,自动清零
参数:size_t nmemb 所需内存单元数量;size_t size内存单元大小
返回值:void *成功返回非空指针指向申请的空间,失败返回NULL

int main()
{
  int *p = (int *)calloc(10, sizeof(int));
  for (int i = 0; i < 10; i++)
  {
    printf("%d\n", p[i]);
  }
  free(p);
  return 0;
}

3.3.3. realloc

函数声明:void *realloc(void *ptr, size_t size);
所在文件:stdlib.h
函数功能:扩容(缩小)原有内存的大小。通常用于扩容,缩小会导致已存储的部分数据丢失
参数和返回值:void * ptr表示待扩容(缩小)的指针,ptr为之前用malloc或calloc分配的内存地址。或ptr==NULL,则该函数等同于malloc;size_t:size表示扩容(缩小)后内存的大小。成功返回非空指针指向申请的空间,失败返回NULL;void *返回的指针,可能与ptr的值相同, 也有可能不同。若相同,则说明在原空间后面申请,否则,则可能后续空间不足,重新申请的新的连续空间,原数据拷贝到新空间。

int main()
{
  int *pa;
  printf("start len\t");
  int len;
  scanf("%d", &len);
  pa = (int *)malloc(len * sizeof(int));
  int oldLen = len;
  for (int i = 0; i < len; i++)
  {
    pa[i] = 100 + i;
    printf("%d\n", pa[i]);
  }
  printf("start larger len\t");
  scanf("%d", &len);
  pa = (int *)realloc(pa, len * sizeof(int));
  for (int i = 0; i < len; i++)
  {
    if (i >= oldLen)
      pa[i] = 200 + i;
    printf("%d\n", pa[i]);
  }

  /* printf("start smaller len\t");
  scanf("%d", &len);
  pa = (int *)realloc(pa, len * sizeof(int));
  for (int i = 0; i < len; i++)
  {
    if (i >= oldLen)
      pa[i] = 200 + i;
    printf("%d\n", pa[i]);
  }
  free(pa); */
  return 0;
}

3.3.4. free

函数声明:void *free(void *p);
所在文件:stdlib.h
函数功能:释放申请的堆内存
参数:void *,p指向手动申请的空间
返回值:void,无返回值
注意:

  • malloc和free要求配对使用,申请完了就释放
  • malloc多于free,必然会导致泄漏;free多于malloc,会导致double free
// 申请,判空,(使用,释放)配对使用,置空
int main()
{
  char *pc = (char *)malloc(100);
  if (pc == NULL)
  {
    printf("error");
    exit(-1);
  }
  strcpy(pc, "abcdefghijklmnopqrstuvwxyzABCDEF");
  free(pc);
  pc = NULL;
  return 0;
}

3.4. 应用模型

3.4.1. 动态数组

堆空间的动态申请与释放,动态数组就可以实现了

int main()
{
  int len;
  printf("len:");
  scanf("%d", &len);
  int *p = (int *)realloc(NULL, len * sizeof(int));
  for (int i = 0; i < len; i++)
  {
    printf("%d\n", p[i]);
  }
  // 变大
  printf("plus new len:");
  scanf("%d", &len);
  p = (int *)realloc(p, len * sizeof(int));
  for (int i = 0; i < len; i++)
  {
    printf("%d\n", p[i]);
  }
  // 变小
  printf("small new len:");
  scanf("%d", &len);
  p = (int *)realloc(p, len * sizeof(int));
  for (int i = 0; i < len; i++)
  {
    printf("%d\n", p[i]);
  }
  return 0;
}

3.5. 常见错误案例剖析

以下三个模型,也是针对堆内存的使用特点而设计的,即:

  • 返回判空
  • 配对使用
  • 自申请,自释放

3.5.1. 置空与判空

堆内存的使用逻辑:申请,判空,使用/释放(配对),置空。常见错误之一就是释放以后未置为NULL再次作判空使用 或 释放以后继续非法使用。

int main()
{
  char *p = (char *)malloc(100);
  strcpy(p, "hello");
  free(p); // p所指向的内存被释放,但是p所指向的地址仍然不变
  // p = NULL; //忘了这句,后面又用到了
  if (NULL != p)
  {
    // 没有起到防错作用
    strcpy(p, "hello"); // 出错
  }
}

3.5.2. 重复申请

在服务器模型中,常用到大循环,在大循环中未释放原有空间,重新申请新空间,造成原有空间内存泄漏

int main()
{
  while (1)
  {
    char *p = malloc(1000);
    printf("xxxx\n");
    printf("xxxx\n");
    printf("xxxx\n");
    printf("xxxx\n");
    p = malloc(1000); // 中途可能忘了,重复申请,内存泄漏
    free(p);
    printf("xxxx\n");
    Sleep(10);
  }
}

3.5.3. 谁申请谁释放模型(并非绝对)

如果没有协同的原则,有可能会造成重复释放

void func(char *p)
{
  strcpy(p, "China");
  printf("%s\n", p);
  free(p); // 此处违反了谁申请谁释放原则
}
int main()
{
  char *p = malloc(100);
  func(p);
  free(p);
  return 0;
}

3.5.4. 其他常见错误

  • free非alloc函数申请赋值的指针
  • free(p+i);

4. 开放的地址空间

传对象的地址到不同的作用域,可依据地址修改地址所指向对象的内容,根本原因是地址是开放的

void func(int *p)
{
  *p = 400;
}
int main()
{
  int *p = (int *)0x12345678;
  *p = 200;
  int a;
  func(&a);
  *(&a) = 400;
  return 0;
}

5. 堆与栈空间的返回

5.1. 栈空间不可以返回

5.2. 堆空间可以返回

int func()
{
  int a = 500;
  return a;
}
int *foo()
{
  int a = 500;
  int *pa = &a;
  printf("a = %p\n", pa);
  return pa;
}
int main()
{
  int a = func();
  printf("%d\n", a);
  int *pa = foo();
  printf("pa = %p\n", pa);
  // a是foo()的局部变量,当foo()返回后,a的内存空间就不再保留有效,pa指向的地址变成了一个悬空指针(野指针)。
  printf("*pa = %d\n", *pa); // 不可以
  *pa = 300;
  return 0;
}
  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值