踩内存问题
踩内存问题
踩内存
我们把访问不属于当前对象、当前进程的内存的行为,称为踩内存。
踩内存本身是未定义行为(除非是柔性数组),但由于操作系统的内存页面管理机制,未必会引发段错误(Segmentation Fault)或内存访问错误(Access Violation):如果你强行访问该块内存,假设足够“幸运”——当前该内存的地址范围仍在当前映射给当前进程的物理页中,则不会发生段错误,只不过你会读到一个脏的内存值。而如果这块内存所在物理页没有被映射给当前进程,则访存就触发缺页异常,MMU地址转换后发现该地址不属于当前进程的地址空间,触发段错误。
场景
访问不属于当前对象的内存
这种场景发生在栈上紧挨着的两个对象。前一个对象访问到了后一个对象的内存。这种做法只有柔性数组是语言标准支持(且只有C支持,C++不支持,不过LLVM给出了类似的实现)的,其他做法都是未定义行为。
#include <stdio.h>
int main() {
const char * x = "he";
char y[10] = {'1', '2', '3', '4'};
printf("%c%c%c%c\n", x[0], x[1], x[2], x[3]); // 输出he1。显然'1'是一个脏数据
return 0;
}
访问悬垂指针(delete后指针)所指内存
在指针被delete之后,此时指针被称为空悬指针或者悬垂指针,即指向一块曾经保存数据对象,但现在已经无效的内存的指针。
当我们delete一个指针后,指针所指向的堆地址空间便被释放,指针值变成无效,此时该地址不该访问了,否则是未定义行为。同时,该块内存会归还给内存分配器(如glibc的 ptmalloc ,而后内存分配器决定何时将其归还操作系统)。
但是虽然该指针已经无效,但该指针仍然保存着已经被释放了的动态内存地址。
因此,正确的做法是在使用delete了一个指针之后,及时将该指针置为nullptr。
#include <iostream>
class X {
public:
X() { std::cout << "X构造函数" << std::endl; }
~X() { std::cout << "X析构函数" << std::endl; }
void func() {
std::cout << "X的func" << std::endl;
}
};
int main() {
X* obj = new X();
std::cout << obj << std::endl;
obj->func();
delete obj;
obj->func(); // func() 是一个非虚成员函数。调用非虚成员函数时,编译器只需要知道对象的地址(obj的值)来作为this指针传递,它不会检查内存是否有效。由于func()的实现没有访问任何对象的成员变量,所以这次调用侥幸成功了。
std::cout << obj << std::endl;
return 0;
}
/*
X构造函数
0000029EFCD0B2D0
X的func
X析构函数
X的func
0000029EFCD0B2D0
*/

浙公网安备 33010602011771号