C++学习笔记:5.1 堆与栈

堆与栈

进程地址空间如下:
在这里插入图片描述

数据段和BSS段

int data[] = {1,2}; // 数据段
int big_data[10000] = {};// BSS

int main(){
    int A[] = {1,2,3}; // 栈空间
}

注意数据段/BSS段比栈空间大,但是更慢

栈和堆内存一览:

内存组织连续的(先进后出)一次分配内的内存连续,分配间分段
最大大小很小整个系统的内存
如果超出程序会崩溃异常或空指针
分配编译时运行时
局部性
线程视角每个线程都有自己的栈线程间共享

栈内存:

一个局部变量要么在栈内存要么在cpu寄存器中

int x = 3; // 不在栈上,在数据段

struct A {
    int k; // 取决于A的实例在哪
};
int main(){
    int y = 3; // 栈
    char z[] = "abc"; // 栈
    A a; // 栈
    void* ptr = malloc(4); // 变量“ptr”在栈上
}

栈内存的组织可以获得更高的性能,但是它也是有限的。

存储在栈上的数据类型:

  • 局部变量:局部作用域的变量
  • 函数参数:从调用者传入函数的数据
  • 返回地址:从函数传入调用者的数据
  • 编译器临时变量:编译器特定的指令
  • 中断上下文

每个存在栈上的对象在作用域外不再有效,例如

int* f(){
    int array[3] = {1,2,3};
    return array;
}
int* ptr = f();
cout<<ptr[0]; // 非法

void g(bool x){
    const char* str = "abc";
    if(x){
        char xyz[] = "xyz";
        str = xyz;
    }
    cout << str; // 如果x为真,为非法访问
}

堆内存:

new/new[] 和 delete/delete[]是c++关键字,可以执行动态内存分配以及释放,并且运行时对象创建和析构。而malloc和free是c函数,并且它们只能分配和释放内存块。

new和delete的优点:

  • 使用语言关键字,更加安全
  • 返回类型:new会返回精确的数据类型,而malloc只会返回void*
  • 失败:new会抛出异常,而malloc会返回一个NULL指针
  • 分配的size:使用new关键字,编译器会计算所需要的字节数;而malloc需要手动计算
  • 初始化:new可以在分配时进行初始化
  • 多态:含有虚函数的对象必须使用new分配来初始化虚表指针

一些例子如下:

// 分配单个元素
int* value = new int;

// 分配N个元素
int* array = new int[N];
// 分配N个结构
MyStruct* array = new MyStruct[N];
//分配并且0初始化
int* array = new int[N]();
// 释放单个元素
int* value = new int;
delete value;
// 释放N个元素
int* value = new int[N];
delete[] value;

注意一些基本规则:

  • 每个通过malloc分配的对象必须通过free释放
  • 每个通过new分配的对象必须通过delete释放
  • 每个通过new[]分配的对象必须通过delete[]释放
  • malloc,new,new[]在成功例子不会返回空指针,除非0-size的分配
  • free,delete和delete[]应在在空指针上不会产生错误

混合使用new,new[],malloc可能会出现UB

2维内存分配:

// 在栈上很简单,编译期知道维度
int A[3][4];

// 运行时才知道维度,old version
int** A = new int*[3];
for(int i = 0;i<3;i++)
    A[i] = new int[4];

for(int i = 0;i<3;i++)
    delete[] A[i];
delete[] A;

// C++ 11
auto A = new int[3][4];
int n = 3;
auto B = new int[n][4];
// auto C = new int[n][n]; // error
delete[] A;

非分配放置(non-allocating placement)

(ptr) type允许显式地确定对象的位置

// 栈内存
char buffer[8];
int* x = new (buffer) int;
short* y =new (x+1) short[2];
// 不需要释放x和y

// 堆内存
unsigned* buffer2 = new unsigned[2];
double* z = new (buffer2) double;
delete[] buffer2; // ok
// delete[] z; // ok,but bad

注意非平凡对象的放置分配需要在运行时显式调用对象的析构函数,因为不能检测到何时对象超出作用域。

struct A {
    ~A() {cout<< "des";}
};
char buffer[10];
auto x = new (buffer) A();
// delete x; // error
x->~A();

非抛出分配(non-throwing allocation)

new操作允许非抛出分配,通过传入std::nothrow对象。它返回NULL指针而不是抛出std::bad_alloc异常,如果内存分配失败了

int* array = new (std::nothrow) int[very_large_size];

std::nothrow不意味着分配内存的对象不能自己抛出异常

struct A {
    A() {throw std::runtime_error{};}
};
A* array = new (std::nothrow) A; // throw std::runtime_error{};

内存泄漏

程序不再使用的堆内存的实体却仍然占据着内存空间

问题:

  • 非法的内存访问 -> 段错误/错误的结果
  • 额外的内存消耗
int main(){
    int* array = new int[10];
    array = nullptr; // 内存泄漏
}

程序请求os分配的内存,os分配的内存是以虚拟页为粒度的,例如linux上为4KB。这意味着越界访问并不总是导致段错误,最坏的情况是带有UB的执行过程。

int* x = new int;
int num_iters = 4096 / sizeof(int);
for(int i = 0;i<num_iters;i++)
    x[i] = 1;// ok
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值