【C】内存布局

news/2025/2/21 7:01:47/文章来源:https://www.cnblogs.com/Shima-Rin/p/18716171

动态内存管理

动态内存管理的几个函数

  • malloc -- 申请动态内存空间
  • free -- 释放动态内存空间
  • calloc -- 申请并初始化一系列内存空间
  • realloc -- 重新分配内存空间

malloc

  • 函数原型:

    • void *malloc(size_t size);
  • malloc函数向系统申请分配size个字节的内存空间,并返回一个指向这块空间的指针void类型)。

  • 如果函数调用成功,返回一个指向申请的内存空间的指针,由于返回类型是 void 指针(void *,所以它可以被转换成任何类型的数据;如果函数调用失败,返回值是NULL。另外,如果 size 参数设置为0, 返回值也可能是 NULL ,但这并不意味着函数调用失败。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {int *ptr;ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof()//  (int *)将返回的void指针转化为int指针if (ptr == NULL){printf("分配内存失败了! \n");exit(1); //退出程序,需导入头文件<stdlib.h>}printf("请输入一个整数 : ");scanf("%d", ptr);printf("你输入的整数是 : %d\n", *ptr);return 0;
    }
    

    运行结果

    请输入一个整数 : 100
    你输入的整数是 : 100
    
  • 注意:由于malloc申请的空间位于内存的上,如果你不主动释放堆上的内存资源,那么它将永远存在,直到程序被关闭。所以当我们不再使用这块内存时,要自己动手释放,避免造成内存泄漏。

  • malloc不仅可以申请存储基本数据类型的空间,事实上它还可以申请一块任意尺寸的内存空间。对于后者,由于申请得到的空间是连续的,所以我们经常用数组来进行索引即可:

    #include <stdio.h>
    #include <stdlib.h>
    int main(void)
    {int *ptr = NULL;int num, i;printf("请输入待录入整数的个数:");scanf("%d", &num);ptr = (int *)malloc(num * sizeof(int));for (i = 0; i < num; i++){printf("请录入第%d个整数:", i+1);scanf("%d", &ptr[i]);}printf("你录入的整数是:");for (i = 0; i < num; i++){printf("%d ", ptr[i]);}putchar('\n');free(ptr);return 0;
    }
    

free

  • 函数原型:

    • void free(void *ptr);
  • free 函数释放 ptr 参数指向的内存空间。该内存空间必须是由 malloccallocrealloc函数申请的。否则,该函数将导致未定义行为。如果 ptr
    参数是 NULL不执行任何操作

    注意:该函数并不会修改 ptr 参数的值,所以调用后它仍然指向原来的地方(变为非法空间)。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {int *ptr;ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof()//  (int *)将返回的void指针转化为int指针if (ptr == NULL){printf("分配内存失败了! \n");exit(1); //退出程序,需导入头文件<stdlib.h>}printf("请输入一个整数 : ");scanf("%d", ptr);printf("你输入的整数是 : %d\n", *ptr);free(ptr); //释放内存空间printf("你输入的整数是 : %d\n", *ptr); //再次打印会出错return 0;
    }
    

内存泄漏

导致内存泄漏主要有两种情况:

  • 隐式内存泄漏(即用完内存块没有及时使用free函数释放)

    注: 下面这段程序可能会造成系统假死,运行需谨慎

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {while (1) // 死循环{malloc(1024); //不断地申请内存空间}return 0;
    }
    
  • 丢失内存块地址

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {int *ptr;int  num = 123;ptr = (int *)malloc(sizeof(int)); //因为不是所有的操作系统的int都是4个字节,所以参数使用sizeof()//  (int *)将返回的void指针转化为int指针if (ptr == NULL){printf("分配内存失败了! \n");exit(1); //退出程序,需导入头文件<stdlib.h>}printf("请输入一个整数 : ");scanf("%d", ptr);printf("你输入的整数是 : %d\n", *ptr);ptr = &num; //ptr指向局部变量printf("你输入的整数是 : %d\n", *ptr);free(ptr); //此时ptr指向的不是malloc申请的内存空间,使用free函数释放会出错return 0;
    }
    

初始化内存空间

由于malloc并不会帮你初始化申请的内存空间,所以需要自己进行初始化。

memset

  • 以men开头的函数被编入字符串标准库,函数的声明包含在string.h这个头文件中:

    • memset —— 使用一个常量字节填充内存空间
    • memcpy —— 拷贝内存空间
    • memmove —— 拷贝内存空间
    • memcmp —— 比较内存空间
    • memchr —— 在内存空间中搜索一个字符
  • memset函数可以用来初始化内存空间

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #define N 10
    int main(void)
    {int *ptr = NULL;int i;ptr  = (int *)malloc(N*sizeof(int));if (ptr == NULL){exit(1);}memset(ptr, 0, N*sizeof(int));for (i = 0;i < N; i++){printf("%d ",ptr[i]);}putchar('\n');free(ptr);
    }
    

calloc

  • 函数原型:

    • void *calloc(size_t nmemb, size_t size);
  • calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb * size),这些内存空间全部被初始化为0.

  • calloc函数与malloc函数的一个重要区别是:

    • calloc 函数在申请完内存后,自动初始化该内存空间为零
    • malloc 函数不进行初始化操作,里边数据是随机的
  • 下面两种写法是等价的

    // calloc() 分配内存空间并初始化
    int *ptr = (int *)calloc(8, sizeof(int)); //两个参数
    
    // malloc() 分配内存空间并用 memset() 初始化
    int *ptr = (int *)malloc(8 * sizeof(int));  //一个参数
    memset(ptr, 0, 8 * sizeof(int));
    

