123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- /* Memory handling, malloc wrapers, etc */
- #include <stdlib.h>
- #include <string.h>
- #include <stdio.h>
- #include "mem.h"
- /* Calls exit(EXIT_FAILURE) if ptr is NULL. Is message is not null, prints this on stderr. */
- void * exit_if_null(void * ptr, char * message) {
- if(!ptr) {
- if(message) {
- fprintf(stderr, "%s\n", message);
- }
- exit(EXIT_FAILURE);
- }
- return ptr;
- }
- #define EXIT_ON_NOMEMORY(PTR) exit_if_null(PTR, "Out of memory!")
- /** Wrapper for calloc/malloc */
- void * mem_calloc(size_t amount, size_t size) {
- void * ptr = calloc(amount, size);
- return EXIT_ON_NOMEMORY(ptr);
- }
- /** Wrapper for calloc/malloc */
- void * mem_alloc(size_t size) {
- void * ptr = mem_calloc(1, size);
- return EXIT_ON_NOMEMORY(ptr);
- }
- /** Wrapper for free */
- void * mem_free(void * ptr) {
- free(ptr);
- return NULL;
- }
- /** Wrapper for realloc */
- void * mem_realloc(void *ptr, size_t newsize) {
- void * aid = realloc(ptr, newsize);
- return EXIT_ON_NOMEMORY(aid);
- }
- /** Resizes memory, taking care not to lose ptr* if the reallocation
- fails. */
- void * mem_resize(void ** ptr, size_t newsize) {
- if(*ptr) {
- void * res = mem_realloc((*ptr), newsize);
- if(res) {
- (*ptr) = res;
- return res;
- }
- }
- return NULL;
- }
- /** Wrapper for memmove, for consistency */
- void * mem_move(void * dest, void * src, size_t size) {
- return memmove(dest , src, size);
- }
- /* Thoughts on hierarchical data structures.
- * Often, in C data is allocatedin a hierarchical way.
- * It's useful to have functions that take this into consideration...
- */
- typedef struct Memtree_ Memtree;
- struct Memtree_ {
- void * memory;
- size_t size;
- Memtree * next, * parent, * child;
- };
- #ifdef COMMENT_
- /* Tree memory allocator. */
- void * mem_tree_alloc(void * vparent, size_t size) {
- char * aid;
- Memtree * me, * next;
- Memtree * parent = vparent;
- aid = mem_calloc(1, size + sizeof(Memtree));
- if (!aid) return NULL;
- me = (Memtree *) aid;
- me->memory = aid + sizeof(Memtree);
- me->size = size;
- if (parent) {
- me->parent = parent;
- if (parent->child) {
- next = parent->child;
- } else {
- next = NULL
- }
-
- parent->child = me;
- me->next = next;
- } else {
- me->parent = NULL;
- me->next = NULL;
- me->child = NULL;
- }
- return me->memory;
- }
- void mem_tree_free(void * vparent) {
- char * aid;
- Memtree * me, * next;
- Memtree * parent = vparent;
- aid = vparent;
- if (!aid) return;
- me = (Memtree *) (aid + sizeof(Memtree));
- if (!me) return;
- me->memory = NULL;
- me->size = 0;
- if (me->child) {
- mem_tree_free(me->child);
- } else if (me->next) {
- for (next = me->next; next; next = next->next) {
-
- mem_tree_free(next);
- }
- } else {
- next = NULL
- }
-
- mem_free(me);
- }
- #endif
- /*
- * Thoughts on memory allocation in C.
- *
- * In theory, memory allocations could just use malloc an free and
- * that would be enough, however, in practise there are several factors that
- * mean that just calling free() on any pointer won't free all allocated
- * memory and resources associated to it. In general, when a pointer to
- * a resource needs to be cleaned up, there are 3 concens to take care of:
- *
- * 1) Cleaning up of non-memory resources like OS handles, API haandles,
- * FILEs, etc. that must be closed or cleaned up before the pointer/struct
- * that refers to them may be deallocated. In general, this could be
- * called deinitialization.
- *
- * 2) The second problem is that the pointer may point to a complex data
- * structure that may have been allocated my several mallocs. In this case
- * multiple frees are needed. This could be called deconstruction.
- *
- * 3) Finally the pointer will usually point to allocated memory that must
- * be deallocated. For this, free could suffice, but not in all cases.
- * This could be deallocation.
- *
- * These 3 steps are of course mirrored when the resource is created, as can
- * be seen in the diagram below.
- *
- * step state of resource
- *
- * Before creation: : VOID
- *
- * /- allocation : ALLOCATED
- * Creation {-- construction : CONSTRUCTED
- * \- initialization : INITIALIZED
- *
- * Use of the resource....
- *
- * /- deinitialization : CONSTRUCTED
- * Destruction {-- deconstruction : ALLLOCATED
- * \- deallocation : VOID
- *
- * It should be noticed that with proper care an object that has not been
- * intialized, or that merely has been deinitialized (i.e. in the CONSTRUCTED
- * state) could be used or reused simply by initializing or reinitializing it.
- * It could be useful for performance or practical reasons or to enable such
- * recycling of already allocated and constructed resources.
- *
- * On the other hand a resource that has been deallocated or not allocated
- * cannnot be used correctly at all. What then about a resource that has not
- * been constructed yet or one that has already been deconstructed? The barrier
- * is more fluid and uncrear here. However, in many progaming languages, these
- * 3 steps get simplified ito 2 or even 1 step. However, experience shows that
- * having only one step is too gross, especially in the face of reference
- * counting or on-GC deinitialization. Therefore, it's best to have 2
- * steps on construction and 2 steps on deconstruction namely
- *
- * construction by XXX_new : XXX_build XXX_init.
- * destruction by XXX_free : XXX_done XXX_demolish
- *
- * The rule is that XXX_done and XXX_init should be complementary,
- * any resource RES that has XXX_init(XXX_done(RES), params...) called
- * will be usable again. Furthemore XXX_done should be idempotent,
- * that is XXX_done(XXX_done(RES)), etc is allowed.
- * XXX_init need not be idempotent as long as it can undo XXX_done correctly.
- * XXX_free is guaranteed to call XXX_done and XXX_demolish.
- * XXX_demolish must not neccesarily be exposed on the public API or
- * even exist.
- * XXX_new calls XXX_build and XXX_init in turn.
- *
- *
- * The problem remains then : how to know which destructor to call?
- * In case it's known at compile-time which other destructors will be needed
- * it's simple. However, some containers may want to store objects whose type
- * is only known at run time. For them it is neccesary to either not accept
- * ownership and let some other routine deallocate them) which is
- * bothersome, certainy for, say, hash tables, or to properly devise a system
- * where the correct destructors can be passed at run time.
- *
- * And then we arrive at interfaces that use and/or consist of virtual tables,
- * that is tablew with function pointers. For the destructor a single pointer
- * could suffice, but while we're going virtual, allowing interface/class like
- * tables will be better since it alllows more generality and saves space.
- *
- *
- * Another point is what to do if memory allocation fails. While for a critical
- * application, it woule make sense to try to free ups some space and try
- * again, for non-critical ones like Eruta, the best thing to do is to
- * simply end the program, since there's nothing sensible that could be done,
- * apart from trying to restore the sceen to it's previous resolution if possible.
- *
- *
- */
- /** Thoughts on memory management and garbage collection.
- * The probelm in C is that it is not always clear who is the owner
- * for an object, and and who is just using a reference.
- * If ownership is well defined, then correct cleanup is easier to arrange.
- *
- * Here's an idea on reference linking for shared ownership:
- */
- typedef struct Memref_ Memref;
- struct Memref_ {
- void * pointer;
- Memref * next;
- Memref * previous;
- };
- Memref memref(void * pointer) {
- Memref ref = { pointer, 0, 0 };
- return ref;
- }
|