12-C语言的内存管理

news2024/12/25 0:09:44

12-C语言的内存管理

文章目录

  • 12-C语言的内存管理
    • 一、C语言进程的内存布局
      • 1.1 程序与进程
      • 1.2 虚拟内存与物理内存
        • 1.2.1 虚拟内存布局
    • 二、栈空间的特点与管理
    • 三、静态变量
      • 3.1 全局静态变量
      • 3.2 局部静态变量
      • 3.3 为什么需要静态变量?
    • 四、数据段与代码段
      • 4.1 数据段
      • 4.2 代码段
      • 4.3 数据段的特点
    • 五、堆内存
      • 5.1 堆内存的基本特性
      • 5.2 如何申请堆内存
        • 5.3 malloc
        • 5.4 calloc
        • 5.5 `realloc`
      • 5.6 释放堆内存`free`
    • 六、示例代码
      • 总结

一、C语言进程的内存布局

在C语言中,一个程序在运行时,操作系统会为其分配内存空间,这个内存空间可以分为几个不同的区域,每个区域有其特定的用途和特性。这些区域包括:栈、堆、数据段和代码段。理解这些区域有助于编写更高效的代码并调试程序中的内存问题。

1.1 程序与进程

  • 程序:静态的二进制文件,存储在磁盘上。
  • 进程:运行中的程序实例,动态的,操作系统为其分配各种资源,包括内存。

所有的程序被执行起来之后,系统会为他分配各种资源内存,用来存放该进程中用到的各种变量、常量、代码等等。这些不容的内容将会被存放到内存中不同的位置 (区域),不同的内存区域他的特性是有差别。

每一个进程所拥有的内存都是一个虚拟的内存,所谓的虚拟内存是用物理内存中映射(投影)而来的,对于每一个进程而言所有的虚拟内存布局都是一样的。让每个进程都以为自己独自拥有了完整的内存空间。

1.2 虚拟内存与物理内存

  • 虚拟内存:每个进程拥有独立的虚拟内存空间,给进程一种独占整个内存的错觉。这种虚拟内存通过内存管理单元(MMU)映射到物理内存。
  • 物理内存:实际的硬件内存,虚拟内存映射到这里。

物理内存(Physical Memory)
虚拟内存(Virtual Memory)
在这里插入图片描述

1.2.1 虚拟内存布局

在这里插入图片描述

  1. 代码段(Text Segment)

    • 存放程序的可执行代码,包括函数体、控制语句等。
    • 该段是只读的,防止程序意外修改指令。
    • 通常在内存的低地址区域。
  2. 数据段(Data Segment)

    • 存放静态数据全局变量
    • 细分为初始化数据段和未初始化数据段(BSS段)。
      • 初始化数据段:存放已初始化的全局变量和静态变量。
      • BSS段:存放未初始化的全局变量和静态变量,程序开始执行时会被清零。
  3. 堆(Heap)

    • 用于动态分配内存,由程序员控制,使用 malloccallocrealloc 等函数分配,使用 free 释放。
    • 从低地址向高地址增长。
    • 适合用于需要在程序运行期间动态分配的较大数据。
  4. 栈(Stack)

    • 用于存放函数的局部变量函数参数返回地址
    • 栈空间的分配和释放由系统自动管理,遵循LIFO(Last In, First Out)原则。
    • 从高地址向低地址增长。
    • 由于栈空间有限,存放大数据结构时要谨慎。

在这里插入图片描述

二、栈空间的特点与管理

用于存放函数的局部变量函数参数返回地址.
在这里插入图片描述

  • 特点

    • 空间有限,尤其在嵌入式环境中(例如某些系统默认栈大小仅为8MB)。
    • 系统自动管理分配和释放。
    • 函数调用时分配内存,函数返回时释放内存。
  • 管理栈空间

    • 避免在栈上分配大块内存。
    • 使用 ulimit 命令查看和修改栈大小限制(临时修改,重启后恢复默认值)。
    ulimit -a                # 查看当前系统的资源限制,包括栈大小
    ulimit -s 10240          # 临时将栈大小修改为 10MB,重启后会回到默认值
    

注意

  • 每当一个函数被调用的时候, 栈空间会向下增长一段,用来存放该函
  • 当一个函数退出的时候 , 栈空间会向上回缩一段,该空间的所有权将
  • 栈空间的分配与释放,用户是无法干预的, 全部由系统来完成。

三、静态变量

