如何安全地检查空指针

时间:2011-03-01 00:49:19

标签: c++

(使用visual studio 2010)

我有一个简单的returnLength()函数用于列表,简单定义为

int returnLength() 
{
    if (!next) return 1;
    else return 1 + next->returnLength();
}

next是指向列表中另一个节点的指针。检查if语句以检查next是否有效时,Visual Studio会引发运行时错误,引用访问冲突。这个错误发生在递归树深处的几次调用中。

检查指针是否存在的推荐方法是什么?

7 个答案:

答案 0 :(得分:4)

这是一种有效的方式。这里可能发生的是thisNULL,因此您尝试从next指针读取NULL时遇到访问冲突。您需要在NULL

的呼叫站点检查returnLength

答案 1 :(得分:3)

您展示的代码有效。最有可能发生的是通过无效的this指针调用此函数。

答案 2 :(得分:3)

听起来列表中的最后一个节点的next值无效 - 它应该是NULLnext在此类的默认构造函数中是否已明确初始化为NULL?如果不是那么可能是问题。

答案 3 :(得分:2)

Visual Studio不会引发运行时错误,但我知道你的意思。

我们需要比这更多的代码。返回长度是您的班级列表的方法吗? next在哪里宣布?它在哪里初始化?最有可能的是,next是未初始化的指针或指向释放内存的指针。至少,它是指向无效位置的指针,因此是访问冲突错误。

适当地初始化next。检查指针是否为空是指针有效性的唯一合适机制。

编辑:听起来你通常不熟悉指针有效性。您无法检查指针是否指向有效位置。您只能检查指针是否指向普遍无效的位置null0。所以......如果你没有指向某个东西的指针,你应该初始化或设置为null0,这就是它的目的。

答案 4 :(得分:1)

考虑到您的描述,next似乎不为空,但仍然无效。例如。如果您的ListNode::RemoveNextNode()函数有错误,它会删除下一个节点,但无法更改this->next。在这种情况下,您有一个指向已释放内存的指针。你无法测试;你必须确保它不会发生。

一般来说,解决方案当然是使用std::list

答案 5 :(得分:1)

在C ++中,变量不能保证用合理的东西进行初始化。如果您创建一个变量:     MyType * next;

next指针可以是NULL,也可以是任何其他值。此值将是垃圾,但不会为零,并且会导致您的访问冲突。

为了保证nextNULL,您必须确保在对象的构造函数中将其设置为NULL

有关详细信息,请参阅Variable initialization in C++

答案 6 :(得分:0)

template <typename T_TYPE, typename T_RET, typename T_DELETE_POLICY>
class ToRef: public boost::tuple<T_TYPE*const&, T_RET, T_DELETE_POLICY>
{
public:
    ToRef<T_TYPE, T_RET, T_DELETE_POLICY>(T_TYPE*const& p_type, const T_DELETE_POLICY delete_policy)
    : boost::tuple<T_TYPE*const&, T_RET, T_DELETE_POLICY>(p_type, p_type!=NULL, delete_policy)
{
}
};

template <typename T_TYPE>
class ToRef<T_TYPE, bool, bool>: public boost::tuple<T_TYPE*const&, bool, bool>
{
public:
    explicit ToRef<T_TYPE, bool, bool>(T_TYPE*const& p_type, const bool delete_policy)
    :boost::tuple<T_TYPE*const&, bool, bool>(p_type, p_type!=NULL, delete_policy)
{
}

~ToRef()
{
    //delete policy
    if (get<2>())
    {
        if (NULL != get<0>())
            delete get<0>();
        const_cast<T_TYPE*&>(get<0>())= NULL;
    }
}

private:
    ToRef<T_TYPE, bool, bool>(ToRef<T_TYPE, bool, bool>& copy){};
    ToRef<T_TYPE, bool, bool>& operator = (ToRef<T_TYPE, bool, bool>& rhs){};

public:
    bool is_valid(void) const
    {
        //validity of the pointer.
        return get<1>();
    }

    T_TYPE& r_get(void) const
    {
        if (is_valid())
            return *get<0>();
        throw std::string("Invalid Pointer");
    }
protected:
    T_TYPE*const & p_get(void) const
    {
        return get<0>();
    }
};

//use it to safely access the pointers.
//if block is an overhead here.
#define safe_access_start(Ref) if (Ref.is_valid()) {
#define safe_access_end } 

//faster mode but unsafe. exception handling takes care of preventing 
//unhandled exception to cascade to the top.
#define fast_access_start try {
#define fast_access_end }catch (std::string s_exception){ TRACE("%s, %d, 
   %s", __FILE__, __LINE__, LPCTSTR(s_exception.c_str()));}
  

在你的情况下它可能会像这样工作

 int returnLength() 
    {
        if (!next) return 1;

        sa::ToRef<Stest, bool, bool> testRef(sTest, false);

        safe_access_start(testRef)
        return 1 + testRef.r_get().returnLength();
        safe_access_end

        return 1;

    }