动态内存分配函数 | free为什么只传入一个指针就能正确释放

本文详细介绍了Linux内存分布,特别是C标准库中的动态内存分配函数malloc、calloc和realloc,以及它们的使用方法。同时讨论了动态内存分配的常见错误,包括内存对齐、空指针处理和内存泄漏问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.Linux内存分布图

在程序设计当中,可以定义全局变量也可以定以局部变量,分别也是在全局区、栈区开辟,那么这些区域都不有用我们动手管理,但是我们在堆上动态开辟的空间需要我们手动管理!

当然,在程序设计中使用的是虚拟内存,在操作系统层面上是按照的方式分配空间的,所有即使我们向堆申请1个字节的空间都不仅仅只是一个字节,所以小空间的变量要使用malloc来分配!

在这里插入图片描述

  • 代码区(text):二进制代码,字面值常量,const修饰的具有常属性变量
  • 数据区(data):包括已初始化的全局(包括静态和非静态)变量和静态局部变量
  • BSS区:包括未初始化的(包括静态和非静态)变量和静态局部变量(进程成加载此区即被清0)注:有时候静数据区和BSS区被合称为全局区或静态区
  • 堆(heap):动态内存分配,从低地址向高地址扩展,程序员动手管理的区域
  • 文件映射或共享区:动静态库
  • 栈(stack):非静态的局部变量,函数参数和返回值,从高地址向低地址扩展;栈的大小有一个固定值,可以通过调系统参数改变
  • 命令行参数和环境变量

测试代码:

#include<stdio.h>       
#include<stdlib.h>    
         
int g_val = 5;
static int static_g_val = 5;

const int const_g_val = 5;
static const int static_const_g_val = 5;
int g_unval;    
         
int main(int argc,char* argv[],char* env[])    
{      
      static int static_tmp = 10;    
      const char* constant = "hello world";    
         
      printf("env addr:%p\n",env[0]);    
      printf("argv addr:%p\n",argv[0]);    
          
      char* heap1 = (char*)malloc(5);                                                                         
      char* heap2 = (char*)malloc(5);    
      char* heap3 = (char*)malloc(5);    
      
      printf("statck1 addr:%p\n",&heap1);    
      printf("statck2 addr:%p\n",&heap2);                                   
      printf("heap3 addr: %p\n",heap3);
    
      printf("g_unval adrr:%p\n",&g_unval);
      printf("static_tmp adrr:%p\n",&static_tmp);
      printf("static_g_val adrr:%p\n",&static_g_val);
      printf("g_val adrr:%p\n",&g_val);
      printf("constant adrr:%p\n",constant);
      printf("static_const_g_val adrr:%p\n",&static_const_g_val);
      printf("const_g_val adrr:%p\n",&const_g_val);
      printf("code adrr:%p\n",&main);
  
      return 0;
}

输出结果:

env addr:0x7ffe3ca5f812
argv addr:0x7ffe3ca5f809
statck1 addr:0x7ffe3ca5f228
statck2 addr:0x7ffe3ca5f220
heap3 addr: 0x1277050
g_unval adrr:0x60104c
static_tmp adrr:0x601044
static_g_val adrr:0x601040
g_val adrr:0x60103c
constant adrr:0x400790
static_const_g_val adrr:0x400888
const_g_val adrr:0x400884
code adrr:0x40059d
2.C标准库中动态内存分配函数
void* malloc (size_t size)
功能:在堆上申请一块连续可用的空间
参数:申请的字节数,如是0,标准未定义,有编译器决定
返回值:返回指向这块空间的指针,否则返回NULL
void* calloc (size_t num, size_t size);
介绍:函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

关于realloc函数:

void* realloc (void* ptr, size_t size);
功能:realloc 函数就可以做到对动态开辟内存大小的调整。
参数:
    ptr 是要调整的内存地址
    size 调整之后新大小

在这里插入图片描述

情况2:原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用

在这里插入图片描述

free函数

void free (void* ptr);
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的;如果参数 ptr 是NULL指针,则函数什么事都不做


在64位Linux机器,用的是glibc2.7版本:注:在Windows下测试是由很大不同的

示例代码:

```c
int main()
{
	int* ptr1 = (int*)malloc(sizeof(int));
	int* ptr2 = (int*)malloc(sizeof(int));
	double* ptr3 = (double*)malloc(sizeof(double));
	printf("ptr1:%x ,ptr2:%x ,ptr3:%x\n", ptr1, ptr2,ptr3);

	free(ptr2);
	ptr2 = NULL;
	int* ptr4 = (int*)malloc(sizeof(int));

	printf("ptr1:%x ,ptr3:%x ,ptr4:%x\n", ptr1, ptr3,ptr4);

	void *ptr5 = realloc(ptr4,25/*25是一个阈值*/); 
	printf("ptr1:%x ,ptr3:%x ,ptr5:%x\n", ptr1, ptr3,ptr5);
	
	return 0;
}

输出结果:

ptr1:24d1010 ,ptr2:24d1030 ,ptr3:24d1050
ptr1:24d1010 ,ptr3:24d1050 ,ptr4:24d1030
ptr1:24d1010 ,ptr3:24d1050 ,ptr5:24d1070

每个地址都是16的整数倍;而且指针之间有很大的空间比如,ptr1ptr2隔着32个字节比int4个字节要多不少!

为什么会输出这样的结果,另外free的参数只传入一个指针它怎么知道要释放多少个字节

使用malloc函数申请空间,在返回地址前(当然,有可能在后面,不同的实现不同)有个控制信息,记录着malloc开辟空间的管理信息,比如,开辟空间的大小,所以在free释放的时候会根据传入的地址读取控制信息!malloc开辟空间,会根据一定的对齐规则来管理!显然这里地址都是16的整数倍,对齐数是16!

在这里插入图片描述

总的来说:malloc根据一定的对齐规则,开辟空间并自动的开辟了对应的控制信息管理开辟的空间!当然,malloc一般申请的是33页,然后free不会立刻归还系统!

ptr4 和ptr2的指针为什么相同

free释放的资源不会立刻归还给系统,如果对一块空间释放,那么下次申请资源如果释放的空间大小合适,会通过双链表查找到释放的位置重新利用,这也避免一定的内碎片问题!

3.动态内存分配函数的常见错误
  1. 函数返回值都是指针,避免申请的失败对空指针的解引用
  2. 对动态开辟空间的越界访问
  3. 对非动态开辟内存使用free释放
  4. 使用free释放一块动态开辟内存的一部分
  5. 对同一块动态内存多次释放
  6. 动态开辟内存忘记释放导致内存泄漏问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值