代码之家  ›  专栏  ›  技术社区  ›  Anycorn

C++悬空参考

  •  12
  • Anycorn  · 技术社区  · 16 年前

    假设下面的代码

    struct S {
        S(int & value): value_(value) {}
        int & value_;
    };
    
    S function() {
        int value = 0;
        return S(value);   // implicitly returning reference to local value
    }
    

    编译器不产生警告(-wall),很难捕获此错误。

    有哪些工具可以帮助解决这些问题

    7 回复  |  直到 7 年前
        1
  •  10
  •   Nordic Mainframe    16 年前

    有一些基于运行时的解决方案检测代码以检查无效的指针访问。到目前为止,我只使用了mudflap(它自4.0版以来集成在gcc中)。mudflap尝试跟踪代码中的每个指针(和引用),并检查每个访问是否指针/引用实际指向其基类型的活动对象。下面是一个例子:

    #include <stdio.h>
    struct S {
        S(int & value): value_(value) {}
        int & value_;
    };
    
    S function() {
        int value = 0;
        return S(value);   // implicitly returning reference to local value
    }
    int main()
    {
        S x=function();
        printf("%s\n",x.value_); //<-oh noes!
    }
    

    在启用mudflap的情况下编译:

    g++ -fmudflap s.cc -lmudflap
    

    跑步给你:

    $ ./a.out
    *******
    mudflap violation 1 (check/read): time=1279282951.939061 ptr=0x7fff141aeb8c size=4
    pc=0x7f53f4047391 location=`s.cc:14:24 (main)'
          /opt/gcc-4.5.0/lib64/libmudflap.so.0(__mf_check+0x41) [0x7f53f4047391]
          ./a.out(main+0x7f) [0x400c06]
          /lib64/libc.so.6(__libc_start_main+0xfd) [0x7f53f358aa7d]
    Nearby object 1: checked region begins 332B before and ends 329B before
    mudflap object 0x703430: name=`argv[]'
    bounds=[0x7fff141aecd8,0x7fff141aece7] size=16 area=static check=0r/0w liveness=0
    alloc time=1279282951.939012 pc=0x7f53f4046791
    Nearby object 2: checked region begins 348B before and ends 345B before
    mudflap object 0x708530: name=`environ[]'
    bounds=[0x7fff141aece8,0x7fff141af03f] size=856 area=static check=0r/0w liveness=0
    alloc time=1279282951.939049 pc=0x7f53f4046791
    Nearby object 3: checked region begins 0B into and ends 3B into
    mudflap dead object 0x7089e0: name=`s.cc:8:9 (function) int value'
    bounds=[0x7fff141aeb8c,0x7fff141aeb8f] size=4 area=stack check=0r/0w liveness=0
    alloc time=1279282951.939053 pc=0x7f53f4046791
    dealloc time=1279282951.939059 pc=0x7f53f4046346
    number of nearby objects: 3
    Segmentation fault
    

    需要考虑的几点:

    1. 挡泥板可以精确调整它应该检查和执行的操作。阅读 http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging 详情。
    2. 默认行为是在冲突上引发一个sigsegv,这意味着您可以在调试器中找到冲突。
    3. mudflap可能是一个婊子,特别是当您与不使用mudflap支持编译的库进行交互时。
    4. 它不会在创建悬挂引用的地方吠叫(返回s(value)),仅当引用被取消引用时。如果您需要这个,那么您将需要一个静态分析工具。

    另一个需要考虑的问题是 非便携式 检查s()的复制构造函数,该构造函数断言值_u未绑定到寿命较短的整数(例如,如果*它位于它绑定到的整数所在堆栈的“较旧”槽中)。这是高度特定于机器的,当然可能很难正确处理,但是只要只用于调试就可以了。

        2
  •  4
  •   Roddy    16 年前

    我认为这不可能捕捉到所有这些,尽管有些编译器在某些情况下可能会发出警告。

    还需要记住的是,参考资料实际上是引擎盖下的指针,许多可能带有指针的脚中射击场景仍然是可能的。

    为了澄清我所说的“引擎盖下的指针”是什么意思,请学习以下两个课程。一个使用引用,另一个使用指针。

    class Ref
    {
      int &ref;
    public:
      Ref(int &r) : ref(r) {};
      int get() { return ref; };
    };
    
    class Ptr
    {
      int *ptr;
    public:
      Ptr(int *p) : ptr(p) {};
      int get() { return *ptr; };
    };
    

    现在,比较生成的代码。

    @@Ref@$bctr$qri proc    near  // Ref::Ref(int &ref)
        push      ebp
        mov       ebp,esp
        mov       eax,dword ptr [ebp+8]
        mov       edx,dword ptr [ebp+12]
        mov       dword ptr [eax],edx
        pop       ebp
        ret 
    
    @@Ptr@$bctr$qpi proc    near  // Ptr::Ptr(int *ptr)
        push      ebp
        mov       ebp,esp
        mov       eax,dword ptr [ebp+8]
        mov       edx,dword ptr [ebp+12]
        mov       dword ptr [eax],edx
        pop       ebp
        ret 
    
    @@Ref@get$qv    proc    near // int Ref:get()
        push      ebp
        mov       ebp,esp
        mov       eax,dword ptr [ebp+8]
        mov       eax,dword ptr [eax]
        mov       eax,dword ptr [eax]
        pop       ebp
        ret 
    
    @@Ptr@get$qv    proc    near // int Ptr::get()
        push      ebp
        mov       ebp,esp
        mov       eax,dword ptr [ebp+8]
        mov       eax,dword ptr [eax]
        mov       eax,dword ptr [eax]
        pop       ebp
        ret 
    

    找出区别?没有了。

        3
  •  2
  •   Doomsday    16 年前

    您必须使用基于编译时工具的技术。虽然valgrind可以在运行时检查所有函数调用(malloc,free),但它不能仅检查 代码 .

    根据你的架构, IBM PurfyPrPull 找到其中一些问题。因此,您应该找到一个有效的许可证(或者使用您的公司许可证)来使用它,或者使用试用版进行尝试。

        4
  •  1
  •   Dave    16 年前

    我认为任何静态工具都无法捕捉到这一点,但如果您使用 Valgrind 除了一些单元测试或者任何代码崩溃(SEG故障)之外,您可以很容易地找到内存的引用位置和最初分配位置。

        5
  •  1
  •   Stack Overflow is garbage    16 年前

    你的代码甚至不应该编译。我所知道的编译器要么无法编译代码,要么至少发出警告。

    如果你是说 return S(value) 相反,看在上帝的份上 复制粘贴您在此处发布的代码 .

    重写和引入错别字只意味着我们不可能真正猜出你犯了什么错误。 询问 关于,哪些是我们应该忽略的事故。

    当你在互联网上的任何地方发布问题时,如果该问题包含代码, 发布准确的代码 .

    现在,假设这是一个打字错误,代码是完全合法的,没有理由 任何 工具应该警告您。

    只要您不尝试取消对悬空引用的引用,代码是完全安全的。

    一些静态分析工具(例如valgrind或msvc with/analyze)可能会警告您这一点,但似乎没有什么意义,因为您没有做错任何事情。您返回的对象恰好包含悬空引用。您不会直接返回对本地对象的引用(通常编译器 警告),但具有使其使用完全安全的行为的更高级别对象,即使它包含对超出范围的本地对象的引用。

        6
  •  1
  •   Alexandre C.    16 年前

    在被这件事打败后,我遵循了一条准则:

    当一个类有一个引用成员(或者指向某个你不能控制的对象的指针)时,使该对象不可复制。

    通过这种方式,可以减少使用悬空引用逃离作用域的机会。

        7
  •  0
  •   Andreas    16 年前

    这是完全有效的代码。

    如果调用函数并将临时对象绑定到常量引用,则作用域将延长。

    const S& s1 = function(); // valid
    
    S& s2 = function(); // invalid
    

    这在 C++ standard .

    见2.2.4:

    有两种情况下,临时性在不同于完整表达式结尾的点被破坏。

    和122.5:

    第二个上下文是当引用绑定到临时上下文时。引用绑定到的临时对象或作为引用绑定到的子对象的完整对象的临时对象将在引用的生存期内持续存在,但:[…]