【问题标题】:Avoiding null pointer exceptions in a large c++ code base在大型 C++ 代码库中避免空指针异常
【发布时间】:2009-07-25 00:54:04
【问题描述】:

我继承了一个大型 c++ 代码库,并且我的任务是避免代码库中可能发生的任何空指针异常。是否有可用的静态分析工具,我在想你已经成功使用的 lint。

你还注意什么?

【问题讨论】:

    标签: c++ null nullpointerexception


    【解决方案1】:

    您可以从消除 NULL 的来源开始:

    改变

    if (error) {
        return NULL;
    }
    

    进入

    if (error) {
        return DefaultObject; // Ex: an empty vector
    }
    

    如果返回默认对象不适用并且您的代码库已经使用异常,请执行

    if (error) {
        throw BadThingHappenedException;
    }
    

    然后,在适当的地方添加处理。

    如果您使用的是遗留代码,您可以制作一些包装函数/类:

    ResultType *new_function() {
        ResultType *result = legacy_function();
        if (result) {
            return result;
        } else {
            throw BadThingHappenedException;
        }
    }
    

    新功能应开始使用新功能并进行适当的异常处理。

    我知道有些程序员就是不会遇到异常,包括像Joel 这样的聪明人。但是,返回 NULL 最终发生的事情是,这个 NULL 会疯狂地传递,因为每个人都会认为处理它并默默返回不是他们的事。某些函数可能会返回错误代码,这很好,但调用者通常最终会返回另一个 NULL 以响应错误。然后,您会在每个函数中看到大量的 NULL 检查,无论该函数多么微不足道。而且,只需要一个不检查 NULL 来使程序崩溃的地方。异常迫使您仔细考虑错误并准确决定应该在何处以及如何处理它。

    您似乎只是在寻找简单的解决方案,例如静态分析工具(您应该始终使用)。更改指向引用的指针也是一个很好的解决方案。但是,C++ 具有 RAII 的优点,它消除了到处都有“try {} finally {}”的需要,所以我认为它值得你认真考虑。

    【讨论】:

    • 第一个是为 slop 编码,我认为这通常不是一个好主意(尽管如果它在发布日后 3 天修复了你的最后一个错误,它可以成为救命稻草!)。尽可能快地失败——如后面的示例一样抛出异常。
    • 我应该提一下,第一个称为 NullObject 模式。像任何模式一样,在使用之前三思而后行。
    【解决方案2】:

    如果您主要维护代码库,那么您可以做的最低工作量和最高回报的事情之一就是开始重构指向reference counted pointers 的裸指针。

    我还会查看 Purify 之类的东西,它将检测您的代码以检测内存损坏。

    【讨论】:

      【解决方案3】:

      首先,作为一个技术点,C++ 没有 NULL 指针异常。取消引用 NULL 指针具有未定义的行为,并且在大多数系统上会导致程序突然终止(“崩溃”)。

      至于工具,我也推荐这个问题:

      Are C++ static code analyis tools worth it?

      关于 NULL 指针取消引用,请考虑 NULL 指针取消引用具有三个主要元素:

      1. 引入 NULL 指针。
      2. 该指针在程序中其他地方的流向。
      3. 该指针的取消引用。

      静态分析工具的难点当然是第 2 步,工具的区别在于它们可以准确(即,没有太多误报)跟踪的路径有多复杂。查看您想要捕获的一些特定错误示例可能会很有用,以便更好地建议哪种工具最有效。

      免责声明:我为 Coverity 工作。

      【讨论】:

        【解决方案4】:

        如果您不想更改任何代码,则必须使用一些工具(请参阅其他答案)。但是对于问题的一个特殊部分(你在函数中放置一个指针来使用它)有一个很好的小 Makro-Definition 你可以用来找到一些小虫子: (在发布模式下没有时间开销,并为代码添加了可见条件)

            #ifdef  NDEBUG
        
            #define NotNull(X) X
        
            #else // in Debug-Mode
        
            template<typename T> class NotNull;
        
            template<typename T> // template specialization only for pointer-types
            class NotNull<T*> {
            public:
                NotNull(T* object)
                : _object(object) {
                    assert(object);
                }
        
                operator T*() const {
                    return _object;
                }
        
                T* operator->() const {
                    return _object;
                }
            private:
                T *_object;
            };
        
            #define NotNull(X) NotNull<X>
        
            #endif // in Debug-Mode
        

        你只需改变每个函数:

        void increase(int* counter)  
        { .. } 
        

        到这里

        void increase(NotNull(int*) counter)
        { .. }  
        

        p.s : 首次发现 HERE 并且可以进一步调整

        【讨论】:

          【解决方案5】:

          【讨论】:

            【解决方案6】:

            一个附带问题,避免这些的目的是因为他们不希望客户看到崩溃吗?在许多情况下,空指针是应立即处理的意外情况,但它们常常像烫手山芋一样通过系统。

            我曾经在一个代码库中工作,习惯是在进入函数时首先检查任何空指针,如果是,则返回。这样做的问题是,虽然该工具没有崩溃,但它最终默默地生成了坏数据。并且尝试调试这些问题很困难,因为在结果变得无法容忍或最终不得不表现出来之前,可能已经通过许多函数非法传递了很长时间的空指针。

            理想情况下,您至少在开发期间需要正确的断言,因此请考虑使用宏来隐藏或重新定义生产构建的断言

            【讨论】:

              猜你喜欢
              • 2011-10-01
              • 2014-05-20
              • 2019-04-24
              • 1970-01-01
              • 1970-01-01
              • 2017-02-18
              • 1970-01-01
              • 1970-01-01
              • 2015-05-07
              相关资源
              最近更新 更多