【C++】五大内存分区

本文详细介绍一下 C++ 中常被提及的五大内存分区(或称为内存区域)。需要注意的是,这是一种逻辑上的划分,帮助理解程序运行时不同类型数据的存储方式和生命周期。实际的物理内存管理和具体的内存分段会因操作系统、编译器和体系结构而异,但这五大分区的概念对于 C++ 程序员来说是非常重要的。

这五大内存分区通常是:

  1. 栈区 (Stack)
  2. 堆区 (Heap)
  3. 全局/静态存储区 (Global/Static Storage Area)
  4. 常量存储区 (Constant Storage Area / Read-Only Data)
  5. 代码区 (Code Area / Text Segment)

1. 栈区 (Stack)

  • 存储内容: 主要存储函数的局部变量 (非 static 修饰)、函数参数返回地址函数调用的上下文信息 (如寄存器状态)。
  • 管理方式: 由编译器自动分配和释放。内存分配操作类似于数据结构中的栈,遵循“先进后出”(FILO)或“后进先出”(LIFO)的原则。当函数被调用时,其栈帧(stack frame)被压入栈顶;函数返回时,其栈帧被弹出。
  • 生命周期: 与其所在的作用域(通常是函数或代码块 {})绑定。进入作用域时分配,离开作用域时自动释放。
  • 特点:
    • 分配和释放速度非常快,仅涉及栈顶指针的移动。
    • 内存空间有限(通常为几 MB),如果分配过多(如深度递归、巨大的局部数组)会导致栈溢出 (Stack Overflow)
    • 内存是连续的。
  • 代码示例:
#include <iostream>

void stackFunction(int param) {
    int localVar = 20; // 存储在栈区
    // param (函数参数) 也存储在栈区
    std::cout << "Inside stackFunction:" << std::endl;
    std::cout << " - Parameter 'param' (address): " << &param << ", value: " << param << std::endl;
    std::cout << " - Local variable 'localVar' (address): " << &localVar << ", value: " << localVar << std::endl;
    // 当 stackFunction 返回时, param 和 localVar 占用的栈空间会被自动释放
}

int main() {
    int mainVar = 10; // 存储在 main 函数的栈帧中
    std::cout << "Inside main:" << std::endl;
    std::cout << " - Local variable 'mainVar' (address): " << &mainVar << ", value: " << mainVar << std::endl;
    stackFunction(mainVar); // 调用函数,参数传递,创建新的栈帧
    std::cout << "Back in main." << std::endl;
    // stackFunction 返回后,其栈帧被销毁
    // mainVar 仍然存在,直到 main 函数结束
    return 0; // main 函数结束,mainVar 被销毁
}

2. 堆区 (Heap)

  • 存储内容: 程序运行时动态分配的内存。主要通过 new (C++) 或 malloc © 进行分配。
  • 管理方式: 由程序员手动或通过智能指针管理。使用 new 分配的内存必须使用 delete 释放;使用 new[] 分配的数组必须使用 delete[] 释放。malloc 分配的内存用 free 释放。
  • 生命周期: 从程序员显式分配 (new/malloc) 开始,到程序员显式释放 (delete/free) 或程序结束为止。如果忘记释放,会导致内存泄漏 (Memory Leak)
  • 特点:
    • 分配和释放速度相对较慢(涉及查找合适的内存块、记录分配信息等)。
    • 内存空间较大(理论上受限于可用虚拟内存)。
    • 可能产生内存碎片(多次分配和释放后,可用内存被分割成不连续的小块)。
    • 需要程序员小心管理,容易出错(忘记释放、重复释放、野指针等)。
  • 代码示例:
#include <iostream>
#include <memory> // for smart pointers

