5.10.6. Dangling Pointers and Memory Faults


Ownership of memory

A running program is called a process. It is useful to think of each block of memory as having one of a few possible ownerships.

  1. The operating system (OS) keeps track of memory that a given process owns. The OS does not allow a process to look at or store anything into memory that it does not own.

  2. The heap manager is part of a process. It asks for memory from the OS as needed, and can return memory to the OS. Typically, it either requests or gives back fairly large chunks of memory.

    The heap manager keeps its own table concerning the memory in the heap. Some of that memory has been allocated by the heap manager to the program, and the program is free to do whatever it chooses with that memory. Other parts of the heap are marked as belonging to the heap manager. It still belongs to the process, but should not be used by the program. The OS does not know about the heap manager's concept of ownership of memory in the heap. As far as the OS knows, it is all owned by the process.

  3. Some of a process's memory is used for the run-time stack. The OS keeps track of the boundary of the run-time stack, and does not allow a program to use memory that has not been allocated as part of a frame. So memory in the run-time stack is allocated and deallocated from the OS in small chunks that correspond to frames.


Memory faults

A memory fault occurs when a process either uses memory in an incorrect way or uses memory that does not belong to it, according to the OS. On a Linux system, a memory fault is called a segmentation fault. When a memory fault occurs, the OS terminates the process immediately.

Dangling pointers

A dangling pointer is a pointer to memory that your program should not use. There are a few forms that a dangling pointer can have.
  1. One kind of dangling pointer is an address in a part of memory that the OS knows does not belong to your process. Using such a dangling pointer leads to a memory fault.

  2. Another kind of dangling pointer is a pointer to memory in the heap that you have deleted. For example, after

      int* p = new int;
      *p = 0;
      delete p;
    
    variable p holds a dangling pointer. Using p will probably not cause a memory fault, because that memory is almost surely still owned by the heap manager, and the OS has assigned that memory to your process. But it is an error nonetheless. The heap manager uses the memory that it owns to keep track of what it owns and what the program owns. Performing
      int* p = new int;
      delete p;
      *p = 0;
    
    can corrupt the heap-manager's information, and can cause future uses of new to fail.

    A pointer to memory that you have deleted does not necessarily refer to memory that is owned by the heap manager. Look at the following.

      int* p = new int;
      delete p;
      int* q = new int;
      *p = 0;
    
    It is entirely possible (but not a sure thing) that the memory that was allocated to p and then given back to the heap manager is used again for q. By changing *p, you might have changed *q because p and q might point to the same place. Your program is unwittingly using the same memory for two different purposes.

  3. Using the & operator, you can get the address of a variable in a function's frame in the run-time stack. After the function returns, the function's frame is removed, and that address becomes a dangling pointer.

    Suppose that p is a dangling pointer that points into the run-time stack. What happens when you use p depends on what your program has done.

    1. If p points to memory that does not belong to any allocated frame, then your program will get a memory fault.

    2. It is possible that your program has done more function calls, causing more frames to be allocated, causing p to point into one of those new frames. Using p will not cause a memory fault, but you cannot know what will happen with it. Getting the value of *p will probably lead to a senseless value. Storing something into *p can have very bad consequences to the program, since it ruins a function's stack frame. It can destroy critical information that is used to manage the run-time stack. If you corrupt the run-time stack, a debugger cannot tell what is happening in the program.

  4. An uninitialized pointer variable is usually called a dangling pointer simply because you do not know where it points. Performing

      int* p;
      *p = 0;
    
    has unpredictable effects.

  5. Using an array index that is out of the array's bounds is also considered use of a dangling pointer. For example,

      int a[10];
      a[10] = 1;
    
    has unpredictabe effects. The allowed indices of array a are 0 to 9, and a[10] is outside the array. (There is no automatic check for array bounds errors. You are expected to do those checks yourself.)


Read-only memory

Recall that some memory is marked read-only. If your program tries to change something in read-only memory, it will get a memory fault. For example, a string constant is stored in read-only memory. We will see that string constants can be treated as arrays. Performing
  char* str = "a string constant";
  str[0] = 'b';
leads to a memory fault, since it tries to alter the memory where "a string constant" is stored. Do not use a string constant as a substitute for allocating memory using new.

Memory faults due to null pointers

Memory address 0 is never owned by your process. Any attempt to use the memory at address 0 will lead to a memory fault.

Memory faults due to running out of memory

If your program runs out of memory, either in the run-time stack or the heap, it will probably get a memory fault.


Recognizing and finding memory errors

Using a dangling pointer can cause very strange errors. If something happens that appears to be impossible, then you should suspect that you are using a dangling pointer.

If you get a memory fault, use a debugger to track down exactly where the fault occurs. Just run the program in the debugger. When the memory fault occurs, the program will stop. Look at the values of pointers and array indices at that point. If you are not sure whether a pointer p is valid, try to show the value of *p.

In a Linux system, a null pointer is shown as 0x0 or (null); a pointer into the heap is a relatively small number such as 0x6a003c; and a pointer into the run-time stack is a very large number, such as 0xffffffffffff2104.


Exercises

  1. What is a dangling pointer? Answer

  2. What is a memory fault? Answer

  3. If you delete memory and immediately try to use it, do you always get a memory fault? Answer

  4. Look at the following.

      int* p = new int;
      int* q = p;
      *p = 50;
      *q = 100;
      delete p;
      printf("*q = %i\n", *q);
      delete q;
    
    Comment on what happens when this is run. Answer

  5.   int* p;
      *p = 50;
      printf("*p = %i\n", *p);
    
    Comment on what happens when this is run. Answer

  6. What are the consequences of using a dangling pointer? Answer