拓展内存空间

memcpy

有时候我们可能会需要对原来的分配的内存空间进行拓展,这时就需要再调用一次malloc 申请一个更大的内存空间,再使用memcpy将原来的数据搬进去

  • 函数原型:

    • void *memcpy(void *dest,const void *src, size_t n);
  • memcpy 函数从 src 指向的内存空间拷贝 n 个字节到dest指向的内存空间。srcdest指向的内存区域不能出现重叠,否则应该使用 memmove 函数。

  • memcpy函数并不关心被复制的数据类型,只是逐字节地进行复制,这给函数的使用带来了很大的灵活性,可以面向任何数据类型进行复制。

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{int *ptr1 = NULL;int *ptr2 = NULL;//第一次申请的内存空间ptr1 = (int *)malloc(10 * sizeof(int));//进行若干操作之后发现ptr1申请的内存空间不够用//第二次申请的内存空间ptr2 = (int *)malloc(20 * sizeof(int));//将ptr1的数据拷贝到ptr2中memcpy(ptr2, ptr1, 10);free(ptr1);//对ptr2申请的内存空间进行若干操作...free(ptr2); //释放对ptr2申请的内存空间return 0;
}

realloc

  • 函数原型:

    • void *realloc(void *ptr, size_t size);
  • 以下几点是需要注意的:

    • realloc函数修改 ptr指向的内存空间大小为size字节
    • 如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间的大小小于旧的内存空间,可能会导致数据丢失,慎用!
    • 该函数将移动内存空间的数据并返回新的指针
    • 如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)
    • 如果size参数为 0,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)
    • 除非ptr参数为NULL,否则ptr的值必须由先前调用malloccallocrealloc 函数返回
  • 设计一个程序,要求:

    • 不使用malloc函数,使用realloc函数申请内存空间
    • 让用户一直输入一个整数,用户每输入一个整数就新增加一个整形空间存放。直到用户输入-1表示输入结束,再把输入的所有整数打印出来
    #include<stdio.h>
    #include<stdlib.h>int main(void)
    {int i, num;int count = 0;int *ptr = NULL; //注意,这里必须初始化为NULLdo{printf("请输入一个整数(输入-1表示结束) : ");scanf("%d",&num);count++;ptr = (int *)realloc(ptr, count * sizeof(int));if (ptr == NULL){exit(1);}ptr[count-1] = num;}while (num != -1);printf("输入的整数分别是 : ");for (i=0;i<count;i++){printf("%d ",ptr[i]);}putchar('\n');free(ptr);return 0;
    }
    

    运行结果

    请输入一个整数(输入-1表示结束) : 1
    请输入一个整数(输入-1表示结束) : 2
    请输入一个整数(输入-1表示结束) : 3
    请输入一个整数(输入-1表示结束) : -1
    输入的整数分别是 : 1 2 3 -1
    //如果不想打印-1,把for循环条件表达式改为"i<count-1"即可
    

C语言的内存布局

#include<stdio.h>
#include<stdlib.h>
int global_uninit_var;
int global_init_var1 = 520;
int global_init_var2 = 880;
void func(void);
void func(void)
{;
}
int main(void)
{int local_var1;int local_var2;static int static_uninit_var;static int static_init_var = 456;char *str1 = "I love FishC.com!";char *str2 = "You are right!";int *malloc_var = (int *)malloc(sizeof(int));printf("addr of func -> %p\n",func);printf("addr of str1 -> %p\n",str1);printf("addr of str2 -> %p\n",str2);printf("addr of global_init_var1 -> %p\n",&global_init_var1);printf("addr of global_init_var2 -> %p\n",&global_init_var2);printf("addr of static_init_var -> %p\n",&static_init_var);printf("addr of static_uninit_var -> %p\n",&static_uninit_var);printf("addr of global_uninit_var -> %p\n",&global_uninit_var);printf("addr of malloc_var -> %p\n",malloc_var);printf("addr of local_var1 -> %p\n",&local_var1);printf("addr of local_var2 -> %p\n",&local_var2);return 0;
}