在C语言中,静态变量有两个主要类型:全局静态变量局部静态变量。这些变量在程序的生命周期内具有持久性,存储在数据段中,而不是栈或堆中。

3.1 全局静态变量

全局静态变量定义在函数体外部,并且具有文件作用域,即只能在定义它的文件中访问。

#include <stdio.h>

static int global_var = 1000; // 全局静态变量

void func1() {
    printf("global_var: %d\n", global_var);
}

void func2() {
    global_var += 100;
    printf("global_var after increment: %d\n", global_var);
}

int main() {
    func1(); // 输出: global_var: 1000
    func2(); // 输出: global_var after increment: 1100
    func1(); // 输出: global_var: 1100
    return 0;
}

3.2 局部静态变量

局部静态变量定义在函数体内部,并且具有块作用域,但其生命周期跨越整个程序运行时间。这意味着它在第一次初始化后,其值在函数调用之间保持不变。

#include <stdio.h>

void func() {
    int a = 250; // 局部变量
    static int b = 100; // 静态局部变量,只初始化一次
    printf("a: %d, b: %d\n", ++a, ++b);
}

int main() {
    func(); // 输出: a: 251, b: 101
    func(); // 输出: a: 251, b: 102
    func(); // 输出: a: 251, b: 103
    return 0;
}

在上面的代码中,每次调用 func 函数时,变量 a 都会重新初始化为250,而变量 b 仅在第一次调用时初始化为100,此后 b 保留其在上一次函数调用时的值。

3.3 为什么需要静态变量?

  1. 持久性:静态变量在程序的整个生命周期内都存在。这在需要跨函数调用持久化数据时非常有用。
  2. 局部静态变量:在希望一个局部变量的值在多次函数调用之间保持不变的情况下,可以使用静态局部变量。
  3. 全局静态变量:在需要将变量的作用域限制在单个文件内时,可以使用全局静态变量。它们不会被其他文件中的函数访问到,增强了数据的封装性。

四、数据段与代码段

4.1 数据段

数据段分为三个部分:.bss、.data和.rodata。
在这里插入图片描述

  1. .bss段:存放未初始化的静态数据,程序运行时会自动初始化为0
  2. .data段:存放已初始化的静态数据。
  3. .rodata段:存放只读数据常量),如字符串常量,不允许修改。
#include <stdio.h>

int uninitialized_global; // 位于 .bss 段
int initialized_global = 10; // 位于 .data 段
const char *msg = "Hello, World!"; // 位于 .rodata 段

int main() {
    printf("uninitialized_global: %d\n", uninitialized_global); // 输出: 0
    printf("initialized_global: %d\n", initialized_global); // 输出: 10
    printf("msg: %s\n", msg); // 输出: Hello, World!
    return 0;
}

4.2 代码段

代码段存放程序的可执行代码,包括用户编写的函数编译器生成的系统初始化代码。代码段通常是只读的,以防止程序无意中修改指令。
在这里插入图片描述

#include <stdio.h>

void func() {
    printf("This is a function.\n");
}

int main() {
    func(); // 输出: This is a function.
    return 0;
}

4.3 数据段的特点

  • 自动初始化:未初始化的静态数据会自动初始化为0。
  • 初始化执行一次:静态数据的初始化语句在程序加载时执行一次。
  • 持久性:静态数据的内存从程序开始运行到程序结束都存在,与进程共存亡。

五、堆内存

堆内存,又称动态内存自由内存,是唯一一个由开发者随意分配与释放的内存空间。堆内存的具体申请大小和使用时长由程序员决定。

5.1 堆内存的基本特性

  1. 大空间:相对于栈空间,堆空间大很多,堆的大小受限于物理内存,系统不会对堆空间进行限制。
  2. 从下往上增长:堆内存是从低地址向高地址增长的。
  3. 匿名内存:堆内存没有名字,只能通过指针来访问。(不像栈空间那样有名字)
  4. 手动管理:堆内存的申请和释放由程序员自行管理,申请的内存需要手动释放,直到程序退出。

5.2 如何申请堆内存

5.3 malloc

malloc 函数用于向系统申请内存,但不会清空这块内存。

malloc (向系统申请内存)
头文件:

  • #include <stdlib.h>
    函数原型:

  • void *malloc(size_t size);

参数分析:

  • size ‐‐> 需要申请的内存 (字节为单位)

