踩内存问题

踩内存问题

踩内存

我们把访问不属于当前对象、当前进程的内存的行为,称为踩内存。

踩内存本身是未定义行为(除非是柔性数组),但由于操作系统的内存页面管理机制,未必会引发段错误(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
*/
posted @ 2023-09-30 21:52  3的4次方  阅读(100)  评论(0)    收藏  举报