2

I'm trying to come up with an example for a dangling pointer in C, but the code I came up with produces results that don't exactly display what I'm expecting.

#include <stdio.h>

int* dangling_ptr();

int main(void)
{
    int* ptr = dangling_ptr();
    printf("%d\n", *ptr); // outputs 5 to the console
    return 0;
}

int* dangling_ptr()
{
    int x = 5;
    int* x_ptr = &x;

    return x_ptr;
}

I was expecting some garbage integer value to be sitting in the memory returned, but de-referencing and printing the value displays 5 to the screen.

I know that the memory address returned by dangling_ptr() is not valid anymore, but it's clear that the memory still hasn't been overwritten. This brings up two questions.

  1. Is my understanding of dangling pointers here wrong? The memory should now be invalid, correct?

  2. If I am correct in my understanding, why hasn't the OS reclaimed that memory and made it invalid? Why is the value 5 still sitting there?

Visual Studio
  • 184
  • 1
  • 10
  • 4
    Your expectation is wrong. Accessing an invalid pointer is Undefined Behaviour. Not garbage values. Not 0. Not anything. It's undefined which means it can have any of those behaviours including appearing to "work". – kaylum Dec 23 '21 at 01:20
  • 1
    Does this answer your question? [Undefined, unspecified and implementation-defined behavior](https://stackoverflow.com/questions/2397984/undefined-unspecified-and-implementation-defined-behavior) – kaylum Dec 23 '21 at 01:21
  • 1
    I think your confusion is to think that allocating (and releasing) memory on the stack is something magic. It is not: The stack is simply a succession of objects with "automatic storage duration" (vulgo: local variables and parameters) in memory in the order of their creation. The location where the next object will be created is kept track of by some internal pointer, much like a water tank gauge. When a variable goes out of scope and the object's life time ends, that pointer is simply incremented or decremented by the size of the object. Nothing else happens to that memory. – Peter - Reinstate Monica Dec 23 '21 at 02:02
  • 1
    I suggest that you read [this answer](https://stackoverflow.com/questions/6441218/can-a-local-variables-memory-be-accessed-outside-its-scope/6445794#6445794) to a corresponding C++ question. That answer provides a very interesting analogy, and has been upvoted nearly 5000(!) times. Most of what is said about C++ also applies to C. – Andreas Wenzel Dec 23 '21 at 02:35
  • @AndreasWenzel Thank you for the link! That analogy definitely helped me get a better perspective about the rules of memory access in C/C++ – Visual Studio Dec 23 '21 at 02:51

1 Answers1

4

Is my understanding of dangling pointers here wrong?

Yes.

The memory should now be invalid, correct?

Depends on how you define "invalid". It is still accessible, and may contain the original, or any other value (the value it contains is undefined).

why hasn't the OS reclaimed that memory and made it invalid

You mean "inaccessible", not "invalid".

Doing so for stack operations would be exceedingly inefficient, and no OS in existence does this. This isn't done for heap either (outside of heap debug allocators), for the same reason.

In addition, OSes are limited by the capabilities of the processors they are running on, and no mainstream processor has ability to make inaccessible anything less than a full page (usually 4096 bytes or more). So even if the OS wanted to make the dangling stack inaccessible, it wouldn't be able to -- the area it would need to make inaccessible is too small.

Update:

So then would this not be a dangling pointer?

Yes, this is a dangling stack pointer.

My understanding is that the function dangling_ptr() allocates memory for x on the stack and returns the address of that memory.

Correct.

Once dangling_ptr() terminates, all the memory reserved via that stack frame is freed and now no longer reliable to refer to.

Correct in that you should no longer read or write that memory.

Incorrect in that nothing is freed here -- the memory is still on the stack, it's just in the unallocated part of the stack.

Where am I going wrong in my understanding there?

You believe that once the memory is unallocated, something definitive should happen when you access it.

But what actually happens is undefined -- anything can happen. Among the things which could happen: you could read the original value, you could read some other value, your program may crash (this outcome is unlikely on most OSes for reasons stated above).

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • So then would this not be a dangling pointer? My understanding is that the function `dangling_ptr()` allocates memory for `x` on the stack and returns the address of that memory. Once `dangling_ptr()` terminates, all the memory reserved via that stack frame is freed and now no longer reliable to refer to. Where am I going wrong in my understanding there? – Visual Studio Dec 23 '21 at 01:32
  • Notable: OS/400 does some pretty ridiculous things to protect pointers stored in user space from being used to access object regions no longer/ever considered valid or viable, including a protected bit map that flags whether the content of a user pointer does, in fact, contain a resolvable address. It is one of the reasons that any structure packing goes out the window once a pointer member is introduced (since they must reside on specific boundaries to line up with the protection map) Frankly, it's amazing, and worthy of a side research by interested readers. – WhozCraig Dec 23 '21 at 01:38
  • @RyanR "... and now no longer reliable to refer to." is true. It is _unreliable_. It might work, might fail, might crash and catch fire. – chux - Reinstate Monica Dec 23 '21 at 01:46
  • @RyanR I've updated the answer. – Employed Russian Dec 23 '21 at 01:52
  • @EmployedRussian Thank you so much. Your update really clarified a lot. I've marked it as my answer. – Visual Studio Dec 23 '21 at 02:21