【C/C++】理解 C/C++ 中的堆、栈、静态区和只读区(重传)

我很久以前写过大量的博文都被我删除了,找了一些有价值的重传一下

在 C/C++ 编程中,内存管理是一个重要的基础知识。但国内的大多数初学者是接触不到这些内容的,本文就简单讲解一下,程序编译运行后,代码都被划分成那些区域,再通过运行时程序控制运行的。


一、内存分区简介

C/C++ 程序的内存通常分为以下几个部分:

  1. 栈区(Stack)
    栈区用于存储函数调用时的临时变量(如局部变量、函数参数等)。它由操作系统自动管理,分配和释放内存,速度快,但空间有限。

  2. 堆区(Heap)
    堆区是程序运行中动态分配内存的区域,通常由程序员通过 mallocnew 分配内存,并需要用 freedelete 手动释放。

  3. 静态区(Static Segment)
    静态区存储程序中的全局变量、静态变量和常量。它们在程序运行期间分配一次,直到程序结束时才释放。

  4. 只读区(Read-Only Segment)
    只读区存放不可修改的常量数据(如字符串字面量)。尝试修改只读区的数据会导致程序崩溃。

  5. 代码区(Code Segment)
    代码区存储程序的可执行指令,通常是只读的,防止代码被意外修改。


二、栈区

1. 栈区的特点

  • 自动管理:栈区由操作系统自动分配和释放,程序员无需干预。
  • 存储内容:存储局部变量、函数参数、返回地址等。
  • 生命周期:变量在函数调用时分配,函数结束时自动销毁。
  • 效率高:栈区由于连续的内存分配和自动管理,访问速度非常快。

2. 栈区的示例代码

#include <iostream>

void exampleFunction() {
    int a = 10;  // 局部变量,存储在栈区
    int b = 20;  // 局部变量
    std::cout << "a + b = " << (a + b) << std::endl;
}

int main() {
    exampleFunction();
    return 0;
}

在上面的代码中,ab 是局部变量,存在栈区。当 exampleFunction 执行完毕后,ab 的内存会被自动释放。

3. 开发中的注意事项

  • 栈溢出(Stack Overflow):栈空间有限(通常为几 MB),如果递归深度过大或局部变量占用内存过多,可能会导致栈溢出。
  • 禁止返回栈内存地址:函数结束后,栈上的变量会被销毁,因此返回它们的地址是危险的。
int* riskyFunction() {
    int a = 10;
    return &a;  // 错误!返回已释放的栈内存地址
}

三、堆区

1. 堆区的特点

  • 手动管理:程序员需要通过 mallocnew 分配内存,并使用 freedelete 释放内存。
  • 存储内容:动态分配的内存,例如对象、数组等。
  • 生命周期:由程序员控制,手动释放前内存会一直占用。
  • 灵活性:堆内存可以在程序运行时动态调整大小。

2. 堆区的示例代码

#include <iostream>

int main() {
    int* p = new int(42);  // 在堆区分配一个整数,值为 42
    std::cout << "Value: " << *p << std::endl;
    delete p;  // 手动释放内存
    return 0;
}

3. 开发中的注意事项

  • 及时释放内存:忘记释放堆内存会导致内存泄漏,特别是在长时间运行的程序中。
  • 避免悬空指针:释放堆内存后,指针仍然指向已释放的地址。建议将指针置为 nullptr
int* p = new int(10);
delete p;
p = nullptr;  // 避免悬空指针

四、静态区

1. 静态区的特点

  • 全局变量和静态变量存储在静态区。
  • 生命周期长:变量从程序开始到程序结束始终存在。
  • 默认初始化:未显式初始化的全局变量和静态变量会被自动置为零值。

2. 静态区的示例代码

#include <iostream>

int globalVar = 100;  // 全局变量,存储在静态区

void staticExample() {
    static int staticVar = 10;  // 静态局部变量,存储在静态区
    staticVar++;
    std::cout << "staticVar = " << staticVar << std::endl;
}

int main() {
    staticExample();
    staticExample();  // 再次调用时,staticVar 保留上次的值
    return 0;
}

五、只读区

1. 只读区的特点

  • 存储不可修改的数据:包括字符串字面量和 const 修饰的全局变量。
  • 只读属性:尝试修改只读区的数据会导致运行时崩溃。

2. 示例代码

#include <iostream>

int main() {
    const char* str = "Hello, World!";  // 字符串字面量,存储在只读区
    // str[0] = 'h';  // 错误!尝试修改只读区内容
    std::cout << str << std::endl;
    return 0;
}

六、堆栈区与操作系统堆栈区的关系

1. 操作系统堆栈区是什么?

  • 操作系统为每个线程分配了一块栈内存,称为线程栈系统栈
  • 在 C/C++ 中,程序的栈区就位于操作系统分配的线程栈中。

2. 关系与区别

  • 线程栈是栈区的底层实现:C/C++ 的栈区实际上是操作系统分配的线程栈的一部分。
  • 操作受限:程序员无法直接操作操作系统的线程栈,栈区的内存分配完全由编译器和操作系统管理。

七、内存区域的对比表

特性 栈区 堆区 静态区 只读区
管理方式 系统自动分配和释放 程序员手动分配和释放 系统自动分配,程序结束释放 系统自动分配,程序结束释放
存储内容 局部变量、函数参数等 动态分配的对象和数组 全局变量、静态变量等 常量数据(如字符串字面量)
生命周期 函数调用期间 程序员控制 程序运行期间始终存在 程序运行期间始终存在
是否可修改 可修改 可修改 可修改 不可修改
速度 快速 相对较慢 快速 快速
空间大小 受限(一般为几 MB) 较大(由系统内存决定) 较大 较小

八、堆栈区与堆栈数据结构是否有关系

注意,堆栈区和堆栈实际上没有直接关系。但栈区的数据结构通常确实是栈,但堆区的数据结构一般都比较复杂,不同语言实现也都不太一样。

九、简单总结

  1. 栈区 是函数调用期间的临时存储区域,速度快但空间有限。
  2. 堆区 提供灵活的内存分配方式,但需要程序员手动管理。
  3. 静态区 用于存储全局变量和静态变量,这些变量贯穿程序的整个生命周期。
  4. 只读区 存放不可修改的常量数据,修改会导致错误。
  5. 栈区和操作系统堆栈区密切相关,栈区是线程栈的一部分,由操作系统和编译器协同管理。

理解这些内存区域的作用和特点,不仅能帮助你写出高效的代码,还能避免常见的 Bug,比如内存泄漏、栈溢出等问题。希望本文对你有所帮助!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容