C++ Tutorial - Debugging Crash & Memory Leak
Bookmark and Share






Undefined Behavior

Undefined behavior can happen because there are lots of things not specified by the C/C++ standard for a variety of reasons.

One of the reasons may be to allow flexibility in implementation. The specification leaves the results of certain operations specifically undefined.
When we are facing undefined behavior, anything might happen, such as crash, freeze, etc. The worst case is it appears to work correctly.

For example, related to array bound checking, the standard simply says what should happen if we access the elements within the bounds of an array. The behavior is left undefined. Anything can happen when we use free not delete to deallocate the memory of an object which was previously allocated by new. Also, using these values at un-initialized memory can lead to unpredictable program behavior as well.



Incorrect Memory Usage and Corrupted Memory

Here are the primary sources of the memory related problems.

  1. Using memory not initialized
  2. Using memory that we do not own
  3. Using more memory than allocated (buffer overruns)
  4. Using faulty heap memory management


Accessing NULL pointer - invalid object

When we try to access a method of an object using a NULL pointer, our program crashes.

Here is a typical example of accessing an object with invalid pointer.

#include <iostream>

using namespace std;

class A 
{
	int value;
public:
	void dumb() const {cout << "dumb()\n";}
	void set(int x) {cout << "set()\n"; value=x;}
	int get() const {cout << "get()\n"; return value;}
};

int main()
{
	A *pA1 = new A;
	A *pA2 = NULL;
   
	pA1->dumb();
	pA1->set(10);
	pA1->get();
	pA2->dumb();
	pA2->set(20);
	pA2->get();

        return 0;
}

Output from the run:

dumb()
set()
get()
dumb()
set()

We have three member function of a class A, "dumb()", "set()", and "get()". Pointers to A object are calling the methods of A. There is no problem calling those methods with properly allocated pointer pA1. However, the code crashes at the line:

pA2->set(20); 

Why?
In the line, "set(20)" is invoked for a NULL pA2, it crashes when we try to access member variables of A class while there is no problem in calling "dumb()" with the same NULL pointer to the A object.

Invoking a method with an illegal object pointer is the same as passing an illegal pointer to a function. A crash happens when any member variable is accessed in the called method. In other words, the "set(20)" tries to access a member variable "value" but "dumb()" method does not.

If a pointer is a dangling pointer (pointing to memory that has already been freed), or to a memory location outside of current stack or heap bounds, it is referring to memory that is not currently possessed by the program. And using such pointer usually leads to a program crash.




Dangling Pointer

A dangling pointer arises when a code uses a memory resource after it has been freed as in the example below.

struct X 
{
	int data;
};

int foo() 
{
	struct X *pX;
	pX = (struct X *) malloc(sizeof (struct X));
	pX->data = 10;	   
	free(pX);
        ...	  
	return pX->data;
}

The function "foo()" returns a member of struct X by using a pointer "pX" that has already released its memory. There is a chance that the memory block to which xp points has been overwritten with a different value. In the worst case, it may be deep into other places until it shows some symptoms. Dangling pointers are a constant source of headaches for C/C++ programs.



Uninitialized Pointer

Another common mistake is trying to access uninitialized memory as the example below.

void fooA()
{
	int *p;
	*p = 100;
}

Most of the implementation of compiler, this triggers "segmentation violation."

As another example, the code below trying to free the pointer "p" which has not been initialized.

void fooB()
{
	int *p;
	free(p);
}

The outcome of this error is actually undefined, in other words, anything can happen.



Deallocation Error

Freeing a memory which has already been freed is another example of memory error.

void fooA() 
{
	char *p;
	p = (char *)malloc(100);	 
	cout << "free(p)\n";
	free(p);
	cout << "free(p)\n";
	free(p);
}

This type of error results in undefined behavior, it may crash or it may be passed unnoticed.



Not calling derived class destructor
ParentClass *pObj = new ChildClass;
...
delete pObj;

In the above example, coder's intention is do free the memory allocated for Child class object. However, because the type of "pObj" is a pointer to a Parent class, it deletes Parent object leaving the memory allocated for the Child object untouched. So, the memory leak.

In this case, we need to use a virtual destructor to avoid this problem. The ~ParentClass() is called and then the destructor for Child class ~ChildClass() is called at run time because it is a virtual destructor. If it is not declared virtual, then only the ~ParentClass() is called leaving any allocated memory from the ChildClass to persist and leak.



Buffer Overflow

Depending on the length of the string, it may be attempting to write where the memory is not alloacted (void * memcpy ( void * destination, const void * source, size_t sz ).

char *s = (char *)malloc(128*sizeof(char));
memcpy(s, str, str_len);

As another example, when we try to copy a string, we need to consider the null character at the end of the string.

char *p = (char *)malloc(strlen(str));
strcpy(p, str);

In the code, we need to change the strlen(str) to strlen(str)+1.



gdb with core file generated by Segmentation fault

Segmentation fault occur when a program attempts to access memory not allowed. This is often caused by improper usage of pointers in the source code, dereferencing a null pointer as shown in the example below.

Usually signal SIGSEGV set, which is defined in the header file signal.h file. The default action for a program upon receiving SIGSEGV is abnormal termination. This action will end the process, but may generate a core file (aka core dump) to aid debugging, or perform some other platform-dependent action. A core dump is the recorded state of the working memory of a computer program at a specific time, generally when the program has terminated abnormally.

Here is the file to generate the core file. The code is dereferencing a NULL pointer in bad(), and when we run, it gives us core file.

/* bad.c */

int bad(int *pt)
{
  int x = *pt;
  return x;
}

int main()
{
  int *ptr = 0;   /* null pointer */
  return bad(ptr);
}

Compile and run:

$ gcc -g -o ./bad bad.c
$ ./bad
Segmentation fault
$ ls
bad  bad.c

As we see from the list, core was not made. To generate the core file, we need to do specify the size of the file:

$ ulimit -c
0
$ ulimit -c unlimited
$ ulimit -c
unlimited

If we run it again:

$ ./bad
Segmentation fault (core dumped)
$ ls
bad  bad.c  core.2333

Now, let's run gdb with the exec and core file names as arguments:

$ gdb ./bad core.2333
GNU gdb (GDB) Fedora (7.5.1-37.fc18)
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
...
Reading symbols from /home/KHong/Work/Debug/bad...done.
[New LWP 2333]
Core was generated by `./bad'.
Program terminated with signal 11, Segmentation fault.
#0  0x00000000004004f8 in bad (pt=0x0) at bad.c:5
5	  int x = *pt;
(gdb) backtrace
#0  0x00000000004004f8 in bad (pt=0x0) at bad.c:5
#1  0x000000000040051e in main () at bad.c:12
(gdb) 

More on core dump, please visit Debugging - core/memory dump.






Check also,
Memory Allocation
Debugging & Profiling



More to come...


Sunset2