Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ catching dangling reference

Suppose the following piece of code

struct S {
    S(int & value): value_(value) {}
    int & value_;
};

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

compiler does not produce warning (-Wall), this error can be hard to catch.

What tools are out there to help catch such problems

like image 477
Anycorn Avatar asked Jul 07 '10 21:07

Anycorn


People also ask

What is dangling reference with example?

A dangling reference is a reference to an object that no longer exists. Garbage is an object that cannot be reached through a reference. Dangling references do not exist in garbage collected languages because objects are only reclaimed when they are no longer accessible (only garbage is collected).

How we can avoid the problem of dangling reference?

We can avoid the dangling pointer errors by initialize pointer to NULL , after de-allocating memory, so that pointer will be no longer dangling. Assigning NULL value means pointer is not pointing to any memory location.

What is a dangling reference and why are they a problem?

Dangling pointers arise during object destruction, when an object that has an incoming reference is deleted or deallocated, without modifying the value of the pointer, so that the pointer still points to the memory location of the deallocated memory.

How do you get rid of dangling pointers?

The dangling pointer errors can be avoided by initializing the pointer to the NULL value. If we assign the NULL value to the pointer, then the pointer will not point to the de-allocated memory. Assigning NULL value to the pointer means that the pointer is not pointing to any memory location.


5 Answers

There are runtime based solutions which instrument the code to check invalid pointer accesses. I've only used mudflap so far (which is integrated in GCC since version 4.0). mudflap tries to track each pointer (and reference) in the code and checks each access if the pointer/reference actually points to an alive object of its base type. Here is an example:

#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!
}

Compile this with mudflap enabled:

g++ -fmudflap s.cc -lmudflap

and running gives:

$ ./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

A couple of points to consider:

  1. mudflap can be fine tuned in what exactly it should check and do. read http://gcc.gnu.org/wiki/Mudflap_Pointer_Debugging for details.
  2. The default behaviour is to raise a SIGSEGV on a violation, this means you can find the violation in your debugger.
  3. mudflap can be a bitch, in particular when your are interacting with libraries that are not compiled with mudflap support.
  4. It wont't bark on the place where the dangling reference is created (return S(value)), only when the reference is dereferenced. If you need this, then you'll need a static analysis tool.

P.S. one thing to consider was, to add a NON-PORTABLE check to the copy constructor of S(), which asserts that value_ is not bound to an integer with a shorter life span (for example, if *this is located on an "older" slot of the stack that the integer it is bound to). This is higly-machine specific and possibly tricky to get right of course, but should be OK as long it's only for debugging.

like image 133
Nordic Mainframe Avatar answered Oct 14 '22 22:10

Nordic Mainframe


I think this is not possible to catch all these, although some compilers may give warnings in some cases.

It's as well to remember that references are really pointers under the hood, and many of the shoot-self-in-foot scenarios possible with pointers are still possible..

To clarify what I mean about "pointers under the hood", take the following two classes. One uses references, the other pointers.

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; };
};

Now, compare at the generated code for the two.

@@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 

Spot the difference? There isn't any.

like image 33
Roddy Avatar answered Oct 14 '22 22:10

Roddy


You have to use an technology based on compile-time instrumentation. While valgrind could check all function calls at run-time (malloc, free), it could not check just code.

Depending your architecture, IBM PurifyPlus find some of these problem. Therefore, you should find a valid license (or use your company one) to use-it, or try-it with the trial version.

like image 42
Doomsday Avatar answered Oct 15 '22 00:10

Doomsday


I don't think any static tool can catch that, but if you use Valgrind along with some unit tests or whatever code is crashing (seg fault), you can easily find where the memory is bring referenced and where it was allocated originally.

like image 26
Dave Avatar answered Oct 15 '22 00:10

Dave


There is a guideline I follow after having been beaten by this exact thing:

When a class has a reference member (or a pointer to something that can have a lifetime you don't control), make the object non-copyable.

This way, you reduce the chances of escaping the scope with a dangling reference.

like image 34
Alexandre C. Avatar answered Oct 15 '22 00:10

Alexandre C.