四区的理解

注:如果读者学习过c++代码,编者推荐直接通过目录找到最后一个“利用c++代码对四区和代码编写运行过程进行梳理”进行阅读

代码编写整体过程的梳理

  1. 程序员先写代码,在未进行编译时,这些代码最初是以人类可读的文本形式存储在文件系统中的,通常是保存在源代码文件中,如.c、.cpp、.java等文件。这些源代码文件包含了程序员编写的程序代码、注释、宏定义等内容。
  2. 在进行编译时生成obj文件时,生成exe文件前,编译器介入,将源代码转化为二进制的类型,此时操作系统会划分出代码区和全局区的目标文件阶段(并不是真正的全局区)来来存储这些转化的二进制数据,但是这时全局区虽然不存在,但编译器会处理源代码中的全局变量和静态变量,为它们分配相应的空间(在obj文件的某个部分,这刻于看作全局区的前身,或是说全局区在目标文件的表示)链接器会将多个obj文件(以及可能的其他库文件)链接成一个可执行文件,在这个过程中才会确定全局变量和静态变量在内存中的实际位置(即全局区的具体位置)。
  3. 在exe文件执行时,栈区和堆区出现了
  4. exe文件结束后,一些数据内存会被释放,一些内存因为需要持久保存,程序员通过编程逻辑来命令系统创建一些文件,这些数据就会被写入到文件中(可以是文本文件、二进制文件、数据库文件等,具体取决于程序的逻辑和设计)

有关四区的理解

1.在生成exe文件前,只有代码区(全局区在物理内存中确实不存在)
代码区:储存二进制代码,只读特点
包括有函数体的二进制代码,源代码
存储所有可执行指令(函数编译后的机器码)
temp:C++特性关联:虚函数、模板、内联函数等高级特性均依赖代码区的统一管理

2.在生成obj文件时,编译器会处理全局变量和静态变量,将他们存储在obj文件的某个部分,是全局区的前身
生成exe文件时,全局区才确定
全局区:存储静态变量和全局变量,其生命周期和程序一样

3.在exe文件执行时,才会有栈区和堆区。
栈区的内存生成释放管理是由编译器来完成的
栈区:存储局部变量,函数参数,返回地址(是函数声明后,下一条紧接着要执行指令的的地址),其他临时数据。
特点是后进先出。

4.堆区是由程序员在程序执行时动态分配的,需要程序员手动释放。
堆区:存储动态分配对象,大型数据结构(链表,树,图),需要长期保存的数据(长时间保存,甚至跨越多个函数调用,适合在堆区分配内存)
内存分配和释放没有固定的顺序


利用c++代码对四区和代码编写运行过程进行梳理

下面选取c++编写的代码进行形象化讨论:

//=== 预处理指令(代码区) ===
#include    // 代码区:预处理器展开后的库代码
#define MAX_VALUE 100 // 代码区:宏定义

//=== 全局变量(全局区) ===
int globalVar = 42;   // 全局区:程序生命周期内存在

//=== 类定义(代码区) ===
class Base {          // 代码区:类定义(虚函数表指针在此类结构中)
public:
    virtual void show() { // 代码区:虚函数表指针指向此函数
        std::cout << "Base::show()" << std::endl;
    }
    static int s_count; // 全局区:静态成员变量(声明)
    int m_data;         // 非静态成员变量(随对象存在堆/栈)
};

//=== 静态成员初始化(全局区) ===
int Base::s_count = 0; // 全局区:静态成员实际存储位置

//=== 菱形继承结构 ===
class A : virtual public Base {  // 虚继承(解决菱形继承问题)
public:
    void show() override { 
        std::cout << "A::show()" << std::endl; 
    }
};

class B : virtual public Base {  // 虚继承
public:
    void show() override { 
        std::cout << "B::show()" << std::endl; 
    }
};

class Derived : public A, public B { // 菱形继承
public:
    void show() override { 
        std::cout << "Derived::show()" << std::endl; 
    }
};

//=== 函数定义(代码区) ===
int main() {
    //=== 栈区对象 ===
    Base stackObj;           // 栈区:对象内存(含虚表指针)
    stackObj.m_data = 10;     // 栈区:非静态成员数据

    //=== 堆区对象 ===
    Base* heapObj = new Derived(); // 堆区:对象内存(含虚表指针)
    heapObj->show();          // 通过虚表指针找到代码区函数

    //=== 静态成员访问 ===
    Base::s_count++;         // 全局区:静态成员变量

    //=== 动态内存操作 ===
    int* dynamicArray = new int[MAX_VALUE]; // 堆区:动态分配的内存
    delete[] dynamicArray;
    
    delete heapObj;
    return 0;
}

下面我将进行对从敲代码到代码运行整个历程进行说明:(这里先说明一下根据操作系统的内存管理这四个区域是相互独立的

  1. 我们在编译器中敲写代码,整个代码包括注释,宏定义等等)是将会被保存在源文件中的(.cpp和.h等等)。
  2. 接着只有当程序员进行了编译相关的指令(不进行编译指令的代码文件只是将代码持久化存储到磁盘,而代码的翻译需要程序员主动触发编译命令才会开始)。
  3. 首先是预处理头文件和宏定义(例如上述代码中的#include 和 #define)会被替换为可读的代码文本集成为一个.i文件,在默认情况下是和其他的源文件并列在一个目录中的。
  4. 接下来就是进行编译了,编译器将处理后的代码翻译成汇编代码.s文件
  5. 然后就是汇编代码转化为二进制目标文件.obj文件),注意这时操作系统会划分出代码区全局区的目标文件阶段可以看作是全局区的前身),这时全局区虽然不存在但是仍会为静态变量(上述的static int s_count等全局变量(int globalVar = 42分配相应的空间,而代码区确实是存在的,大部分的预处理命令(上述的#include 和 #define类的定义(上述的class base虚基表(在使用虚继承来解决菱形继承时)和虚函数表(在使用虚函数时会产生虚函数表,在基类的结构中,其中的虚函数指针指向基类中的虚函数,**函数体代码(只要不是其他四区的内容都属于函数体代码,例如cout等等)**都是在代码区中存储的。
  6. 紧接着是.exe文件执行时,栈和堆区出现了栈区存储对象(上述Base stackObj),指针(包括虚函数表指针)等堆区存储由程序员动态分配的所有上述Base * heapObj = new Derived(),注:实例对象是会包括虚函数表指针的,该指针也在堆区内。

你可能感兴趣的:(c++,学习)