Linux--C语言之动态内存分配

动态内存分配

我们要想实现动态内存分配,就需要学习标准C提供的函数库:

  1. 函数所属的库文件
  2. 函数的原型-函数的声明
  • 函数名
  • 形式参数
  • 返回值类型
  1. 函数的功能
  • 注意:内存分配函数在申请内存时,建议用多少申请多少,可以有少量的预留量;但不能越界访问(虽然编译运行不报错,但数据不安全)

一、常用函数

(一)malloc

1. 头文件:#include <stdlib.h>
2. 函数功能:

C库函数void *malloc(size_t size)分配所需的内存空间,并返回一个指向它的指针。

3. 函数原型:
  • 函数名:malloc

  • 形式参数:size_t size:内存块的大小,以字节为单位。本质上就是一个无符号的整型unsigned int

  • 返回值类型:void*:该函数返回一个指针,指向已分配大小的内存,如果请求失败,返回NULL。

  • 举例:

    int *p = (int*)malloc(4);
    
  • 说明:

    • malloc函数分配的内存没有默认值,是不确定的数,大概率是0;
    • malloc函数申请的内存空间连续。

(二)calloc

1. 头文件:#include <stdlib.h>
2. 函数功能:

C库函数void * calloc(size_t nitems,size_t size)分配所需的内存空间,并返回一个指向它的指针。 malloc和calloc之间不同的是,malloc不会设置内存为零,而calloc会设置内存为零。

3. 函数原型:void * calloc(size_t nitems,size_t size)
  • 函数名:calloc

  • 形式参数:

    • size_t nitems:申请多少个
    • size_t size:一个占几个内存单元(一个内存单元等于一个字节)
  • 返回值类型:void*:该函数返回一个指针,指向已分配大小的内存。如果请求失败,返回NULL。

  • 举例:

    int *p = (int*)calloc(3,4); // p指向的空间大小是12个字节
    if(!p)
    {
        printf("内存申请失败!\n");
    }
    printf("内存申请成功!\n");
    
  • 说明:

    • calloc函数分配的内存有默认值,每个单元都是0;

    • calloc函数申请的内存空间连续;

    • calloc大多数为数组中的元素申请内存。

      转存栈中数组中的数据;

      int arr[3] = {10,20,30}; // 在栈区
      
      int *p = (int*)calloc(3,4); // 申请内存,在堆区
      if(!p)
      {
          puts("内存申请失败!\n");
      }
      puts("内存申请成功!\n");
      
      // 转存
      for(int i = 0; i < 3; i++)
      {
          p[i] = arr[i];
      }
      
      // 遍历
      for(int i = 0; i < 3; i++)
      {
          printf("%d ",p[i]);
      }
      printf("\n");
      
      //  p使用完,记得释放内存
      free(p);
      p = NULL; // 内存回收后,建议置空
      

(三)realloc

1. 头文件:#include <stdlib.h>
2. 函数功能:

尝试重新调整之前调用malloc或calloc所分配的p所指向的内存块的大小。

3. 函数原型:void *realloc(void *ptr,size_t size)
  • 函数名:realloc

  • 形式参数:

    • void *ptr:是malloc或calloc的返回值
    • size_t size:重新分配后的内存大小
  • 返回值类型:void*:该函数返回一个指针,指向已分配大小的内存。如果请求失败,返回NULL。

  • 案例:

    int *p = (int*)malloc(4);
    int *w = (int*)realloc(p,20);
    // int *q = (int*)realloc(p,0); // 等效于free(p)
    
    • 说明:
      • realloc以原来malloc返回的内存地址开始,分配总共20个字节的内存空间。
      • 如果原来的内存空间后有20个连续空间,就扩容20-1=16个内存单元,返回原来旧的内存首地址。
      • 如果原来的内存空间后不够20个连续内存空间,就重新找一个内存地址开始,申请20个内存单元。并将原来的数据拷贝到新的内存中,回收旧的内存单元,并返回新的内存首地址。

(四)free

1. 头文件:#include <stdlib.h>
2. 函数功能:

释放之前调用malloc、calloc、realloc所分配的内存空间,访问完记得使用NULL置空。

3. 函数原型:void free(void *ptr)
  • 函数名:free

  • 形式参数:

    • void *ptr:calloc、malloc、realloc的返回值
  • 返回值类型:void:没有返回值

  • 注意:

    • 堆内存中的指针才需要回收,栈中系统会自动回收。
    • 堆内存不能重复回收,运行会报错。
  • 说明:

    • 堆的内存空间相比较栈要大很多。
    • 内存分配函数返回的指针变量可以参与运算(只读),但不能被修改(p++或者p+=i是错误的)

