C语言09--进程的内存镜像

2024-09-06 13:20
文章标签 语言 镜像 内存 进程 09

本文主要是介绍C语言09--进程的内存镜像,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C进程内存布局

        任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因此我们需要研究C语言的内存布局,逐个了解不同内存区域的特性。

        每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存,就是从实际物理内存映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。

  • PM:Physical Memory,物理内存。
  • VM:Virtual Memory,虚拟内存。

将其中一个C语言含如进程的虚拟内存放大来看,会发现其内部包下区域:

  • 栈(stack)
  • 堆(heap)
  • 数据段
  • 代码段

虚拟内存中,内核区段对于应用程序而言是禁闭的,它们用于存放操作系统的关键性代码,另外由于 Linux 系统的历史性原因,在虚拟内存的最底端 0x0 ~ 0x08048000 之间也有一段禁闭的区段,该区段也是不可访问的。

虚拟内存中各个区段的详细内容:

栈内存

  • 什么东西存储在栈内存中?
    • 环境变量 , 使用命令env获得的所有东西都成为环境变量
    • 命令行参数 比如运行程序时所携带的参数: ./a.out hello Even GZ2497
    • 局部变量(包括形参), 在函数体内定义的所有变量
    • 由高地址向低地址增长
  • 栈内存有什么特点?
    • 空间有限,尤其在嵌入式环境下。因此不可以用来存储尺寸太大的变量。.

ulimit -a
real-time non-blocking time  (microseconds, -R) unlimited
core file size              (blocks, -c) 0
data seg size               (kbytes, -d) unlimited
scheduling priority                 (-e) 0
file size                   (blocks, -f) unlimited
pending signals                     (-i) 7823
max locked memory           (kbytes, -l) 64
max memory size             (kbytes, -m) unlimited
open files                          (-n) 65536
pipe size                (512 bytes, -p) 8
POSIX message queues         (bytes, -q) 819200
real-time priority                  (-r) 0
【默认栈大小】stack size      (kbytes, -s) 8192  = 8M
cpu time                   (seconds, -t) unlimited
max user processes                  (-u) 7823
virtual memory              (kbytes, -v) unlimited
file locks                          (-x) unlimited
    • 每当一个函数被调用,栈就会向下增长一段(从高地址-》低地址),用以存储该函数的局部变量。
    • 每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
  • 注意:
    • 栈内存的分配和释放,都是由系统规定(自动完成)的,我们无法干预。

  • 示例代码:
void func(int a, int *p) // 在函数 func 的栈内存中分配
{double f1, f2;        // 在函数 func 的栈内存中分配...                   // 退出函数 func 时,系统的栈向上缩减,释放内存
}int main(void)
{int m  = 100;  // 在函数 main 的栈内存中分配func(m, &m);  // 调用func时,系统的栈内存向下增长
}

静态数据

C语言中,静态数据指得是数据的生命周期是固定(内存一旦被分配出来就不会被释放,直到程序退出),静态数据有两种:

  • 全局变量:定义在函数外部的变量。
  • 静态局部变量:定义在函数内部,且被static修饰的变量。
  • 示例:
int a; // 全局变量,退出整个程序之前不会释放
void f(void)
{static int b; // 静态局部变量,退出整个程序之前不会释放printf("%d\n", b);b++;
}int main(void)
{f();f(); // 重复调用函数 f(),会使静态局部变量 b 的值不断增大
}
  • 为什么需要静态数据?
  1. 全局变量在默认的情况下,对所有文件可见(全局可见),为某些需要在各个不同文件和函数间访问的数据提供操作上的方便。
  2. 当我们希望一个函数退出后依然能保留局部变量的值,以便于下次调用时还能用时,静态局部变量可帮助实现这样的功能。
  • 注意1:
    • 若定义时未初始化,则系统会将所有的静态数据自动初始化为0
    • 静态数据初始化语句,只会执行一遍。
    • 静态数据从程序开始运行时便已存在,直到程序退出时才释放。
  • 注意2:
    • static修饰局部变量:使之由栈内存临时数据,变成了静态数据(存储与数据段)。
    • static修饰全局变量:使之由各文件可见的静态数据,变成了本文件可见(缩小可见范围、降低同名冲突的概率)的静态数据。
    • static修饰函数:使之由各文件可见的函数,变成了本文件可见(缩小可见范围、降低同名冲突的概率)的静态函数。

数据段与代码段

  • 数据段细分成如下几个区域:
    • .bss 段:存放未初始化的静态数据,它们将被系统自动初始化为0
    • .data段:存放已初始化的静态数据
    • .rodata段:存放常量数据
  • 代码段细分成如下几个区域:
    • .text段:存放用户代码
    • .init段:存放系统初始化代码(编译器根据具体运行环境自动添加)

int a;       // 未初始化的全局变量,放置在.bss 中
int b = 100; // 已初始化的全局变量,放置在.data 中int main(void)
{static int c;       // 未初始化的静态局部变量,放置在.bss 中static int d = 200; // 已初始化的静态局部变量,放置在.data 中// 以上代码中的常量100、200防止在.rodata 中
}

注意:数据段和代码段内存的分配和释放,都是由系统规定的,我们无法干预。

堆内存

