Jump to content

I've read about it, looked for some videos on it but I still can't wrap my head around it, I just don't understand it.

 

I'm looking for some really dumbed-down information on the topic, I'm a bit more visual so images along text might help quite a lot.

 

Thank you very much :)

 

Link to comment
https://linustechtips.com/topic/1051754-c-stack-and-heap-memory/
Share on other sites

Link to post
Share on other sites

The stack adds a frame (chunk of memory) each time you call a function, and removes that frame when you return. That means that variables on the stack only last until you return from the function that created them, so it is not correct to return a pointer to something that is in this function's stack frame.

The heap is basically just a pool of memory that you can use, which doesn't get automatically deleted. This means that you can return a pointer to something on the heap.

 

Unless you explicitly request it using new (or malloc), in C/C++ things are allocated on the stack rather than the heap. This is because the location of things on the stack are known at compile time, whereas memory on the heap is allocated at runtime, so you need to use a pointer to find the address of a value on the heap. That pointer will be stored on the stack so that the program knows where to find it (you can have pointers to pointers, but there must at some point be a pointer on the stack that you use).

 

Some examples might make it a bit clearer (if you want to try them, you'll need to replace auto with the actual return type that you want to test, and comment out the others):

auto f() {
  int a = 4; // a is on the stack
  std::string b ("foo"); // b is also on the stack, because we didn't request new
  std::string* c = &b; // c is a pointer to b (the string "foo")
      // Both b and c exist on the stack.
  std::string* d = new std::string("bar"); // now it gets a bit more complicated.
      // c is a pointer, which exists on the stack. The value pointed to by c
      // (the string "bar") is on the heap.
  
  // We are allowed to return a because we are copying the value back to the function
  return a;
  // We are also allowed to return b for the same reason
  return b;
  
  // However, we can't return a pointer to a or b, because once we return from this
  // function, neither a nor b still exist
  //return &b; // Don't do this! (But the compiler will let you, and things will break)
  //return c;  // This is exactly the same as returning &b - bad
  
  // We can return d, which is our pointer to the on-heap memory. What is returned here
  // is the *address* of "bar", which the recipient can then use to access it
  // Even though c and d have the same type (std::string*), it's only safe to return d,
  // because only d refers to memory on the heap
  return d;
  
  // We still can't return a pointer to d (the address of the variable d which contains
  // the address of "bar"), because the value of d (the address) is still on the stack.
  //return &d; // Don't do this!
}

Hopefully this clears it up a bit for you. If not, what in particular are you confused about?

HTTP/2 203

Link to post
Share on other sites

7 hours ago, Hi P said:

I've read about it, looked for some videos on it but I still can't wrap my head around it, I just don't understand it.

 

I'm looking for some really dumbed-down information on the topic, I'm a bit more visual so images along text might help quite a lot.

 

Thank you very much :)

 

You can imagine the stack like a stack of dishes. You add and take plates from the top, the last plate you put on is the first you take off. You never add or take plates from the bottom. Thus, the stack automatically grows when we add stuff we need on top and automatically shrinks when we remove stuff we no longer need. This serves as a form of automatic memory management. This is why variables on the stack are called "automatic variables" in C and C++.

 

Take the following code sample:

void
Func1()
{
	int var3;
	//Do work...
	Func2();
}

void
Func2()
{
	int var4;
	//Do work...
}

void
Func3()
{
	int var5;
	//Do work...
	Func4();
}

void
Func4()
{
	int var6;
	//Do work...
}

int 
main()
{
	int var1;
	int var2;

	Func1();
	Func3();

	return 0;
}

The program starts execution at main. Then 2 automatic variables var1 and var2 are put on the stack, so the stack looks like this:

stack1.png.19f348c27f6cde7ce449786b050d5258.png

 

Then, function Func1 is called. This function needs to know where it should return to when it is done, so the return address for Func1 is put on the stack aswell:

stack2.png.ed978a710ead7d8fdeaad0d515d9ec83.png

 

Func 1 has it's own local variable var3 and will call Func2, which again has a automatic variable and so in the end things looks like this:

stack3.png.8094b0a95777c7bfdbaa1d244e5f60c3.png

 

This is as deep as this program goes, when Func2 ends and returns, var4 - which is only visible from within Func2 - is no longer required and thus removed from the stack. The return address for Func2 is also removed from the stack and used to be able to return to the right place. The same thing then happens for var3 and Func1 and so on. This is called unwinding the stack.

 

main will then call Func3 and the stack will be grown again...

stack4.png.b2bd397fe1dbe2d974dafccebf1f24ba.png


...and again be unwound when Func4 ends.

 

You can clearly see that even tough there are 6 automatic variables throughout our program, there's never more then 4 on the stack at the same time (ignoring return addresses). That's because for the code path in the example there's never more then 4 variables in use at the same time and the stack automatically manages this.

This is simple, elegant and effective.

 

In practice, it's somewhat more complicated as function parameters and such are also pushed onto the stack but I omitted such details for clarity.

 

You can also see that the lifetime of objects clearly follows program structure. What if we want to manually determine the lifetime of a object? This is where the heap comes in. You can look at the heap simply as a large pool of memory of which you can demand a chunk (allocate) and free a chunk whenever you wish. When you allocate some heap memory with "new" the object(s) you put there will exist until you manually free them with "delete". Forgetting to free such a chunk of memory is called a memory leak. (which is why you should use smart pointers in C++ these days and avoid naked new and delete).

 

Another reason to use heap memory rather then the stack besides object lifetime is in order to allocate large amounts of memory. The stack is typically fairly small - a handful of megabytes - while the heap can be pretty much as large as a application can address on a given platform. So if you need many megabytes of RAM to load some wave sound samples, for example, that should be on the heap.