void heapExample() {
    // 1. 使用 new / delete 手动管理
    int* heapInt = nullptr;
    try {
        heapInt = new int(100); // 在堆上分配一个 int,并初始化为 100
        if (heapInt) {
            std::cout << "Heap allocation (raw pointer):" << std::endl;
            std::cout << " - Address: " << heapInt << ", value: " << *heapInt << std::endl;
            *heapInt = 150; // 修改堆上的值
            std::cout << " - Modified value: " << *heapInt << std::endl;
            delete heapInt; // !!! 必须手动释放 !!!
            heapInt = nullptr; // 良好习惯:释放后置空指针
            std::cout << " - Memory released." << std::endl;
        }
    } catch (const std::bad_alloc& e) {
        std::cerr << "Memory allocation failed: " << e.what() << std::endl;
        // delete heapInt; // 不需要,因为 new 失败会抛异常,不会赋值
    }

    // 2. 使用 new[] / delete[] 手动管理数组
    int* heapArray = nullptr;
    try {
        heapArray = new int[5]; // 在堆上分配 5 个 int 的数组
        if (heapArray) {
            std::cout << "\nHeap allocation (raw array pointer):" << std::endl;
            std::cout << " - Array address: " << heapArray << std::endl;
            for (int i = 0; i < 5; ++i) {
                heapArray[i] = i * 10;
                std::cout << "   - heapArray[" << i << "] = " << heapArray[i] << std::endl;
            }
            delete[] heapArray; // !!! 必须使用 delete[] 释放数组 !!!
            heapArray = nullptr;
            std::cout << " - Array memory released." << std::endl;
        }
    } catch (const std::bad_alloc& e) {
         std::cerr << "Array allocation failed: " << e.what() << std::endl;
    }

    // 3. 使用智能指针 (推荐的现代 C++ 方式)
    std::cout << "\nHeap allocation (smart pointer):" << std::endl;
    try {
        std::unique_ptr<int> smartHeapInt = std::make_unique<int>(200);
        std::cout << " - Address (unique_ptr): " << smartHeapInt.get() << ", value: " << *smartHeapInt << std::endl;
        *smartHeapInt = 250;
        std::cout << " - Modified value: " << *smartHeapInt << std::endl;
        // 不需要手动 delete!unique_ptr 在离开作用域时会自动释放内存
    } catch (const std::bad_alloc& e) {
         std::cerr << "Smart pointer allocation failed: " << e.what() << std::endl;
    }
     std::cout << " - unique_ptr going out of scope, memory will be released automatically." << std::endl;
}

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

3. 全局/静态存储区 (Global/Static Storage Area)

  • 存储内容:
    • 全局变量 (Global Variables): 定义在任何函数之外的变量。
    • 静态变量 (Static Variables):
      • 使用 static 关键字修饰的全局变量(限制作用域在当前文件)。
      • 使用 static 关键字修饰的局部变量(生命周期延长至整个程序运行期,但作用域仍是局部的)。
      • 使用 static 关键字修饰的类成员变量(属于类本身,所有对象共享)。
  • 管理方式: 由编译器在编译时分配内存,程序启动时加载,程序结束时由操作系统回收。
  • 生命周期: 变量的生命周期贯穿整个程序的运行期间。内存main 函数执行前就已经分配好了(对于 C++ 对象,构造函数在 main 之前或首次使用时调用,取决于具体情况),直到程序结束才释放。
  • 特点:
    • 生命周期长。
    • 通常分为两个子区域:
      • 初始化数据区 (Data Segment): 存储已初始化的全局变量和静态变量。
      • 未初始化数据区 (BSS Segment - Block Started by Symbol): 存储未初始化或初始化为零的全局变量和静态变量。操作系统通常会在程序加载时将 BSS 段清零。
  • 代码示例:
#include <iostream>

int globalVar = 30; // 已初始化全局变量 (存储在 Data Segment)
int globalUninitVar; // 未初始化全局变量 (存储在 BSS Segment, 默认为 0)
static int staticGlobalVar = 40; // 静态全局变量 (Data Segment, 作用域限当前文件)
static int staticGlobalUninitVar; // 静态未初始化全局变量 (BSS Segment, 作用域限当前文件)

void staticLocalExample() {
    static int staticLocalVar = 50; // 静态局部变量 (首次调用时初始化, 存储在 Data/BSS)
                                    // 生命周期是整个程序运行期,但作用域是函数内部
    int normalLocalVar = 5; // 普通局部变量 (存储在栈上)

    staticLocalVar++;
    normalLocalVar++;

    std::cout << "Inside staticLocalExample:" << std::endl;
    std::cout << " - staticLocalVar (address): " << &staticLocalVar << ", value: " << staticLocalVar << std::endl;
    std::cout << " - normalLocalVar (address): " << &normalLocalVar << ", value: " << normalLocalVar << std::endl;
}

