文章目录
- 内存分区
- 堆(heap)和栈(stack)的区别
- new和malloc的区别
- delete和free有什么区别
- 野指针
- 导致野指针的原因
- 如何避免野指针
- 野指针和悬浮指针
- 内存对齐
- 什么是内存对其
- 为什么要内存对齐
- 内存对其的规则
内存分区
从高地址到低地址依次是:
- 栈(stack)区:由编译器自动释放、分配。存放局部变量、形参和返回值
- 堆(heap)区:由程序员分配内存和释放。调用
malloc()\new
和free()\delete()
- 全局(静态)区:这部分可以细分为data区、bss区、常量区
- .data区里主要存放的是已经初始化的全局变量、静态变量和常量
- .bss区主要存放的是未初始化的全局变量、静态变量,这些未初始化的数据在程序执行前会自动被系统初始化为0或者NULL
- .rodata常量区是全局区中划分的一个小区域,也被称为只读区.里面存放的是常量,如const修饰的全局变量、字符串常量等
- .text代码区:存放函数体的二进制代码,由操作系统进行管理的
堆(heap)和栈(stack)的区别
栈是⼀种有限的内存区域,⽤于存储局部变量、函数调⽤信息等。
堆是⼀种动态分配的内存区域,⽤于存储程序运⾏时动态分配的数据。
变量的生命周期:栈上的变量⽣命周期与其所在函数的执⾏周期相同,⽽堆上的变量⽣命周期由程序员显式控制,可以(使⽤ 或 malloc
)和释放(使⽤ delete
或 free
)。
内存分配和释放速度:栈上的内存分配和释放是⾃动的,速度较快。⽽堆上的内存分配和释放需要⼿动操作,速度相对较慢。
new和malloc的区别
类型安全:
new
是C++的运算符,可以为对象分配内存并调⽤相应的构造函数。malloc
是C语⾔库函数,只分配指定⼤⼩的内存块,不会调⽤构造函数。
返回类型:
new
返回的是具体类型的指针,⽽且不需要进⾏类型转换。malloc
返回的是void*
,需要进⾏类型转换,因为它不知道所分配内存的⽤途。
释放内存的方式:
new
的对象用delete
,其会调⽤对象的析构函数,然后释放内存。malloc
的对象用free
,其会调⽤对象的析构函数,然后释放内存
内存分配失败时的⾏为:
new
在内存分配失败时会抛出 std::bad_allocmalloc
在内存分配失败时返回 NULL 。
内存块⼤⼩:
new
可以⽤于动态分配数组,并知道数组⼤⼩。malloc
只是分配指定⼤⼩的内存块,不了解所分配内存块的具体⽤途。
delete和free有什么区别
类型安全性:
delete
会调用对象的析构函数,确保资源被正确释放free
不了解对象的构造和析构,只是简单得释放内存块
内存块释放后的行为:
delete
释放的内存块的指针指会被设置为nullptr
,以避免野指针free
不会修改指针的值,可能导致野指针问题
数组的释放:
delete
可以正确释放通过new[]
分配的数组free
不会修改数组的大小,不适用于释放通过malloc
分配的数组。
野指针
野指针(wild pointer)是指指向内存中未知位置的指针,即指针变量指向的地址没有被正确初始化或者指向的内存已经被释放,但指针变量仍然保留了这个地址。这样的指针通常会导致未定义行为,可能会导致程序崩溃、数据损坏或者其他不可预测的结果。
导致野指针的原因
未初始化的指针
当指针变量被声明但未被初始化时,其值是未知的,它可能指向任意内存地址。
int *ptr; // 未初始化的指针
已释放或者销毁的指针
当指针变量指向的内存已经被释放或销毁,但指针变量本身仍然保留了之前的地址。
int *ptr = new int; // 为指针分配内存
delete ptr; // 释放指针所指向的内存
//此时 ptr 成为野指针,因为它仍然指向已经被释放的内存
ptr = nullptr; // 避免野指针,应该将指针置为 nullptr 或赋予新的有效地址
返回局部变量的指针
int* createInt()
{int x = 10;return &x;
}
如何避免野指针
-
初始化指针:始终确保在使用指针之前将其初始化为一个有效的地址或者 nullptr。
-
及时置空指针:当指针所指向的内存不再需要时,及时将指针置为空指针(nullptr)。
int *ptr = new int; // 为指针分配内存
delete ptr; // 释放指针所指向的内存
//此时 ptr 成为野指针,因为它仍然指向已经被释放的内存
ptr = nullptr; // 避免野指针,应该将指针置为 nullptr 或赋予新的有效地址
- 避免返回局部变量的指针。
- 注意函数参数的⽣命周期, 避免在函数内释放调⽤⽅传递的指针
void foo(int*& ptr) { // 操作 ptr delete ptr; // 这⾥可能造成调⽤⽅的指针成为野指针 ptr = nullptr;
}
int main() { int* ptr = new int; foo(ptr);
}
- 使用智能指针:使用C++11引入的智能指针(如
std::unique_ptr
、std::shared_ptr
等),它们可以自动管理内存生命周期,避免了手动管理内存带来的潜在问题,从而减少了野指针的风险。
野指针和悬浮指针
区别在于,野指针可能指向任意未知或无效的内存地址,而悬空指针则是指向已经被释放或者超出作用域的对象的地址。悬空指针通常是由于对已释放对象的指针未及时置空而产生的,而野指针则可能是由于未初始化、错误释放或者错误使用指针而产生的。虽然两者都可能导致程序出错,但野指针的来源更加广泛,也更容易产生严重的后果。
以下为悬空指针的典型案例:
int *ptr;
{int value = 10;ptr = &value; // 指针指向局部变量value,当value超出作用域后,ptr就成为悬空指针
} // value超出作用域,指针ptr成为悬空指针
内存对齐
什么是内存对其
元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为4或8)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。
为什么要内存对齐
引用的一位B友(L__B_)的评论:
一般来说,64位机器对应8个字,也就是每次取八个字节,而且由于内存的分层级策略,直接和CPU打交道的是CPUcache,而一个cacheline一般是32或64个字节。完全不对齐的话,CPU读取一个数据的寻址次数将大大增加,甚至有些CPU如果你不照着8字节或者4字节对齐直接g,对齐的话能够保证更少的cache miss,加快CPU处理速度。
内存对其的规则
仍然来自B友(L__B_)的评论
64位系统对应8字节,为了减少存取次数,数据成员应该以8字节为单位存取,保证中途不会跨越式的拿到其他不完整的成员数据,比如int char int,从头到尾依次字节数为4 1 4,那么八个字节取出发现会拿到第三个成员的不完整数据,所以我们给第二个成员+padding到4字节所以总共这个结构体就是12字节。同样我们假设上述的结构体类型为t,那么有下列结构 int64_t int64_t t int64_t int64_t,8 8 12 8 8。以8字节遍历,发现在第四次遍历的时候拿到了其他成员的数据,所以给这12这个结构+padding到16,所以总共48字节。32位默认4字节也是同理。