![]() |
|
Stack vs. Heap storage - Printable Version +- Sinisterly (https://sinister.ly) +-- Forum: Coding (https://sinister.ly/Forum-Coding) +--- Forum: C, C++, & Obj-C (https://sinister.ly/Forum-C-C-Obj-C) +--- Thread: Stack vs. Heap storage (/Thread-Stack-vs-Heap-storage) |
Stack vs. Heap storage - Inori - 01-30-2017 In C and it's derivatives, (as well as most, if not all other languages) variables are stored in two separate ways. Static types (often called fixed or fixed length) are stored on the "stack", whereas dynamic storage (namely Objects and their properties in any OO language like C++, C#, Python, Ruby, etc) is handled by the heap. There are significant pros and cons to both systems, and it's good to know when to use one over the other when you're the one manually allocating all the memory. The Stack
The stack is a special place in memory that stores local variables (i.e. temporary; this is important) created by each function in your program (main() included). The stack is what's commonly called a LIFO structure, meaning "last in, first out", and it's managed entirely by the CPU, unless explicitly called upon. Whenever a local variable is declared inside a function, said variable is pushed to the stack. When that function returns, all the variables pushed to the stack that the function created are freed from memory (you could also say they're deleted). When the space that a variable is taking up inside the stack is freed, that region immediately becomes available to other stack variables. A quick demonstration of the stack is as follows: Code: #include <stdio.h> // io utilities
// function print the square of an integer, n
void print_square(int n){
// declare a variable sq_n of type int. Memory for
// sq_n is allocated for us in the stack
int sq_n;
// assign sq_n to n squared, filling the allocated memory
sq_n=n*n;
// print the value
printf("%i\n",sq_n);
// end of function; sq_n is deallocated
}
// main function
int main(){
// declare a variable num of type int, then fill the
// allocated memory with the int 7
int num=7;
// call the print_square() function using num
print_square(num);
// return from the function; num is deallocated
return 0;
}The Heap
The heap is another region of memory available to your program that is not automatically managed for you, and is not optimized by the CPU. It's often referred to as "free-floating" memory, and is larger than the stack. To allocate memory to the heap, you must do so manually with the standard library malloc() or calloc() functions. Once memory has been allocated to the heap, you are responsible for clearing it with the free() function (also stdlib) to avoid a memory leak. Contrary to the stack, there is no size limit for variables stored in the heap except for the physical limit of the computer your program is running on. Heap variables, again unlike those in the stack, are passed by using pointers (which themselves are stored on the stack, and reference the value in the heap) instead of passing by value, as you would with a stack variable. In light of these points, heap storage is just a bit slower than stack. This difference in speed can appear negligible in small programs, but it can add up as programs get larger and more complex. Finally, once created, heap variables are accessible anywhere in your program, essentially putting them in a global scope. A demonstration of heap storage is as follows. Obviously, this isn't the optimal way to write a program with this functionality, but it serves as a good demonstration: Code: #include <stdlib.h> // C standard library
#include <string.h> // string utilities
#include <stdio.h> // IO utilities
// declare an empty char pointer
char *str;
// function to reallocate heap memory to *str and
// copy a greeting to the address
void create_greeting(char *name){
// reallocate enough heap memory for the
// greeting and the name
realloc(str,sizeof(str)+sizeof(name));
// copy the formatted greeting to our heap memory
sprintf(str,"Hello, %s!",name);
// end of function; str is not deallocated because it's
// on the heap
}
// main function
int main(){
// allocate enough heap memory to our pointer for
// a greeting with no name (which is 8 characters)
str=(char*) malloc(8*sizeof(char));
// declare a *fixed* char array to hold a name; this is pushed
// to the stack automatically, because it has a fixed size
char name[]="Sinisterly";
// call the create_greeting() function with our name
create_greeting(name);
// print our greeting
printf("%s\n",str);
// manually free str from the heap
free(str);
// return from the function; our name string is freed
// from the stack
return 0;
}Pros, cons, and use cases
Now that we've gone over what does what, we can list the pros and cons of both storage methods, as well as use cases. Stack storage Pros: automatically allocated and freed, faster, CPU optimized Cons: limited variable size (varies by OS), variables are fixed size Most commonly, you should use the stack when dealing with relatively small variables that only need to exist for the lifetime of a function. It's easier and faster, so you might as well utilize it. Heap storage Pros: global access to variables, only size limit is the system's physical capacity, variables can be resized Cons: slightly slower read/write operations, manual allocation/deallocation Again generally speaking, you should use the heap when you need to allocate a large block of memory (e.g. a large array, a struct with lots of members, etc) and/or you need to keep it around for a long period of time, or access it globally. RE: Stack vs. Heap storage - silur - 02-01-2017 You have an error in your code, Code: sizeof(char*) != strlen(char*)RE: Stack vs. Heap storage - Inori - 02-01-2017 (02-01-2017, 12:51 PM)silur Wrote: You have an error in your code, Code runs fine; would this be undocumented behaviour? RE: Stack vs. Heap storage - silur - 02-01-2017 it's documented, the code is not that complicated to cause a segfault, you can even write data to a null pointer and have chance to read from it, but actually it'll just write into some random point of the RAM and eventually cause a crash RE: Stack vs. Heap storage - neko.py - 03-27-2017 Wow. This is actually a really interesting topic, and demonstrates one of the nuanced magical differences between an char pointer and a char array. In fact, char* and char[] aren't _exactly_ the same thing, as many people treat them to be. One of those differences is how the sizeof keyword handles them. char[] actually maintains size information along with it. This causes the sizeof operator to evaulate the actual byte size (not length) of the underlying array, whereas the sizeof a char* will always be the length of the pointer, just like how sizeof char will be the size of a char. Let's take a look at some simplified code... Code: #include <stdlib.h> // C standard library
#include <string.h> // string utilities
#include <stdio.h> // IO utilities
// main function
int main(){
// allocate enough heap memory to our pointer for
// a greeting with no name (which is 8 characters)
char* str=(char*) malloc(2*sizeof(char));
str[0] = 'A';
str[1] = '\0';
// declare a *fixed* char array to hold a name; this is pushed
// to the stack automatically, because it has a fixed size
char name[]="Sinisterly";
printf("sizeof str: %u\n", sizeof(str));
printf("strlen str: %u\n", strlen(str));
printf("sizeof name: %u\n", sizeof(name));
printf("strlen name: %u\n", strlen(name));
free(str);
return 0;
}We're allocating a memory region that can contain two characters, and storing the address in the pointer called str. Afterwards, we're placing our string Sinisterly\0 on the stack, and we'll point to it with name. Let's compile with gcc -O0 -ggdb main.c -o main and load it up in your disassembler of choice Code: [0x004004e0]> pdf @ main
;-- main:
/ (fcn) sym.main 183
| sym.main ();
| ; var int local_20h @ rbp-0x20
| ; var int local_18h @ rbp-0x18
| ; var int local_16h @ rbp-0x16
| ; var int local_8h @ rbp-0x8
| ; DATA XREF from 0x004004fd (entry0)
| 0x004005d6 55 push rbp ; sys_errlist.:72
| 0x004005d7 4889e5 mov rbp, rsp
| 0x004005da 4883ec20 sub rsp, 0x20
| 0x004005de bf02000000 mov edi, 2 ; main.c:9 char* str=(char*) malloc(2*sizeof(char)); ; .//main.c:9 // a greeting with no name (which is 8 characters)
| 0x004005e3 e8e8feffff call sym.imp.malloc ; sys_errlist.:410; void *malloc(size_t size);
| 0x004005e8 488945f8 mov qword [rbp - local_8h], rax
| 0x004005ec 488b45f8 mov rax, qword [rbp - local_8h] ; main.c:10 str[0] = 'A'; ; .//main.c:10 char* str=(char*) malloc(2*sizeof(char));
| 0x004005f0 c60041 mov byte [rax], 0x41 ; 'A' ; [0x41:1]=0 ; 'A'
| 0x004005f3 488b45f8 mov rax, qword [rbp - local_8h] ; main.c:11 str[1] = '\0'; ; .//main.c:11 str[0] = 'A';
| 0x004005f7 4883c001 add rax, 1
| 0x004005fb c60000 mov byte [rax], 0
| 0x004005fe 48b853696e69. movabs rax, 0x72657473696e6953 ; main.c:15 char name[]="Sinisterly"; ; .//main.c:15 // to the stack automatically, because it has a fixed size
| 0x00400608 488945e0 mov qword [rbp - local_20h], rax
| 0x0040060c 66c745e86c79 mov word [rbp - local_18h], 0x796c
| 0x00400612 c645ea00 mov byte [rbp - local_16h], 0
| 0x00400616 be08000000 mov esi, 8 ; main.c:17 printf("sizeof str: %u\n", sizeof(str)); ; .//main.c:17
| 0x0040061b bf20074000 mov edi, str.sizeof_str:__u_n ; "sizeof str: %u." @ 0x400720
| 0x00400620 b800000000 mov eax, 0
| 0x00400625 e896feffff call sym.imp.printf
| 0x0040062a 488b45f8 mov rax, qword [rbp - local_8h] ; main.c:18 printf("strlen str: %u\n", strlen(str)); ; .//main.c:18 printf(\"sizeof str: %u\n\", sizeof(str));
| 0x0040062e 4889c7 mov rdi, rax
| 0x00400631 e87afeffff call sym.imp.strlen
| 0x00400636 4889c6 mov rsi, rax
| 0x00400639 bf30074000 mov edi, str.strlen_str:__u_n ; "strlen str: %u." @ 0x400730
| 0x0040063e b800000000 mov eax, 0
| 0x00400643 e878feffff call sym.imp.printf
| 0x00400648 be0b000000 mov esi, 0xb ; main.c:20 printf("sizeof name: %u\n", sizeof(name)); ; .//main.c:20
| 0x0040064d bf40074000 mov edi, str.sizeof_name:__u_n ; "sizeof name: %u." @ 0x400740
| 0x00400652 b800000000 mov eax, 0
| 0x00400657 e864feffff call sym.imp.printf
| 0x0040065c 488d45e0 lea rax, [rbp - local_20h] ; main.c:21 printf("strlen name: %u\n", strlen(name)); ; .//main.c:21 printf(\"sizeof name: %u\n\", sizeof(name));
| 0x00400660 4889c7 mov rdi, rax
| 0x00400663 e848feffff call sym.imp.strlen
| 0x00400668 4889c6 mov rsi, rax
| 0x0040066b bf51074000 mov edi, str.strlen_name:__u_n ; "strlen name: %u." @ 0x400751
| 0x00400670 b800000000 mov eax, 0
| 0x00400675 e846feffff call sym.imp.printf
| 0x0040067a 488b45f8 mov rax, qword [rbp - local_8h] ; main.c:24 free(str); ; .//main.c:24
| 0x0040067e 4889c7 mov rdi, rax
| 0x00400681 e81afeffff call sym.imp.free ; void free(void *ptr);
| 0x00400686 b800000000 mov eax, 0 ; main.c:26 return 0; ; .//main.c:26
| 0x0040068b c9 leave ; main.c:27 } ; .//main.c:27 return 0;
\ 0x0040068c c3 retThere's some really fun stuff going on here. First, lets talk about the sizeof keyword. As we mentioned before, the sizeof keyword runs off static information that's known at compile time. Whats _really_ cool about this, is that it doesnt even assemble as a function call. It produces _literals_ for the two printf calls in which it is used: Code: 0x00400616 be08000000 mov esi, 8 ; main.c:17 printf("sizeof str: %u\n", sizeof(str)); ; .//main.c:17
...
0x00400648 be0b000000 mov esi, 0xb ; main.c:20 printf("sizeof name: %u\n", sizeof(name)); ; .//main.c:20Now, as we mentioned before, we're looking at sizeof a pointer with str, not the size of the array to which it points. We'll notice that i modified OP's code slightly to allocate 2 characters on the heap. And we confirm that we're looking at the 8 bytes of a pointer, not the 2 bytes of a size-two array. Then, since the second data type is a magical char[] and not a pointer, in this case _we do_ print the length, which comes out to be the literal 0xb, or 11 dec. Compiled right into the code, even at O0 ![]() There's some other mega interesting stuff too. Look at how the name array is initialized at 0x004005fe. Most of the string fits in rax, so we move it right on in. Very cool. Notice that in both cases, when we're calling the strlen, we are using pointers. This interchangeability is where most C/C++ get tripped up and treat char* and char[] as the same. And of course, our output is as expected: Code: [neko@catbox bad_realloc]$ ./main
sizeof str: 8
strlen str: 1
sizeof name: 11
strlen name: 10Note that strlen doesnt include the trailing null terminator, hence the results being one-off of whats actually in memory. RE: Stack vs. Heap storage - ClawsMissingBall - 07-03-2017 This is not accurate for a few reasons. The stack is simply a part of the heap. When a program is loaded into memory, everything is done at once (similar to a stack of plates, hence the name). The slot in memory is set aside for the programs memory, instruction sets, ect (in that order btw, fairly important). At the beginning of the instruction set, and offset is given that determines where in memory the program is, and then the ESP register is used to determine where in the program we are currently (fucking Pentium pro). If you want to have some serious fun, the ESP register is actually signed. You can send it into negatives and start accessing instruction sets of other programs. |