堆内存(heap)又被称为动态内存、自由内存,简称堆。堆是唯一可被开发者自定义的区段,开发者可以根据需要申请内存的大小、决定使用的时间长短等。但又由于这是一块系统“非地”,所有的细节均由开发者自己把握,系统不对此做任何干预,给予开发者绝对的“自由”,但也正因如此,对开发者的内存管理提出了很高的要求(内存泄漏)。对堆内存的合理使用,几乎是软件开发中的一个永恒的话题。

  • 堆内存基本特征:
    • 相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。
    • 相比栈内存,堆内存从下往上(低地址-》高地址)增长。
    • 堆内存是匿名的,只能由指针来访问。
    • 自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。

  • 相关API:
    • 申请堆内存:malloc() / calloc() / realloc() / reallocarray()
    • 清零堆内存:bzero() / memset()
    • 释放堆内存:free()

申请API接口分析:

 #include <stdlib.h>void * malloc  (size_t size);
参数:size --> 需要申请的堆内存的大小
返回值:成功:返回申请到的内存的入口地址失败:返回NULL 
注意:通过该函数申请得到的内存没有被初始化,因此他的值是未知。void *calloc  (size_t nmemb, size_t size);参数分析:nmemb --> 需要申请的内存的块数量size -->  需要申请的内存块的大小
注意:通过该函数申请得到的内存有被初始化,因此他的值都是0 。void *realloc (void *ptr,     size_t size);参数分析:ptr -- > 原本的内存的入口地址size --> 期望的新空间的大小返回值:成功 返回新的内存入口地址
注意:1. 该函数会重新设置内存区的大小新内存大于旧内存:新内存与旧内存入口地址一致:原地拓展,新的内存区不会被清空新内存与旧内存入口地址不一致:realloc 函数会把旧地址中的数据拷贝到新的区域中,旧的内存会被释放掉 新内存小于旧内存:只是把内存的大小进行调整,数据依然保持不变void *reallocarray(void *ptr, size_t nmemb, size_t size);

释放内存空间:

void free(void *ptr);参数分析:ptr --> 需要释放的内存的【入口】地址返回值:无
  • 注意:
    • malloc()申请的堆内存,默认情况下是随机值,一般需要用 bzero() 来清零。
    • calloc()申请的堆内存,默认情况下是已经清零了的,不需要再清零。
    • free()只能释放堆内存,并且只能释放整块堆内存,不能释放别的区段的内存或者释放一部分堆内存。
  • 释放内存的含义:
    • 释放内存意味着将内存的使用权归还给系统。
    • 释放内存并不会改变指针的指向(指针依然会指向这个被释放的内存,因此指针成为了野指针,需要手动让他指向NULL)。
    • 释放内存并不会对内存做任何修改,更不会将内存清零。

free()的本质是释放p所指向的内存,即:将p指向的内存的使用权归还给了系统。还给系统之后,系统不一定会立即使用它,因此在 free 之后再次使用这块内存,很有可能不会触发错误,这就像刚刚在酒店退了房,又偷偷返回房间,很可能不会酒店还没来得及将房间出租给其他人,因此不会发生问题,但这终归是一种错误。这样的代码是有严重安全隐患的

如何清理内存:

#include <strings.h>void bzero(void *s, size_t n);
参数分析:s -->  需要清零的内存地址n -->  需要清理的内存区大小(字节为单位)
返回值:无#include <string.h>void *memset(void *s, int c, size_t n);
参数:s --> 需要设置的内存c --> 需要设置的值n --> 需要设置的内存区大小
返回值:返回一个指向s的指针
注意:该函数会按【一个字节的单位】进行设置内存中的数据// 以下代码是往msg的内存中写入128个97
memset( msg , 97 , 128 );

拓展:

在Ubuntu的环境中如何检测代码中是否存在内存泄漏问题:

    • 安装内存检测工具 valgrind
sudo apt install valgrind
    • 如何使用: 使用valgrind 来间接运行我们自己的程序
valgrind ./a.out

这篇关于C语言09--进程的内存镜像的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1142118

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级

C语言 | Leetcode C语言题解之第393题UTF-8编码验证

题目: 题解: static const int MASK1 = 1 << 7;static const int MASK2 = (1 << 7) + (1 << 6);bool isValid(int num) {return (num & MASK2) == MASK1;}int getBytes(int num) {if ((num & MASK1) == 0) {return

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

C语言:柔性数组

数组定义 柔性数组 err int arr[0] = {0}; // ERROR 柔性数组 // 常见struct Test{int len;char arr[1024];} // 柔性数组struct Test{int len;char arr[0];}struct Test *t;t = malloc(sizeof(Test) + 11);strcpy(t->arr,

Java第二阶段---09类和对象---第三节 构造方法

第三节 构造方法 1.概念 构造方法是一种特殊的方法,主要用于创建对象以及完成对象的属性初始化操作。构造方法不能被对象调用。 2.语法 //[]中内容可有可无 访问修饰符 类名([参数列表]){ } 3.示例 public class Car {     //车特征(属性)     public String name;//车名   可以直接拿来用 说明它有初始值     pu

C语言指针入门 《C语言非常道》

C语言指针入门 《C语言非常道》 作为一个程序员,我接触 C 语言有十年了。有的朋友让我推荐 C 语言的参考书,我不敢乱推荐,尤其是国内作者写的书,往往七拼八凑,漏洞百出。 但是,李忠老师的《C语言非常道》值得一读。对了,李老师有个官网,网址是: 李忠老师官网 最棒的是,有配套的教学视频,可以试看。 试看点这里 接下来言归正传,讲解指针。以下内容很多都参考了李忠老师的《C语言非