二、void与void*的区别

(一)定义:

  1. void:是空类型,是数据类型的一种
  2. void*:是指针类型,是指针类型的一种,可以匹配任意类型的指针,类似于通配符

(二)void

1. 说明:

void作为返回类型使用,表示没有返回值;作为形参表示形参列表为空,在调用函数时不能给实参

2. 举例:
  // 函数声明
  void fun(void); // 等效于 void fun(); 
  // 函数调用
  fun();

(三)void*

1. 说明:
  • void*是一个指针类型,但该指针的数据类型不明确,无法通过解引用获取内存中的数据,因为void*不知道访问几个内存单元。

  • void*是一种数据类型,可以作为函数返回值类型,也可以作为形参类型。

  • void*类型的变量在使用之前必须强制类型转换,明确它能够访问几个字节的内存空间。

    int *p = (int*)malloc(4);
    double *p2 = (double*)malloc(8);
    
2. 举例:
#include <stdio.h>
#include <stdlib.h>
  
// 函数定义
void* fun(void* p) // 指针函数(返回值类型是指针的函数,此时返回的是不明确类型,需要外部强转)
{
     int *p;
     // double *p;
     // long *p;
     // char *p;
     return p;
}
  
// 函数调用
void mian()
{
     int *p;
     void* a = fun(p); // 这种接收方式,实际上没有意义
     printf("%p\n",a); // 可以正常打印,打印出一个地址
      
     *a = 10; // 编译报错,void*变量不能解引用访问数据
      
     int *w = (int*)a;
     *w = 10; // 编译和运行正常,void*变量a在使用前已经强制类型转换了,数据类型明确了,访问的内存单元明确了
  }  
  • 说明:

    • void*作为返回值类型:这个函数可以返回任意类型(charchar*double*等)的指针。
    • void*作为形参类型:这个函数在调用时,可以给任意类型(charchar*double*等)的指针。
  • 总结:

    • void*类似于通配符,不能对void*类型的变量解引用(因为不明确内存单元的大小)。
    • void*在间接访问(解引用)前要强制类型转换,但不能太随意,否则存和取的数据类型不一致。

三、内存操作函数

我们对于内存的操作借助于stringh这个库提供的内存操作函数。

(一)内存填充–memset 函数

1. 头文件:#include <string.h>
2. 函数功能:

填充s开始的堆内存空间前n个字节,使得每个字节值为c。