返回值:

  • 成功 返回一个指向成功申请到内存的指针(入口地址)
  • 失败 返回 NULL
#include <stdlib.h>
#include <stdio.h>

int main() {
    // 申请可以存放10个整数的堆空间
    int *p = (int *)malloc(10 * sizeof(int));
    if (p == NULL) {
        // 申请失败
        perror("malloc failed");
        return 1;
    }
    
    // 初始化并打印数组
    for (int i = 0; i < 10; i++) {
        *(p + i) = i;
        printf("*(p+%d): %d\n", i, *(p + i));
    }

    // 打印指针地址--> &p 打印的是栈空间地址     p中所存放的地址为堆空间地址
    printf("&p: %p -> %p\n", (void *)&p, (void *)p);

    // 释放内存
    free(p);
    return 0;
}

由终端输入存入malloc所申请的内存中:
在这里插入图片描述

注意在上面的截图中,scanf("%[\n]s",p);–>[\n] 表示读取所有非换行符 \n 的字符,直到遇到换行符为止。避免了在输入空格时就截至了的情况。

5.4 calloc

calloc 函数用于向系统申请内存,会将内存清空(初始化为0)。

calloc (向系统申请内存)

头文件:

  • #include <stdlib.h>

函数原型:

  • void *calloc(size_t nmemb, size_t size);

参数分析:

  • nmemb ‐‐ > N 块内存(连续的)
  • size ‐‐ > 每一块内存的大小尺寸

返回值:

  • 成功 返回一个指向成功申请到内存的指针(入口地址)
  • 失败 返回 NUL
#include <stdlib.h>
#include <stdio.h>

int main() {
    // 申请10块连续的内存,每块大小为sizeof(int)
    int *p = (int *)calloc(10, sizeof(int));
    if (p == NULL) {
        // 申请失败
        perror("calloc failed");
        return 1;
    }
    
    // 初始化并打印数组
    for (int i = 0; i < 10; i++) {
        *(p + i) = i + 998;
        printf("*(p+%d): %d\n", i, *(p + i));
    }

    // 释放内存
    free(p);
    return 0;
}
5.5 realloc

realloc 函数用于重新分配内存大小,可以扩大或缩小内存。

realloc (重新申请空间)
头文件:

  • #include <stdlib.h>

函数原型:

  • void *realloc(void *ptr, size_t size);

参数分析:

  • ptr ‐‐> 需要 扩容/缩小 的内存的入口地址
  • size ‐‐> 目前需要的大小

返回值:

  • 成功 返回修改后的地址
  • 失败 NULL
#include <stdlib.h>
#include <stdio.h>

int main() {
    // 申请10块连续的内存,每块大小为sizeof(int)
    int *p = (int *)calloc(10, sizeof(int));
    if (p == NULL) {
        // 申请失败
        perror("calloc failed");
        return 1;
    }
    
    // 初始化并打印数组
    for (int i = 0; i < 10; i++) {
        *(p + i) = i + 998;
        printf("*(p+%d): %d\n", i, *(p + i));
    }

    // 打印重新分配前的指针地址
    printf("重新分配前p: %p\n", (void *)p);

    // 重新分配内存--> // 如果重新申请内存需要另找宝地 , 那么 原本的地址p会被释放掉
    int *p1 = (int *)realloc(p, 128 * sizeof(int));
    if (p1 == NULL) {
        // 重新分配失败
        perror("realloc failed");
        free(p);
        return 1;
    }

    // 原指针已经被释放了,但是为了防止野指针,所以将原指针置为NULL,防止成为野指针
    p = NULL;

    // 打印重新分配后的指针地址
    printf("重新分配后p1: %p\n", (void *)p1);

    // 打印原数据
    for (int i = 0; i < 10; i++) {
        printf("*(p1+%d): %d\n", i, *(p1 + i));
    }

    // 释放内存
    free(p1);//注意不是p
    return 0;
}

5.6 释放堆内存free

free 函数用于释放堆内存。

free(释放堆内存)
头文件:
-#include <stdlib.h>

函数原型:

  • void free(void *ptr);

参数分析:

  • ptr ‐‐> 需要释放的内存的入口地址

返回值;

#include <stdlib.h>
#include <stdio.h>

int main() {
    // 申请内存
    int *p = (int *)malloc(10 * sizeof(int));
    if (p == NULL) {
        perror("malloc failed");
        return 1;
    }

    // 使用内存...

    // 释放内存
    free(p);

    return 0;
}

