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
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).
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.
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.
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.
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:
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.
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With