3. 函数原型:void *memset(void *s,int c,size_t n);
  • 函数参数:

    • void *s:带操作内存的首地址。
    • int c:填充的字节数据。
    • size_n:填充的字节数。
  • 返回值:返回s

  • 注意:c常常设置为0,用于动态内存初始化。

  • 案例:

    /**
     * 内存操作函数-memset
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void test1()
    {
        // 在堆内存申请了一块存储空间
        int *p = (int*)malloc(4 * sizeof(int));
        if(!p)
       	{
            puts("内存分配失败!");
            return;// 后续代码不需要执行
      	}
        // 给这块内存进行初始化操作(填充)
        memset(p,0, 4 * sizeof(int));
        
        printf("%d\n",*(p+1));
        
        // 内存使用完毕,进行回收
        free(p);
        p = NULL;
    }
    
    int main()
    {
        test1();
        
        return 0;
    }
    
    

(二)内存拷贝–memcpy | memmove函数

1. 头文件:#include <string.h>
2. 函数功能:

拷贝src开始的堆内存空间前n个字节,到dest对应的内存中。

3. 函数原型:

void *memcpy(void *dest,const void *src,size_t n); 适合目标地址与源地址内存无重叠的情况。

void *memmove(void *dest,const void *src,size_t n);

  • 函数参数:

    • void *dest:目标内存首地址。
    • void *src:源内存首地址。
    • size_n:拷贝的字节数。
  • 返回值:返回dest

  • 注意:

    • 内存申请了几个内存空间,就访问几个内存空间,否则数据不安全。
    • memcpy与memmove一般情况下式一样的,更建议使用memmove进行内存拷贝;因为memmove函数是从自适应(从后往前或者从前往后)拷贝,当被拷贝的内存和目的地的内存有重叠时,数据不会出现拷贝错误;而memcpy函数是从前往后拷贝,当被拷贝的内存和目的地的内存有重叠时,数据会出现拷贝错误。
  • 案例:

    /**
     * 内存操作函数-memcpy|memmove
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void test1()
    {
        // 申请内存
        // int *p1 = (int*)malloc(4 * sizeof(int));
        // int *p2 = (int*)malloc(6 * sizeof(int));
        
        // 给p1,p2填充数据,可以使用for循环..
        // for(int i = 0; i < 4; i++)
        //   p1[i] = 10 + i;
        
        // memset(p2,0,6 * sizeof(int));   
        // 创建数组
        int p1[4] = {11,12,13,14};
        int p2[6] = {21,22,23,24,25,26};
        
        // 将p1中的数据通过内存拷贝函数,拷贝到p2
        // memcpy(p2+2,p1+1,2*sizeof(int))   // int p2[6] = {21,22,12,13,25,26}
        memmove(p2+2,p1+1,2*sizeof(int));
        
        // 测试输出数组
     	for(int i = 0; i < 4; i++)
        {
             printf("%4d",p1[i]);
        }
           
        printf("\n");
        
        for(int j = 0; j < 6; j++)
        {
            printf("%4d",p2[j]);
        }
            
        printf("\n");
        
        // 如果使用手动分配的指针,一定要记得释放内存
        // free(p1);
        // free(p2);
        // p1 = NULL;
        // p2 = NULL;
    }
    
    int main()
    {
        test1();
        return 0;
    }
    

(三)内存比较-- memcmp 函数

1. 头文件:#include <string.h>
2. 函数功能:

比较src和dest所代表的内存前n个字节的数据。

3. 函数原型:int memcmp(void *dest,const void *src,size_t n);
  • 函数参数:

    • void *dest:目标内存首地址
    • const viod* src:源内存首地址
    • size_t n:比较的字节数
  • 返回值:

    • 0:数据相同
    • >0:dest中的数据大于src
    • <0:dest中的数据小于src
  • 注意:n一般和src,dest的总容量一样;如果不一样,内存比较的结果就不确定 了。

  • 案例:

    /**
     * 内存操作-memcmp
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    void test1()
    {
        // 申请内存
        int *p1 = (int*)malloc(3*sizeof(int));
        int *p2 = (int*)malloc(4*sizeof(int));
        
        // int p1[4] = {1,0,3,6};
        // int p2[4] = {1,2,3,4};
        // int result = memcmp(p1,p2,4*sizeof(int));
        
        *p1 = 65;
        *p2 = 70;
        
        char *a = (char*)p1;// 类型转换
        char *b = (char*)p2;
        
        printf("%c,%c\n",*a,*b);
        
        int result = memcmp(a+1,b+1,1*sizeof(char));
        printf("%d\n",result);   
    }
    
    int main()
    {
        test1();
    }
    

(四)内存查找–memchr | memrchr函数

1. 头文件:#include <string.h>
2. 函数功能:

在s开始的堆内存空间前n个字节中查找字节数据c

3. 函数原型:int *memchr|*memrchr(const void *s,int c,size_t n);
  • 函数参数:

    • const void *s:待操作内存首地址;
    • int c:待查找的字节数据;
    • size_t n:查找的字节数。
  • 返回值:返回查找到的字节数据地址

  • 注意:如果内存中没有重复数据,memchr和memrchr结果一样;如果内存中有重复数据,memchr和memrchr结果不一样。

    举例:

    void *memrchr(..); // 在使用时编译会报错,需要使用外部声明
    
    // 外部申请
    extern void *memechr(..);
    
  • 案例:

    /**
     * 内存操作-memchr | memrchr
     */
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 声明外部函数
    extern void *memrchr(const void *s,int c,size_t n);
    
    void test1()
    {
         // 申请内存
        int *p = (int*)malloc(4*sizeof(int));
        if(!p)
       	{
            puts("内存分配失败!");
            return;
       	}
        
        // 给变量赋值
        for(int i = 0; i < 4; i++)
       	{
            p[i] = i * 2;
       	}
        p[3] = 4;
        
        // 输出
        for(int i = 0; i < 4; i++)
       	{
            printf("%d,",p[i]);
      	}
        printf("\n");
        
        // 内存查找 memchr
        int *x = (int*)memchr(p,4,4*sizeof(int));
        printf("%p--%p--%d\n",x,p,*x);
        
        // 内存查找 memrchr
        int *y = (int*)memrchr(p,4,4*sizeof(int));
        printf("%p--%p--%d\n",y,p,*y);
        
        // 回收内存
        free(p);
        p = NULL;
    }
    
    int main()
    {
        test1();
    }
    

  • 18
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值