int main() {
    std::cout << "Global/Static Storage:" << std::endl;
    std::cout << " - globalVar (address): " << &globalVar << ", value: " << globalVar << std::endl;
    std::cout << " - globalUninitVar (address): " << &globalUninitVar << ", value: " << globalUninitVar << std::endl;
    std::cout << " - staticGlobalVar (address): " << &staticGlobalVar << ", value: " << staticGlobalVar << std::endl;
    std::cout << " - staticGlobalUninitVar (address): " << &staticGlobalUninitVar << ", value: " << staticGlobalUninitVar << std::endl;

    std::cout << "\nCalling staticLocalExample first time:" << std::endl;
    staticLocalExample();

    std::cout << "\nCalling staticLocalExample second time:" << std::endl;
    staticLocalExample(); // 注意 staticLocalVar 的值会保留

    globalVar = 35; // 可以修改全局变量
    std::cout << "\nAfter modifying globalVar: " << globalVar << std::endl;

    return 0; // 程序结束时,全局/静态区的内存被回收
}

4. 常量存储区 (Constant Storage Area / Read-Only Data)

  • 存储内容: 主要存储常量数据,特别是字符串字面量 (e.g., "Hello, World!")。有时也可能存储用 const 修饰的全局变量(取决于编译器优化,有些 const 全局变量可能直接嵌入代码或仍在 Data Segment,但其所在内存页可能被标记为只读)。
  • 管理方式: 由编译器分配,程序启动时加载,程序结束时由操作系统回收。
  • 生命周期: 贯穿整个程序的运行期间
  • 特点:
    • 存储在该区域的数据通常是只读的。试图修改这部分内存(例如,修改字符串字面量的内容)通常会导致运行时错误(如段错误 Segmentation Fault)。
    • 可以节省内存,例如多个地方使用相同的字符串字面量,可能只存储一份。
  • 代码示例:
#include <iostream>

const int globalConstInt = 60; // const 全局变量,可能在常量区,也可能在只读的 Data Segment
const char* stringLiteral = "Hello, Constant Area!"; // 字符串字面量存储在常量区,指针 stringLiteral 本身可能在全局/静态区或栈区

int main() {
    const char* anotherPtr = "Hello, Constant Area!"; // 指向同一个字符串字面量

    std::cout << "Constant Storage:" << std::endl;
    std::cout << " - globalConstInt (address might vary): " << &globalConstInt << ", value: " << globalConstInt << std::endl;
    std::cout << " - stringLiteral pointer address: " << &stringLiteral << std::endl;
    std::cout << " - String literal content address: " << (void*)stringLiteral << ", value: " << stringLiteral << std::endl;
    std::cout << " - anotherPtr pointer address: " << &anotherPtr << std::endl;
    std::cout << " - Another pointer to same literal address: " << (void*)anotherPtr << ", value: " << anotherPtr << std::endl;

    // 检查两个指针是否指向同一内存地址 (通常是的,编译器会优化)
    if ((void*)stringLiteral == (void*)anotherPtr) {
        std::cout << "\nBoth pointers point to the same memory location for the string literal." << std::endl;
    }

    // 尝试修改字符串字面量 (会导致运行时错误,取消注释会崩溃)
    // char* modifiablePtr = (char*)stringLiteral;
    // std::cout << "\nAttempting to modify string literal (will likely crash)..." << std::endl;
    // modifiablePtr[0] = 'J'; // !!! Undefined Behavior / Crash !!!

    // globalConstInt = 65; // 编译错误,因为 globalConstInt 是 const

    return 0;
}

5. 代码区 (Code Area / Text Segment)

  • 存储内容: 存储编译后的二进制机器指令(即可执行代码)。
  • 管理方式: 由编译器和链接器生成,程序启动时由操作系统加载到内存。
  • 生命周期: 贯穿整个程序的运行期间
  • 特点:
    • 通常是只读的,防止程序意外修改自身的指令。
    • 通常是可共享的,例如,如果同一个程序运行多个实例,操作系统可能让这些实例共享同一份代码区的内存。
    • 大小在编译时确定。
  • 代码示例:
    这个区域我们不能像操作变量那样直接操作。我们编写的函数(如 main, stackFunction, heapExample 等)编译后的机器码就存放在这里。我们可以通过函数指针来间接“指向”代码区中的地址。