释放内存的含义是将当前内存的所有权交回给系统,但不会清空内存的内容。释放内存后,需要将指针置为NULL,防止成为野指针。

总结:

  • malloc 申请内存但不清空。
  • calloc 申请内存并初始化为0。
  • realloc 重新分配内存大小。
  • free 释放内存。

释放内存后,记得将指针置为NULL,防止成为野指针。

六、示例代码

以下代码展示了栈、堆和数据段的使用:

#include <stdio.h>
#include <stdlib.h>

// 全局变量(位于数据段)
int global_var = 10;
int uninitialized_global_var;

void function() {
    // 局部变量(位于栈)
    int local_var = 20;
    printf("Local variable: %d\n", local_var);
}

int main() {
    // 动态分配内存(位于堆)
    int *heap_var = (int *)malloc(sizeof(int));
    if (heap_var == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    *heap_var = 30;
    printf("Heap variable: %d\n", *heap_var);

    function();

    // 打印全局变量
    printf("Global variable: %d\n", global_var);
    printf("Uninitialized global variable: %d\n", uninitialized_global_var);

    // 释放堆内存
    free(heap_var);

    return 0;
}

总结

  • 代码段:存放程序的可执行代码,通常只读。
  • 数据段:存放全局变量和静态变量,分为已初始化和未初始化部分。
  • :用于动态内存分配,从低地址向高地址增长。
  • :用于存放局部变量和函数调用信息,从高地址向低地址增长。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VSCode数据库插件

Visual Studio Code (VS Code) 是一个非常流行的源代码编辑器&#xff0c;它通过丰富的插件生态系统提供了大量的功能扩展。对于数据库操作&#xff0c;VS Code 提供了几种插件&#xff0c;其中“Database Client”系列插件是比较受欢迎的选择之一&#xff0c;它包括了对多种数…

Django 连接mysql数据库配置

1&#xff0c;提前创建注册的app1应用 Test/Test/settings.py python manage.py startapp app1 2&#xff0c;配置mysql数据库连接 Test/Test/settings.py DATABASES {default: {ENGINE: django.db.backends.mysql,# 数据库名字NAME: db1,# 连接mysql数据库用户名USER: ro…

力扣 240.搜素矩阵II

题目描述&#xff1a; 编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性&#xff1a; 每行的元素从左到右升序排列。每列的元素从上到下升序排列。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,4,7,11,15],[2,5,8,12,19],[3,6,9…

【自动部署】4.阿里云ECS服务器 IDEA自动部署项目

如何在IDEA中,自动部署项目到阿里云ECS服务器?今天我们就来实现一键部署功能。 执行maven命令打包免密登录,在IEDA中连接服务器执行stop服务上传jar包到服务器执行start脚本查看运行日志1.安装Alibaba Cloud Toolkit 2.配置host 3.自动化部署后端 右击项目,选择Alibaba CL…

Java Web学习笔记28——Element案例

案例&#xff1a; 根据页面原型完成员工管理页面开发&#xff0c;并通过Axios完成数据异步加载。 服务端数据获取地址&#xff0c;也就是API接口。 这个URL返回的是JSON格式的数据。 表格就是把JSON格式的数据渲染显示在页面中。 页面分为三个部分: 页头&#xff1a; 菜单…

学习笔记——路由网络基础——直连路由(direct)

二、直连路由(direct) 直连路由(direct)&#xff1a;直接相连&#xff0c;接口配置好ip地址并up后自动生成的路由。默认优先级为0 Destination&#xff1a;表示路由的目的地址。用来标识IP包的目的地址或目的网络。 Mask&#xff1a;表示目的地址的子网掩码长度。 与目的地址…

【创作活动】面对层出不穷的AI大模型产品我们应该怎么选择?

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Java | Leetcode Java题解之第139题单词拆分

题目&#xff1a; 题解&#xff1a; public class Solution {public boolean wordBreak(String s, List<String> wordDict) {Set<String> wordDictSet new HashSet(wordDict);boolean[] dp new boolean[s.length() 1];dp[0] true;for (int i 1; i < s.len…

[Vue3:axios]:实现实现登陆页面前后端请求,并用Vite解决跨域问题

文章目录 一&#xff1a;前置依赖查看依赖安装 axios&#xff1a;npm install axios 二&#xff1a;配置文件&#xff1a;创建一个用于全局使用的axios实例&#xff0c;并在main.js或main.ts文件中将其配置为全局属性。根目录mainjs文件引入axios 三&#xff1a;登录页面发送登…

C++中的一些困惑(长期更新中)

C中的一些困惑 文章目录 C中的一些困惑1. using std::具体命名与using namespace std;2. 【int \*p[10] 】与 【int (\*p)[10]】3. main()函数可带参&#xff0c;参从何来&#xff1f;4. constexpr函数的返回值可不为常量&#xff0c;那这时constexpr关键字作用是什么&#xff…

Mybatis03-ResultMap及分页

1、属性名和字段名不一致问题 1.问题 数据库中的字段 新建一个项目Mybatis-04&#xff0c;拷贝之前&#xff0c;测试实体类字段不一致的情况 public class User {private int id;private String name;private String password; }select * from mybatis.user where id #{id} …

计算机网络 ——网络层(IPv4地址)

计算机网络 ——网络层&#xff08;IPv4地址&#xff09; 什么是IPv4地址IP地址的分类特殊的IP地址 查看自己的IPv4地址 我们今天来看IPv4地址&#xff1a; 什么是IPv4地址 IPv4&#xff08;Internet Protocol version 4&#xff09;是第四版互联网协议&#xff0c;是第一个被…

【YOLO系列】YOLOv1学习(PyTorch)原理加代码

论文网址&#xff1a;https://arxiv.org/pdf/1506.02640 训练集博客链接&#xff1a;目标检测实战篇1——数据集介绍(PASCAL VOC&#xff0c;MS COCO)-CSDN博客 代码文件&#xff1a;在我资源里&#xff0c;但是好像还在审核&#xff0c;大家可以先可以&#xff0c;如果没有的…

二分【1】二分查找框架 查找指定元素

目录 二分查找 基本思想 几种情况汇总 一。严格递增序列 1.查找本身 2.查找第一个大于等于自己的 3.查找第一个大于自己的 4.严格递减序列 二。有重复元素 1.取其中第一个出现的 2.取其中最后一个出现的 二分查找 基本思想 几种情况汇总 一。严格递增序列 1.查找本身…

Vitis HLS 学习笔记--接口存储器布局模型

目录 1. 简介 2. 详解 2.1 数据对齐 2.2 数据结构填充 3. 总结 1. 简介 软件开发者写的程序会在 CPU 处理器上运行&#xff0c;而硬件开发者设计的“内核”则会在 FPGA 上运行。这两部分需要通过一个精心设计的接口来沟通&#xff0c;就像两个人用对讲机来交流一样。为了…

Java | Leetcode Java题解之第140题单词拆分II

题目&#xff1a; 题解&#xff1a; class Solution {public List<String> wordBreak(String s, List<String> wordDict) {Map<Integer, List<List<String>>> map new HashMap<Integer, List<List<String>>>();List<List…

16_ Vue.js高级指南:条件渲染、列表渲染与数据双向绑定

文章目录 1. 条件渲染v-if2. 列表渲染v-for3. 数据双项绑定v-model4. 计算属性Appendix 1. 条件渲染v-if v-if标签直接接收函数或boolean类型变量 v-if 为true&#xff0c;则当前元素会进入到dom树v-else会自动执行 前方v-if的取反操作 v-show v-show值为true则值展示值不展示…

Qt基于SQLite数据库的增删查改demo

一、效果展示 在Qt创建如图UI界面&#xff0c;主要包括“查询”、“添加”、“删除”、“更新”&#xff0c;四个功能模块。 查询&#xff1a;从数据库中查找所有数据的所有内容&#xff0c;并显示在左边的QListWidget控件上。 添加&#xff1a;在右边的QLineEdit标签上输入需…

C++ | Leetcode C++题解之第139题单词拆分

题目&#xff1a; 题解&#xff1a; class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {auto wordDictSet unordered_set <string> ();for (auto word: wordDict) {wordDictSet.insert(word);}auto dp vector <bool> (s.…

HC-05蓝牙模块配置连接和使用

文章目录 1. 前期准备 2. 进入AT模式 3. 电脑串口配置 4. 配置过程 5. 主从机蓝牙连接 6. 蓝牙模块HC-05和电脑连接 1. 前期准备 首先需要准备一个USB转TTL连接器&#xff0c;电脑安装一个串口助手&#xff0c;然后按照下面的连接方式将其相连。 VCCVCCGNDGNDRXDTXDTXD…