首页 文章

什么会导致C中的分段错误? [关闭]

提问于
浏览
41

我注意到C中的分段错误的常见原因列表没有问题,所以我想我会添加它 .

当然,它没有一个正确的答案 .

我认为这可能对学习C的新程序员有用,如果你不同意,可以随意关闭它 .

9 回答

  • 5

    仅当您的操作系统具有MMU(Memory Management Unit)时,才会因访问内存不良而导致分段错误 . 否则,你不会得到它,但只有奇怪的行为 .

    虚拟内存(您可以访问的整个内存= 2^(sizeof(pointer_type)*8) (即: 2^num_bits_in_pointer_type ))以命名页面或段为单位映射到物理内存(分页取代分段但仍然使用它们) .

    每个页面都有一些保护权限,如果您尝试从没有读取权限的页面读取,您将获得段错误 . 如果您尝试写入只读位置,您将获得SIGSEGV .

    如果你有一个单一化的指针并使用它,它可能会发生它将指向另一个好的位置,所以你不会得到段错误 . 如果在绑定之后有一个小数组读取可能会破坏其他内存区域,如果它没有超过页面边界 .

    此外,由于页面很多,并非所有页面都被映射 . 如果您触摸非映射页面,则会出现段错误 . 实际上,对非映射页面的任何访问都必须考虑写入时的复制,交换页面,延迟加载,内存映射文件和其他内容 . 见this article on page fault handling,特别是那里的第二张图,也在下面发布(但阅读文章了解更多解释)

    page fault handling

    您主要对用户空间和导致SIGSEGV的所有路径中发生的事情感兴趣 . 但内核空间也很有趣 .

  • 0

    取消引用NULL指针 .

    #include <cstddef> //For NULL.
    int* p1 = NULL; //p1 points to no memory address
    *p1 = 3; //Segfault.
    
  • 0

    访问数组越界(可能):

    int ia[10];
    ia[10] = 4; // Someone forgot that arrays are 0-indexed! Possible Segfault.
    
  • 0

    事实上,很多方法都不一定会发生,事实上,这里发布的大多数例子就是这种情况 . 如果您可以在没有发生段错误的情况下执行这些操作,那只是好运(或运气不好,取决于您如何看待它!) .

    这实际上是C中将其与其他语言分开的事情之一;未定义的行为 . 而在Java或C#中,您可能会遇到'InvalidOperationException'或类似情况,这可以保证在执行这些操作时发生;在C中,标准只是说'未定义的行为',这基本上是抽奖的运气,你永远不希望这种情况发生 .

  • 37

    我最喜欢的:

    #include <iostream>
    struct A {
        virtual void f() {
            std::cout << "A::f();\n";
        }
        int i;
    };
    
    struct B : A {
        virtual void f() {
            std::cout << "B::f();\n";
        }
        int j;
    };
    
    void seti(A* arr, size_t size) {
        for (size_t i = 0; i < size; ++i)
            arr[i].i = 0;
    }
    
    int main() {
        B b[10];
        seti(b, 10);
        b[3].f();
    }
    

    与大多数可能导致段错误的事情一样,这也可能失败 . 例如,在ideone上, b[3].f() 失败,但 b[2].f() 有效 .

  • 3

    显而易见的答案是“未定义的行为”,但这对于没有经验的程序员来说是个问题,而某些类型的未定义行为导致分段错误(或其他类型的崩溃)的可能性要小于其他类型 . 分段错误的最常见原因通常是指针相关:取消引用未初始化的指针,空指针或先前释放的指针;访问超出对象(数组或其他)的结尾(或在开头之前,但不那么频繁);使用非法指针强制转换的结果( static_cast 到派生类型,当对象实际上没有该类型时,或大多数 reinterpret_cast );等等

    然而,在这里要记住的最重要的一点可能是,一般来说,这些不能保证会导致分段错误,并且通常,它们引起的分段错误只会在一个完全不相关的操作中稍后发生 . 因此,超出本地数组末尾的写入通常会“起作用”,但会修改在堆栈上跟随数组发生的任何事情:某些其他局部变量(修改堆栈上对象的 vptr 可能会导致分段错误你试图调用对象上的虚函数),调用函数的帧指针(可能会在你返回后导致该函数出现分段错误),或者返回地址(这可能会引起各种奇怪的行为 - 分段错误或非法指令陷阱可能是最好的 . 写入超出释放内存的末尾,或者通过已经释放的指针,可以破坏自由空间竞技场,导致分段错误在很多(有时很多)以后的分配或自由;它还可以修改一些其他完全不相关的对象,破坏它的 vptr 或其他一些对象中的指针,或只是一些随机数据 - 再次,分段错误可能是最好的结果(更喜欢继续损坏的数据) .

  • 4

    忘记初始化指针,留下随机内存地址 . 注意:这可能不是 always segfault,但它可以 .

    int* p1; //No initialization.
    *p1 = 3; //Possible segfault.
    
  • 4

    取消引用释放的内存可能会导致段错误 .

    SomeClass* someObject = new SomeClass();
    delete someObject;
    someObject->someMethod();  //Could cause a segfault.
    
  • 1

    试图修改字符串文字:

    char* mystr = "test";
    mystr[2] = 'w';
    

    CAN 导致分段错误 .

相关问题