运行结果:

addr of func -> 00007ff754751591
addr of str1 -> 00007ff754759000
addr of str2 -> 00007ff754759012
addr of global_init_var1 -> 00007ff754758010
addr of global_init_var2 -> 00007ff754758014
addr of static_init_var -> 00007ff754758018
addr of static_uninit_var -> 00007ff75475c044
addr of global_uninit_var -> 00007ff75475c040
addr of malloc_var -> 0000021f4af616a0
addr of local_var1 -> 000000d8845ff674
addr of local_var2 -> 000000d8845ff670
//注意: 每个人的结果不一定相同

规律:

C语言内存空间划分

根据内存地址从低到高分别划分为:

  • 代码段(Text segment)
  • 数据段(Initialized data segment)
  • BSS段(Bss segment/Uninitialized data segment)
  • 栈(Stack)
  • 堆(Heap)

代码段

  • 代码段通常是指用来存放程序执行代码的一块内存区域。
  • 这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读。
  • 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

数据段

  • 数据段通常用来存放已经初始化的全局变量和局部静态变量。

BSS段

  • BSS 段通常是指用来存放程序中未初始化的全局变量的一块内存区域。
  • BSS 是英文 Block Started by Symbol 的简称,这个区段中的数据在程序运行前将被自动初始化为数字 0。

  • 前边我们学习了动态内存管理函数,使用它们申请的内存空间就是分配在这个堆里边。
  • 所以,堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。
  • 当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上;当利用 free等函数释放内存时,被释放的内存从堆中被剔除。

  • 栈是函数执行的内存区域,通常和堆共享同一片区域。

栈和堆的区别

堆和栈则是 C 语言运行时最重要的元素,下面我们将两者进行对比。

  • 申请方式

    • 堆由程序员手动申请
    • 栈由系统自动分配
  • 释放方式

    • 堆由程序员手动释放
    • 栈由系统自动释放
  • 生存周期

    • 堆的生存周期由动态申请到程序员主动释放为止,不同函数之间均可自由访问

    • 栈的生存周期由函数调用开始到函数返回时结束,函数之间的局部变量不能互相访问

      #include<stdio.h>
      #include<stdlib.h>
      int *func(void)
      {int *ptr = NULL; ptr = (int *)malloc(sizeof(int));//在堆上申请一个内存空间if (ptr == NULL){exit(1);}*ptr = 520;return ptr; //返回ptr的地址
      }
      int main(void)
      {int *ptr = NULL;ptr = func(); printf("%d\n",*ptr);//在main函数中访问func函数中申请的内存空间的值free(ptr);
      }
      

      注意:这段代码中,在func函数中申请内存空间,在main函数中访问并释放。但实战中不建议这样做,建议在哪个函数中申请了内存空间就在哪个函数中释放。

  • 发展方向

    • 堆和其它区段一样,都是从低地址向高地址发展

    • 栈则相反,是由高地址向低地址发展

      #include<stdio.h>
      #include<stdlib.h>
      int main(void)
      {int *ptr1 = NULL; //指针变量存放在栈中int *ptr2 = NULL;ptr1 = (int *)malloc(sizeof(int)); //内存空间存放在堆中ptr2 = (int *)malloc(sizeof(int));printf("stack: %p -> %p\n",&ptr1, &ptr2);//栈  ->  指针变量的地址printf("heap: %p -> %p\n",ptr1,ptr2);//堆  ->  指针变量指向的地址return 0;
      }
      

      运行结果

      stack: 00000060fadffb28 -> 00000060fadffb20  
      //栈   高    ->     低
      heap: 000001a4c5fe16a0 -> 000001a4c5fe16e0   
      //堆   低    ->     高
      

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/883923.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C】指针

指针 变量在内存中的存放 在内存中,字节是最小的存储单元。对于一个内存单元来说,指针就是单元的地址,每个地址可以存放一个字节的数据。存放一个整形变量(int)就需要动用到四个存储单元。 在内存中完全没有存储变量名的必要。 因为编译器知道具体每一个变量名对应的存放地址…

VMwareWorkstation pro 17下载与安装

