1

I have some confusion about these three concepts: Stack, Heap, and Dynamic memory allocation. I'll provide examples in C++.

  1. Am I correct to say that given a program, for all its variables, arrays, and maybe objects on stack, when the program just starting, all the memory space needed is already there, so everything is predetermined? But when the program is running, for me it still sounds like "dynamic" since a stack is still changing in a sense that values are still push into, pop off the stack, on the runtime.

  2. Regarding to heap, for the "dynamic" sense, I brought the idea from some answer on this site, it's for something determined on runtime:

    cin >> box_size;
    int *box = new int[box_size];
    

    But, then how about this:

    Box surprise_box = new Box();
    

    I already know how much space it's needed on compile time right? But it's still on the heap. So it seems like "Dynamic" memory allocation is just about the burden of allocation/deallocation of memory becomes one of programmer's.

†: It should be Box *ptr_surprise_box = new Box(); instead. (Thanks for the comment)


I understand why my question is considered too broad, but I don't know how to split them into pieces. Even worse, now I'm considering whether this has something to do with address space and those memory address determined in compile time.

Kindred
  • 1,229
  • 14
  • 41
  • 1
    *"for all its variables, arrays, and maybe objects on stack"* You make it sound like these are three mutually exclusive categories. They are not. You may have a variable of array type that names an object on the stack. And no, the program doesn't start with all its memory needs already preallocated - that would require prescience on the part of the operating system. – Igor Tandetnik Dec 17 '18 at 04:29
  • 4
    `Box surprise_box = new Box();` wouldn't normally compile with a typical class (it is possible to give `Box` an unusual constructor designed to make this compile.) – Igor Tandetnik Dec 17 '18 at 04:34
  • 2
    You should try to write some assemble code, then everything is clear. If you do not know assemble language, if will be difficult for you to get a whole picture of how CPU & Memory works. – shawn Dec 17 '18 at 06:44
  • 2
    There is no clear "dynamic" definition. For example, in C99, if you do `scanf("%d", &size); int a[size];` it is still OK and `a` is on stack, not heap. It all depends on the compiler's behavior. Memory allocated by you is on heap (you called memory allocation function, including `new()`), Memory allocated by compiler's code is on stack. – shawn Dec 17 '18 at 07:05

4 Answers4

6

I have some confusion about these three concepts: Stack, Heap, and Dynamic memory allocation.

Of course you are, you mixed concepts from different area - first 2 are related to OS, last on to the language.

For C++ there is no such thing as stack or heap. There are 4 different storage durations for objects in C++: automatic, static, thread and dynamic. Yes objects with automatic storage duration are usually stored in stack and ones with dynamic in heap, but that is details of implementations - from language point of view there are no such things. Details about storage duration can be found here

Slava
  • 43,454
  • 1
  • 47
  • 90
  • 1
    A "stack for a thread" is almost always indeed implemented as an OS stack. And thread storage duration is indeed about OS-threads. However: If I compile a program, and start running 4 threads, then it loads in a dynamic library that has a thread local global variable, we can't just "stick them on the stack", because as soon as those threads exit from whatever they're doing, that variable would be popped from the stack and cease to exist. It also can't be in static memory, because the compiler doesn't know how many threads I'll have. So all that's left is the heap. – Mooing Duck Dec 17 '18 at 05:40
  • 1
    Usually what compilers do is they give each program and library a static ThreadLocalHolder** ptr. When N threads are created, it allocates an array of of size N, and store that in the static pointer. Each thread can look up their ThreadLocalHolder, and read their thread local values. Each library has it's own static pointer for this purpose. When more threads are added, the array is is resized, but the individual ThreadLocalHolder objects never move. There's also a vaguely similar mechanism for keeping track of what order to destroy static variables. – Mooing Duck Dec 17 '18 at 05:43
3

I find that best way to look at heap vs. stack, is not in terms of "dynamic" or "knowing the size at compile time", (though, those can be factors). Instead, it is best to view them in terms of lifetime management.

Things allocated on the stack are destroyed when they go out of scope. Things allocated on the heap will exist until they are explicitly freed with a delete, free, or delete[] (depending on how they are allocated).

To answer your questions:

Am I correct to say that given a program, for all its variables, arrays, and maybe objects on stack, when the program just starting, all the memory space needed is already there, so everything is predetermined.

Eh, on a per-function basis, kinda..., but this view doesn't really account for the fact that functions can do things like calling themselves (recursion) and reach arbitrary stack size usage.

So it seems like "Dynamic" memory allocation is just about the burden of allocation/deallocation of memory becomes one of programmer's.

Yes, the fundamental difference is really all about lifetime management.

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
2
  1. Generally speaking, yes. At the beginning of your program, most of the data is on stack when you call a function, then you can allocate memory on heap. (Well, there is not only stack or heap in a program, there may be also global variables in other segments, it is a long story).