#include <iostream>

void message() {
    std::cout << "This function's code resides in the Code Area." << std::endl;
}

int add(int a, int b) {
    // 这部分计算逻辑的机器码在代码区
    return a + b;
}

int main() {
    std::cout << "Code Area Example:" << std::endl;

    // 获取函数地址 (指向代码区的某个位置)
    void (*funcPtr)() = &message; // 函数指针 funcPtr 指向 message 函数的入口
    int (*addPtr)(int, int) = &add; // 函数指针 addPtr 指向 add 函数的入口

    // 打印函数指针的值(即函数在内存中的地址)
    // 注意:直接打印函数指针可能输出 1 或特殊格式,强制转为 void* 通常能看到地址
    std::cout << " - Address of function 'message': " << (void*)funcPtr << std::endl;
    std::cout << " - Address of function 'add': " << (void*)addPtr << std::endl;
    std::cout << " - Address of function 'main': " << (void*)main << std::endl;

    // 通过函数指针调用函数 (执行代码区中的指令)
    std::cout << "\nCalling function via pointer:" << std::endl;
    funcPtr();

    int sum = addPtr(5, 3);
    std::cout << "Result of add(5, 3) via pointer: " << sum << std::endl;

    // 代码区是只读的,不能修改
    // char* codeBytes = (char*)funcPtr;
    // codeBytes[0] = 0xCC; // !!! 尝试修改代码区,极可能导致崩溃 !!!

    return 0;
}

总结

内存区域存储内容管理方式生命周期特点
栈区 (Stack)局部变量, 函数参数, 返回地址编译器自动管理函数调用/作用域速度快, 空间有限, 可能栈溢出
堆区 (Heap)动态分配的内存 (new, malloc)程序员手动/智能指针管理从分配到释放速度慢, 空间大, 可能内存泄漏/碎片
全局/静态存储区全局变量, 静态变量编译器分配, OS 回收整个程序运行期生命周期长, 分为 Data 和 BSS 段
常量存储区字符串字面量, const 全局变量 (有时)编译器分配, OS 回收整个程序运行期只读, 共享
代码区 (Code Area)编译后的机器指令 (函数体)编译器链接器生成, OS 加载整个程序运行期只读, 可执行, 可共享

理解这五大内存分区有助于编写更高效、更健壮的 C++ 代码,尤其是在处理内存管理、变量生命周期和性能优化时。

### 关于ArcGIS License Server无法启动的解决方案 当遇到ArcGIS License Server无法启动的情况,可以从以下几个方面排查并解决问题: #### 1. **检查网络配置** 确保License Server所在的计算机能够被其他客户端正常访问。如果是在局域网环境中部署了ArcGIS Server Local,则需要确认该环境下的网络设置是否允许远程连接AO组件[^1]。 #### 2. **验证服务状态** 检查ArcGIS Server Object Manager (SOM) 的运行情况。通常情况下,在Host SOM机器上需将此服务更改为由本地系统账户登录,并重启相关服务来恢复其正常工作流程[^2]。 #### 3. **审查日志文件** 查看ArcGIS License Manager的日志记录,寻找任何可能指示错误原因的信息。这些日志可以帮助识别具体是什么阻止了许可服务器的成功初始化。 #### 4. **权限问题** 确认用于启动ArcGIS License Server的服务账号具有足够的权限执行所需操作。这包括但不限于读取/写入特定目录的权利以及与其他必要进程通信的能力。 #### 5. **软件版本兼容性** 保证所使用的ArcGIS产品及其依赖项之间存在良好的版本匹配度。不一致可能会导致意外行为完全失败激活license server的功能。 #### 示例代码片段:修改服务登录身份 以下是更改Windows服务登录凭据的一个简单PowerShell脚本例子: ```powershell $serviceName = "ArcGISServerObjectManager" $newUsername = ".\LocalSystemUser" # 替换为实际用户名 $newPassword = ConvertTo-SecureString "" -AsPlainText -Force Set-Service -Name $serviceName -StartupType Automatic New-ServiceCredential -ServiceName $serviceName -Account $newUsername -Password $newPassword Restart-Service -Name $serviceName ``` 上述脚本仅作为示范用途,请依据实际情况调整参数值后再实施。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值