- (꒪ꇴ꒪(꒪ꇴ꒪ ),hello我是祐言
- 博客主页:C语言基础,Linux基础,软件配置领域博主🌍
- 快上🚘,一起学习!
- 送给读者的一句鸡汤🤔:
- 集中起来的意志可以击穿顽石!
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
学习内存布局之前我们先例啊了解一下内存是什么?
内存(Memory)是计算机系统中用于存储和读取数据的物理设备。它是计算机的重要组成部分,用于存储程序代码、数据和临时计算结果。
在计算机中,内存被划分为一系列的存储单元,每个存储单元都有一个唯一地址。每个存储单元可以存储固定大小的数据,例如字节或字(根据计算机的体系结构而定)。这些存储单元按照地址顺序排列,形成了连续的内存空间。
内存的主要作用是提供临时存储和快速访问数据的能力,它具有以下特点:
(1)随机访问:内存中的任何存储单元都可以通过其唯一的地址直接访问,而不需要按顺序逐个访问。
(2)快速读写:相比于其他存储介质(如硬盘或固态硬盘),内存的读写速度非常快,能够满足计算机系统对实时数据处理的需求。
(3)临时存储:内存通常用于存储程序的执行代码、运行时数据和临时计算结果。它提供了临时存储的能力,数据在计算完成后可以被清除或替换。
(4)容量有限:相对于硬盘等外部存储介质,内存的容量通常较小。这意味着需要合理管理内存资源,避免占用过多的内存导致系统性能下降。
计算机中的内存被分为多个层次,从高速缓存(Cache)到主存(RAM),再到辅助存储器(如硬盘)。不同层次的内存具有不同的访问速度和容量特点,用于满足不同的存储需求。
在编程中,我们可以使用编程语言提供的内存管理机制来分配和释放内存。例如,动态内存分配函数(如malloc和free)可以在运行时动态地分配和释放内存空间。学习内存和理解内存的概念和管理方式对于编写高效的程序非常重要,主要体现在:合理利用内存资源,避免内存泄漏和内存溢出等问题。
一、内存布局
任何一个程序,正常运行都需要内存资源,用来存放诸如变量、常量、函数代码等。这些不同的内容,所存储的内存区域是不同的,且不同的区域有不同的特性。因比我们需要研究财经处内存布局,逐个了解不同内存区域的特性。
1.虚拟内存
每个C语言进程都拥有一片结构相同的虚拟内存,所谓的虚拟内存(VM),就是从实际物理内存(PM)中映射出来的地址规范范围,最重要的特征是所有的虚拟内存布局都是相同的,极大地方便内核管理不同的进程。例如三个完全不相干的进程p1、p2、p3,它们很显然会占据不同区段的物理内存,但经过系统的变换和映射,它们的虚拟内存的布局是完全一样的。
将其中一个C语言进程的虚拟内存放大来看,会发现其内部包下区域:
- 代码段(Text Segment):用于存放程序的机器指令,也称为可执行代码。
- 数据段(Data Segment):用于存放已初始化的全局变量和静态变量。
- BSS段(Block Started by Symbol):用于存放未初始化的全局变量和静态变量,通常初始化为零。
- 堆(Heap):用于动态分配内存,主要用于存放程序运行时动态创建的数据结构。
- 栈(Stack):用于存放函数的局部变量、函数的参数、函数调用的上下文等,由系统自动分配和管理。
内存布局的设计旨在使不同类型的数据在内存中有序地存储,并提供有效的内存管理机制。不同的编程语言和操作系统可能具有不同的内存布局方式,但通常遵循类似的原则和结构。
栈内存
堆内存
数据段与代码段
2.内存泄露与内存溢出
(1)内存泄露(Memory Leak)
内存泄漏指的是在程序中动态分配的内存没有被正确释放,导致这部分内存无法再被程序使用,从而造成内存资源的浪费。如果内存泄漏问题在程序中反复发生,最终可能会导致程序耗尽可用内存而崩溃。
以下是一个内存泄漏的示例代码:
void memoryLeak() {
int *ptr = malloc(sizeof(int)); // 分配内存
// 没有释放内存,导致内存泄漏
}
在上述代码中,通过 malloc
函数分配了一块动态内存,但在函数结束时没有调用 free
函数释放内存。这导致每次调用 memoryLeak
函数时都会发生内存泄漏。
(2)内存溢出(Memory Overflow)
内存溢出指的是程序试图访问超出其分配内存范围的内存区域。这可能导致程序崩溃、数据损坏或安全漏洞。
以下是一个内存溢出的示例代码:
void memoryOverflow() {
int array[5]; // 声明一个包含 5 个整数的数组
array[5] = 10; // 超出数组边界,访问无效的内存
}
在上述代码中,声明了一个包含 5 个整数的数组 array
,但在访问第 6 个元素时超出了数组的边界。这将导致访问无效的内存位置,可能引发未定义行为。
因此,为了避免内存泄漏和内存溢出问题,应该注意以下几点:
- 在分配内存后,确保在不再使用时及时释放内存,使用
free
函数。 - 注意数组和指针的边界,避免超出其范围访问内存。
- 避免无限循环或递归,导致堆栈溢出。
- 使用内存分配和释放的最佳实践,遵循动态内存管理规则。
二、作用域
作用域指的是变量或标识符在程序中的可见性和可访问性范围。作用域规定了在程序的哪些部分可以引用变量或标识符。常见的作用域包括以下几种:
1.全局作用域
全局作用域中定义的变量可以在整个程序中访问,包括不同的函数和文件。在程序开始时创建,在程序结束时销毁。
#include <stdio.h>
// 未初始化全局变量
// 全局变量就是在花括号之外
// 声明周期是整个程序
// 作用域范围:整个程序
int a; //.bss
int b[100];
int c; //声明
// 未初始化,编译的时候不会分配内存
// 初始化,编译时会分配内存
int big_array[10000] = {324, 235, 36};
int main(void)
{
return 0;
}
2.局部作用域
局部作用域中定义的变量只能在所在的代码块(如函数、循环、条件语句等)中访问。在进入代码块时创建,在离开代码块时销毁。
#include <stdio.h>
int main(int argc, char *argv[])
{
// 在花括号之内定义的变量,就是局部变量
// 作用域就在这对花括号之间
// 生存周期,从定义开始,到右花括号结束
int a = 100;
// 局部变量没有初始化,就是随机值
int c[100];
for(int i=0; i<100; i++)
{
printf("%d\n", c[i]);
}
int *pa = &a; //pa也是局部变量(指针变量)
{
int b = 200;
printf("%d\n", b);
}
return 0;
}
3.函数参数作用域
函数参数的作用域仅限于函数内部。参数在函数调用时被创建,在函数执行完毕后被销毁。
作用域规定了变量的可见范围和生命周期。不同作用域的变量可以具有相同的名称,但它们是相互独立的,并且在不同的作用域中可以引用不同的内存位置。
#include <stdio.h>
#include <stdlib.h>
int g_a; //.bss 生存周期是整个程序
int g_b = 123; // .data 生存周期,整个程序
void f(void)
{
static int n = 1;
}
int main(int argc, char *argv[])
{
// static修饰的局部变量,生存周期为整个程序
// 默认只初始化一次
static int c = 45; // .data
// 堆内存的声明周期,从分配开始,到用自定义释放
int *p = malloc(12); // 申请堆内存
int a; // 局部变量,到当前的范围结束,就释放
return 0;
}
4.static关键字
static 关键字,具有多种用法和含义。它可以用于修饰变量、函数和成员,具体含义取决于上下文。主要作用是限制变量、函数或成员的作用域、链接属性和生命周期。
#include <stdio.h>
// 函数声明
void function();
// 全局变量
static int globalVariable = 10;
int main() {
// 局部变量
static int localVariable = 20;
printf("Global variable: %d\n", globalVariable);
printf("Local variable: %d\n", localVariable);
function();
return 0;
}
// 函数定义
void function() {
// 局部静态变量
static int staticVariable = 30;
printf("Static variable in function: %d\n", staticVariable);
}
📢写在最后
- 今天的分享就到这啦~
- 觉得博主写的还不错的烦劳
一键三连喔
~ - 🎉感谢关注