This commit is contained in:
Miloslav Ciz 2023-07-12 14:38:55 +02:00
parent 59e56a6987
commit e89960bcfb
12 changed files with 74 additions and 53 deletions

View file

@ -15,7 +15,7 @@ For start let's see which kinds of allocation (and their associated parts of mem
- **static allocation (code/data memory)**: Simplest kind of allocation happening at compile time: if the compiler can do so (i.e. if it knows enough things such as the size of the data in advance), it allocates space of concrete size at some specific address in the part of memory reserved for code or static data (code and data may be in the same or separate parts depending on platform, see e.g. [Harvard architecture](harvard.md)) -- this is straightforward, simple, automatic and poses no real dangers, bloat or burden of dependencies. This kind of allocation applies to:
- **global variables** (variables declared outside any function, i.e. even outside `main`)
- **static variables** (variables inside functions declared with `static` keyword)
- **constants/literals** (i.e. concrete numbers/strings in the source code such as `123` or `"abc"`)
- **constants/literals** (e.g. strings in the source code such as `"abc"`)
- **automatic allocation (stack memory)**: For local variables (variables inside functions) the memory is allocated in a special part of memory known as **call [stack](stack.md)** only at the time when the function is actually called and executed; i.e. this is similar to dynamic allocation (it happens at run time) but happens automatically, without needing any libraries or other explicit actions from the programmer. I.e. when a function is called at run time, a new *call frame* is created on stack which includes space for local variables of that function (along with e.g. return address from the function etc.). This is necessary e.g. to allow [recursion](recursion.md) (during which several instances of the same function may be active, each of which may have different values of its variables), and it also helps consume less RAM. This allows for creating variable sized arrays inside functions (e.g. `int array[x];` where `x` is variable) which is not possible to do with a global array (however variable size arrays aren't supported in old ANSI C!). The disadvantage over dynamic allocation is that stack memory is relatively small and overusing it may easily cause stack [overflow](overflow.md) (running out of memory). Still this kind of allocation is better than dynamic allocation as it doesn't need any libraries, it doesn't generate complex code and the only danger is that of stack overflow -- memory leaks can't happen (deallocation happens automatically when function is exited). Automatic allocation applies to:
- **local variables** (including function arguments and local **variable size arrays**)
- **dynamic allocation (heap memory)**: A kind of more complex manual allocation that happens at run time and is initiated by the programmer calling special functions such as `malloc` from the `stdlib` standard library, which return [pointers](pointer.md) to the allocated memory. This memory is taken from a special part of memory known as **[heap](heap.md)**. This allows to allocate, resize and deallocate potentially very big parts of memory, but requires caution as working with pointers is involved and there is a danger of **memory leaks** -- it is the responsibility of the programmer to free allocated memory with the `free` function once it is no longer needed, otherwise that memory will simply remain allocated and unusable by others (if this happens for example in a loop, the program may just start eating up more and more RAM and eventually run out of memory). Dynamic allocation is also pretty complex (it usually involves communicating with operating system and also keeping track of the structure of memory) and creates a [dependency](dependency.md) on the `stdlib` library. Some implementations of the allocation functions are also infamously slow (up to the point of some programmers resorting to program their own dynamic allocation systems). Therefore only use dynamic allocation when absolutely necessary! Dynamic allocation applies to:
@ -25,9 +25,6 @@ Rule of the thumb: use the simplest thing possible, i.e. static allocation if yo
For [pros](pro.md): you can also create your own kind of pseudo dynamic allocation in pure C if you really want to avoid using stdlib or can't use it for some reason. The idea is to allocate a big chunk of memory statically (e.g. global `unsigned char myHeap[MY_HEAP_SIZE];`) and then create functions for allocating and freeing blocks of this static memory (e.g. `myAlloc` and `myFree` with same signatures as `malloc` and `free`). This allows you to use memory more efficiently than if you just dumbly (is it a word?) preallocate everything statically, i.e. you may need less total memory; this may be useful e.g. on [embedded](embedded.md). Yet another uber [hack](hacking.md) to "improve" this may be to allocate the "personal heap" on the stack instead of statically, i.e. you create something like a global pointer `unsigned char *myHeapPointer;` and a global variable `unsigned int myHeapSize;`, then somewhere at the beginning of `main` you compute the size `myHeapSize` and then create a local array `myHeap[myHeapSize]`, then finally set the global pointer to it as `myHeapPointer = myHeap`; the rest remains the same (your allocation function will access the heap via the global pointer). Just watch out for reinventing wheels, bugs and that you actually don't end up with a worse mess that if you took a more simple approach. Hell, you might even try to write your own garbage collection and array bound checking and whatnot, but then why just not fuck it and use an already existing abomination like [Java](java.md)? :)
Finally let's see some simple code example:
```