C++学习笔记总结练习:C++内存分配

本文围绕C++的内存分配展开,介绍了C/C++编译程序所占用内存的五个部分,包括栈区、堆区等。还说明了栈和堆的内存分配与编译执行过程,如编译器生成栈空间申请和释放指令,用户实现堆空间操作代码由编译器翻译。此外,对编译时内存分配的典型问题给出解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

内存分配

内存分配示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ggcplZx-1692237195837)(image/2021-03-04-10-30-35.png)]

  • Unused Memory:用于程序代码块对齐
    Read-only code segment:只读,存代码和一些其他的东西
  • Read/Write data segment:
    • .data:存初始化的全局变量和static变量,另外还有文字常量区,常量字符串就是放在这里,程序结束后有系统释放
    • .bss:存未初始化的全局变量和static变量
  • Heap:通过new和malloc由低到高分配,由delete或free手动释放或者程序结束自动释放
  • Shared libraries:调用的库文件,位于堆和栈之间
  • Stack:由高向低增长,和堆的增长方式相对,对不同的OS来说,栈的初始大小有规定,可以修改,目前默认一般为2M,由编译器自动分配释放
  • Kernel virtual memory:用户不可见不能访问

内存分配说明

C/C++编译的程序所占用内存区域一般分为以下5个部分:

  • 栈区(stack):由编译器自动分配和释放,用来存放函数的参数、局部变量等。其操作方式类似于数据结构中的栈。
  • 堆区(heap):一般由程序员分配和释放(通过malloc/free、new/delete),若程序员没有释放,则程序结束时由操作系统回收。它与数据结构中的堆是两回事,分配方式类似于链表。
  • 全局/静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和初始化的静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由操作系统回收。
  • 文字常量区:存放常量值,如常量字符串等,不允许修改,程序结束后由操作系统回收。
  • 程序代码区:存放函数体的二进制代码。

例子

#include <stdlib.h>
#include <string.h>
int a = 0; // 全局初始化区
char* p1;  // 全局未初始化区
int main() {
    int a;            // 栈区
    char s[] = "abc"; // 栈区
    char* p2;         // 栈区
    char* p3 = "123456";    // 123456\0在常量区,p3在栈区
    static int c = 0;      // 全局/静态初始化区
    p1 = (char*) malloc(10);
    p2 = (char*) malloc(20); // 分配得来的10和20字节在堆区
    strcpy(p1, "123456"); // 123456\0放在常量区,编译器可能将它与p3所指向的"123456"优化成一个地方
    return 0;
}

2 关于栈和堆的内存分配与编译执行过程的再说明

说明

  1. “编译器申请和释放栈内存空间” 并不是说,在编译阶段就完成了内存空间的申请和释放,而是在编译阶段生成了内存申请和释放的命令。通过这些内存申请释放的机器指令,在执行阶段具体地完成内存的申请和释放。编译器负责生成这些命令,所以是编译器负责栈空间的管理。
  2. “栈”是一种数据结构,通过机器指令实现,而不是一种内存空间结构。通过生成的指令,操作具体的地址,移动地址的指针,实现栈的计算。
  3. 所以在编译阶段到底做了什么呢?当然是
    1. 编译器自动生成了栈空间的申请和释放的机器指令,将指令的对象翻译为相对地址,实现栈的逻辑结构
    2. 用户实现了堆空间申请和释放的代码,编译器负责翻译为机器指令。但是没有相对地址,只有相对地址的指针
  4. 在实行阶段,将程序装入内存,对相对地址进行重定位。执行过程中,完成栈和堆的空间分配操作。

关于一个典型问题的说明

#include<iostream>
using namespace std;

int main(){
    int n=1;
    cin>>n;

    int a=0;
    //编译器提示数组的长度必须是常量(即编译器能够推断出数组的地址)
    // 但是,编译器好像并没有报错而是采用了动态重定位的方式解决了运行时才能确定的数组的长度。
    // 反正用vector就不会出现这些问题了,因为他们的空间都是在堆上分配的。
    int b[n];
    int c=0;
    cout<<&a<<" "<<&b<<" "<<&c<<endl;
    cin>>c;
    return 0;
}
  • 问题描述:如果在编译时进行内存分配,那么n、a、c的相对地址应该都是确定的,但是a的长度只能在运行时确定,那么a和c在编译的时候相对地址到底是多少呢?
  • 解决方案:在《操作系统》中介绍,重定位主要有两种方式,静态重定位(装入的时候进行重定位)。动态重定位(执行过程中进行重定位)。使用动态重定位,在执行到int a[n]的时候,对c进行动态重定位,那么就可以解决问题了。这么说现在主流的操作系统、编译器都是动态重定位吗?大概是吧。
  • intelligence提示不能使用变量初始化数组的长度,因为编译器无法确定相对地址。但是实际编译过程中并不会报错,貌似是编译器采用了动态重定位技术,或者其他的方法。对于这种问题一般有两种解决方法:
    • 让编译器自己解决(虽然提示不可以,但是编译器动态重定位的话应该行)
    • 使用动态内存分配。malloc、new在堆上分配空间,就不会出问题。
    • 使用vector,本身就是在堆上分配空间。

相关章节

  • 编译原理
  • 操作系统—内存管理—程序的装入与链接—重定位
  • C+±–标准库—动态内存管理—new、delete
  • C+±–面试—C++编译过程详解、C++内存分配
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

onnx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值