‌VMware Workstation Pro 17‌是一款功能强大的虚拟机软件,专为开发者和IT专业人士设计。它允许用户在一台物理计算机上创建和运行多个虚拟操作系统,从而实现跨平台的开发、测试和部署。VMware Workstation Pro 17采用了先进的虚拟化技术,能够在单个物理主机上运行多个虚拟…

我的“ DeepSeek 越来越闲了”!讯飞星火无限调用 API 攻略来了

最近DeepSeek 确实挺忙,网页繁忙,API资源紧张。 添加图片注释,不超过 140 字(可选) 开源模型也很忙,被下载了 267万次!!! 添加图片注释,不超过 140 字(可选) 但是,我的 DeepSeek 要越来越闲了。腾讯云的接口刚用上,讯飞星火的又来了。 添加图片注释,不超过 1…

【C】数组

数组 数组的定义语法:类型 数组名[元素个数] (方括号内只能是常量或者常量表达式) int a[6]; char b[24]; double c[3];上面几个类型分别占用内存的字节数为: int a[6] ==> 4 * 6 = 24 char b[24] ==> 1 * 24 = 24 double c[3] …

【牛客训练记录】牛客小白月赛110

训练情况赛后反思 C题没看到偶数,导致打错表了TAT,然后浪费了一点时间 A题 编号每 \(500\) 一组,我们分别除 \(500\) 和取模 \(500\) 就行了点击查看代码 #include <bits/stdc++.h> // #define int long long #define endl \nusing namespace std;void solve(){int x;…

华为FusionCompute虚拟化平台

一、华为FusionCompute虚拟化套件介绍 华为FusionCompute虚拟化套件是业界领先的虚拟化解决方案,能够帮助客户带来如下的价值,从而大幅提升数据中心基础设施的效率。帮助客户提升数据中心基础设施的资源利用率; 帮助客户成倍缩短业务上线周期; 帮助客户成倍降低数据中心能耗…

卡特兰数学习笔记

引入 从 \((0,0)\) 走到 \((n,n)\),求不越过 \(y=x\) 的方案数。 不考虑是否合法的方案数是 \(\binom{2n}{n}\),即从 \(2n\) 个移动中选 \(n\) 个向右的。 接下来考虑不合法的情况,不合法当且仅当碰到了 \(y=x+1\) 这条直线,设这个点是 \((p,p+1)\),将后面的折线沿着 \(y=…

SpringCloud自定义loadbalancer实现标签路由

一、背景最近前端反应开发环境有时候调接口会很慢,原因是有开发图方便将本地服务注册到开发环境,请求路由到开发本地导致, 为了解决该问题想到可以通过标签路由的方式避免该问题,实现前端联调和开发自测互不干扰。该方案除了用于本地调试,还可以用于用户灰度发布。 二、实…

2025.2.14鲜花

ln将卷积转为加法。推歌 (看fengwu博客时候看见的) 《堕》 星河挂在天上 保护璀璨月亮 而你在我心中宛如月光 为你痴为你狂 为你笑为你闯 为你悲为你伤 为你扬 她是踏碎星河落入我梦境的幻想 环遍星系为你寻找的力量 神明给我在最难熬的时光 留下唯一的星光 堕入日月星辉之中…

解密prompt系列48. DeepSeek R1 Kimi 1.5长思维链 - RL Scaling

春节前DeepSeek R1和Kimi1.5炸翻天了,之前大家推测的O1的实现路径,多数都集中在MCTS推理优化,以及STaR等样本自优化方案等等,结果DeepSeek和Kiim直接出手揭示了reasoning的新路线不一定在SFT和Inference Scaling,也可以在RL。也算是Post Train阶段新的Scaling方向,几个核…

求勾股数

基本概念 众所周知(3,4,5)和(6,8,10)是两组勾股数,区别是前一组三个数的公因数是一而后一组的不是。像第一组勾股数一样三个数之间两两互质的就叫做 本原勾股数。而且将一组本原勾股数里的三个数同时扩大相同的倍数得到的一组数还是勾股数(只不过不是本原勾股数)。所…

腾讯元宝接入 DeepSeek R1 模型,支持深度思考 + 联网搜索,好用不卡机!

前言 腾讯元宝AI产品于2025年2月13日在应用商店发布更新,正式接入了DeepSeek R1模型,并宣布该模型已联网、满血上线,DeepSeek+腾讯混元,好用不卡机。腾讯元宝介绍 腾讯元宝是依托于腾讯混元、DeepSeek等大模型,基于跨知识领域和自然语言理解能力的大模型AI产品。元宝期望通…