The stack space is allocated by compiler, when you enter a function, the compiler generated code allocates enough space for the stack variables automatically.

The heap space is allocated by you, when you need some memory, you call a function to allocate heap space for you.

  1. The pointer to an address of memory is a variable on stack

For your code:

cin >> box_size;
int *box = new int[box_size];

Here box is a variable on stack. The value of box variable is a pointer to the memory on heap.

But Box surprise_box = new Box(); is not valid syntax.

Examples:

  • Box box1;: the variable box1 is on stack.

  • Box *box2 = new Box();: the variable of box2 is on stack, it is a pointer, points to the memory of new Box()

Updated:

There is no clear "dynamic" definition with "heap" and "stack" concept. For example, in C99, if you do scanf("%d", &size); int a[size]; it is still OK and a can be on stack, not heap. It all depends on the compiler's behavior. Memory allocated by you is usually on heap (you called memory allocation functions, including new()), memory allocated by the compiler's code is usually on stack.

ps: I think @Slava's answer is quite good. These conepts are in different fields. stack and heap are mainly OS-related, dynamic and static are mainly language-related. I just talked about the most implementations of the modern C++ language, they happened to put new() memory into heap, etc.

shawn
  • 4,305
  • 1
  • 17
  • 25
1

User slava had mentioned these:

automatic, static, thread and dynamic

I will focus on all but thread. He also stated this:

For C++ there is no such thing as stack or heap


User Even Teran had stated this:

I find that best way to look at heap vs. stack, is not in terms of "dynamic" or "knowing the size at compile time", (though, those can be factors). Instead, it is best to view them in terms of lifetime management.

Things allocated on the stack are destroyed when they go out of scope. Things allocated on the heap will exist until they are explicitly freed with a delete, free, or delete[] (depending on how they are allocated).


I will combine the two previous answers into a single answer with demonstrations:


  • 1st - Automatic Storage

int main () {
    int x = 5;

    { // new scope
        int x = 7;
        std::cout << x << '\n';  // prints 7     
    } // end scope

    std::cout << x << '\n';  // prints 5

    return 0;
}

This is considered Automatic Storage since the variable declarations and memory are destroyed once they go out of scope. You can consider this a stack variable in a sense since in C++ each function has a stack frame. These variables x above both live within main.cpp's stack frame but have automatic storage. However the first call to std::cout will print 7 since it is within the same scope of the 2nd variable declared x. Once the closing brace } is reached this scope is destroyed and so is the 2nd x variable. So when we reach the next call to std::cout this will print 5 because it is within the same scope of the 1st declared x.


  • 2nd - Dynamic Storage

int main() {
   int x = 5;  // Automatic storage
   int* ptr = nullptr; // raw pointer

   ptr = new int(x); // Dynamic Storage
   std::cout << "x = " << x << '\n';        // prints 5
   std::cout << "*ptr = " << *ptr << '\n';  // prints 5

   { // new scope
       *ptr = 12;
   } // end scope

   std::cout << "x = " << x << '\n';        // x unchanged prints 5
   std::cout << "*ptr = " << *ptr << '\n';  // prints 12

   delete ptr; // clean up memory

   return 0;
}

Dynamic Storage lives longer than the scope it is declared in. You can pass dynamic memory from one function to another as long as the original object that it references is still valid otherwise you will have a memory leak, invalid or dangling pointer which can lead the unwanted behavior, undefined behavior, program crash, your OS erased and nuclear bombs going off! (Well the last is a bit dramatic; but don't laugh because poor memory management will reek havoc on your code base and ALL WHO use it!). This allows you to store data and to modify it from one function to another without having to create copies upon copies of it every time you need to reference it in some other calculation.


  • 3rd - Static Storage

static int i = 0; // static storage: life time of application or file scope; similar to a global...

void add1() {
   i++;
}

int main() {
    std::cout << i << '\n';
    add1();
    std::cout << i << '\n';

    for ( int n = 0; n < 10; n++ ) {
        add1();
        std::cout << "n=" << n << ": " << i << '\n';
    }

    return 0;
}

Output

0
1
n=0: 2
n=1: 3
n=2: 4
n=3: 5
n=4: 6
n=5: 7
n=6: 8
n=7: 9
n=8: 10
n=9: 11

Static Storage is a little different. There is no need to clean it up like dynamic memory since it has properties similar to Automatic Storage as it will be destroyed automatically. However, these are typically seen in global namespace or global file space they can be useful, but global variables again can be dangerous if not managed nor used properly. These typically have the lifetime of either the program application if defined in main.cpp or the lifetime of file-scope if defined in some other cpp file. The only other difference with Static Storage is it is also only initialized once, and there is usually only ever one instance of it!


Yes there are different types of storage classes and many still refer to them as stack and heap mainly cause C++ was built off of C; but the meanings and usages have change dramatically over the years. Anymore in C++ it has more to do with the lifetime and visibility of a variable than where it is located in memory.

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59