Link to post
Share on other sites

@colonel_mortis , @Unimportant

 

Thank you guys, all that was super helpful :D

 

Now that I understand what they do, do you mind explaining (in the most basic example) when to use one over another? or more exactly, when and why to use Heap over Stack?

 

(I'm aware Unimportant gave small example of it on the last part of his post)

Link to post
Share on other sites

31 minutes ago, Hi P said:

@colonel_mortis , @Unimportant

 

Thank you guys, all that was super helpful :D

 

Now that I understand what they do, do you mind explaining (in the most basic example) when to use one over another? or more exactly, when and why to use Heap over Stack?

 

(I'm aware Unimportant gave small example of it on the last part of his post)

As a general rule, you usually want to allocate on the stack where possible, and only allocate on the heap when:

  • You need to return a pointer to the allocated object
  • You need to return the allocated object, and it's not trivially small (returning an object by value rather than as a pointer means that it needs to be copied, which should be avoided for objects that aren't tiny)
  • The object is large (more than ~a few hundred bytes), as mentioned above
  • The object's size may change at runtime. For example, a std::vector allocates the memory on the heap (although the std::vector object itself may exist on the stack, and contains a pointer to the heap) because you are able to add new elements at runtime, so it's not possible to guarantee that enough memory has been allocated when it is created. In contrast, an array can be allocated on the stack or the heap, because once it's created it will have a fixed size.

nb. I've not done a huge amount of C/C++, so this is mostly based on my understanding of the theory. There may well be other reasons that I've overlooked, and it might not translate perfectly into actual code.

 

The benefit of allocating on the stack rather than the heap is that you don't have to manually free the memory later, because it will automatically be freed when you return from the function. For memory allocated on the heap, you need to remember to call delete/free when you're done with it.

HTTP/2 203

Link to post
Share on other sites

2 hours ago, Hi P said:

Now that I understand what they do, do you mind explaining (in the most basic example) when to use one over another? or more exactly, when and why to use Heap over Stack?

Plain automatic stack based variables should be preferred wherever possible.

  • They are simple to use and understand.
  • You can't leak stack.
  • The stack is hot, it's where all the action is, the cache controller knows that too. It's hard to get cache misses near where the stack pointer is pointing, heap objects can be anywhere.
  • Allocating from the heap is relatively slow. (Keep that in mind when using memory handles like std::vector. Declaring a std::vector inside a hot loop body might not be the smartest thing.)
  • In modern C++, with move semantics, moving a memory handle like std::vector is nearly just as cheap as copying the underlying raw pointer. So you can put the std::vector on the stack, move it around all you want cheap, and let it handle all the heap stuff.

That said, some reasons to use the heap:

  • Allocating large amounts of memory. (but use std::vector instead)
  • Allocating an amount of memory that is unknown at compile time. For example, if you need to load the contents of a user-selected file to memory. You only know how large the file is, and thus how much memory you need, once the user has selected the file at runtime. C++ does not support variable length arrays - the size of all stack based objects must be known compile time. So allocating a variable amount of memory can only be done from the heap. (but use std::vector instead)
  • Objects that should not move. Imagine a "Document" class that encapsulates everything that represents a document. Then another class called "DocumentViewWindow" that represents a window that graphically displays a documents contents. The view holds a pointer to the document it's currently displaying as to allow changing document by simply changing the pointer. The document currently being viewed should not move as this would invalidate the views pointer to it. One way to make absolutely sure of this and prevent bugs is to delete the document's move constructor and assignment (to prevent accidental moves) and allocate documents dynamically on the heap. That way the document will sit at the same position in memory for it's entire lifetime.
  • When certain design patterns are used that simply require it. Such as the abstract factory pattern. When you only know which implementation of a interface to instantiate at runtime you've little choice but to do it dynamically.
  • When ObjectA is a member variable of ObjectB, it requires the full definition of ObjectA to be available for the definition of ObjectB. In larger project this can lead to circular dependencies. If you instead have a ObjectA (smart) pointer as ObjectB's member, you can get away with only a forward declaration. This however requires dynamically allocating the ObjectA.

For clarity: Dynamic allocation means heap.

 

Might be some more reasons but these are the important ones that readily spring to mind.

Link to post
Share on other sites

First off, just because this confused me when I was first learning to program, Stack and Heap memory are all stored in the same memory.  Both are stored in Ram, both are managed by the operating system, etc.  

 

Ignoring when things go wrong, the primary difference between the two is performance.  Data stored in the stack can be better cached by the processor, meaning that retrieving data from it is almost always faster than retrieving data stored in a buffer allocated from the heap.

 

 

 

 

 

Link to post
Share on other sites

The others have done an extremely good job of explaining the difference between the two. To help myself remember what the Heap actually is, I prefer to call it by Bjarne Stroustrup's old preferred term, The Free Store.

 

Some people claim that New/Delete use the Free Store while Malloc/Free use the Heap. In reality, the two are identical and all that New/Delete really is, is a compiler macro for Malloc/Free, in which the compiler figures out how much memory to allocate or free, and then adds the function call to the objects constructor and destructor.

 

This functionality is actually exposed to the developer in modern C++ using the Placement New operator.

ENCRYPTION IS NOT A CRIME

Link to post
Share on other sites

This is how I remember it. 

 

Stack = everything in the function. 

 

function xyz(parameter xyz){

    // Everything here is in stack 

    // Everything here not a pointer(with new) or declared static is destroyed after function return. (For c or cpp)

}

 

 

Heap = everything else. I don't dont know what's on here and I don't care. different langauges put different stuffs here. it is generally extra space in ram. 

Sudo make me a sandwich 

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×