Minor syntactical changes for readability.
[xuni.git] / src / memory.c
blobc2ecca12dfc36f971cecbcbbceb846ca007845f4
1 /*! \file memory.c
3 This source file implements a simple reference counter. It keeps track of
4 the number of variables that are pointing to a block of memory -- the
5 number of shallow copies of a block. Every time a copy is made of the
6 object, this number goes up -- and every time a copy is destroyed, the
7 number goes down. When the number reaches zero, there are no more pointers
8 to the object and it can be safely freed.
9 */
11 #include <stdlib.h>
12 #include <string.h>
13 #include "memory.h"
14 #include "error.h"
15 #include "utility.h"
17 /* A minimalistic implementation which never frees any memory, save when
18 realloc()'ing a block to a smaller size.
20 #if !1
22 size_t allocated_sdl_surface(SDL_Surface *surface) {
23 return 0;
26 void decrement_allocated(size_t size) {
30 void increment_allocated(size_t size) {
34 void xuni_memory_initialize(void) {
35 fprintf(stderr, "Warning: compiled with minimalistic reference counter"
36 " that never frees any\nmemory. Memory consumption will increase"
37 " very quickly.");
40 void *xuni_memory_allocate_func(size_t size, const char *func) {
41 return malloc(size);
44 char *xuni_memory_duplicate_string(const char *data) {
45 return xuni_memory_duplicate_string_len(data, strlen(data));
48 char *xuni_memory_duplicate_string_len(const char *data, size_t len) {
49 char *p = malloc(len + 1);
50 memcpy(p, data, len);
51 p[len] = 0;
52 return p;
55 void *xuni_memory_resize_func(void *data, size_t size, const char *func) {
56 return realloc(data, size);
59 void xuni_memory_add_block_func(void *data, const char *func,
60 enum xuni_memory_block_type_t type) {
64 void xuni_memory_increment(void *data) {
68 void *xuni_memory_decrement(void *data) {
69 return 0;
72 void xuni_memory_add_count(void *data, int count) {
76 size_t xuni_memory_get_count(void *data) {
77 return (size_t)-1;
80 void xuni_memory_free(void *data) {
84 void xuni_memory_keep_freed_blocks(int keep) {
88 void xuni_memory_free_all(void) {
92 #else
94 #define GARBAGE_COLLECT_ALL
95 /*#define RECORD_MAXIMUMS*/
96 #define DEBUG_UNALLOCATED_DECREMENT
97 #define DUMP_EXTRA_MEMORY
99 #ifdef RECORD_MAXIMUMS
100 static size_t maxcount = 0;
101 static size_t sizenow = 0;
102 static size_t maxsize = 0;
103 #endif
105 struct xuni_memory_p_t {
106 void *data;
107 size_t count;
108 const char *func;
109 size_t size;
110 enum xuni_memory_block_type_t type;
113 struct xuni_memory_t {
114 struct xuni_memory_p_t *data;
115 size_t alloc, n;
116 int keepfreed;
119 /*! List of all dynamically allocated blocks of memory. */
120 static struct xuni_memory_t memory = {0, 0, 0, 0};
122 static int memory_find_data_compare(void *data, size_t n, void *find);
123 static int memory_find_data(void *data, size_t *pos);
124 static int memory_find_last_data(void *data, size_t *pos);
125 static void memory_increase_blocks(void);
126 static void memory_insert_data(void *data, size_t pos, size_t size,
127 const char *func, enum xuni_memory_block_type_t type);
128 static void memory_remove_data(size_t pos);
129 static void xuni_memory_free_block(void *data,
130 enum xuni_memory_block_type_t type);
132 static void print_block(size_t x);
134 static int memory_find_data_compare(void *data, size_t n, void *find) {
135 struct xuni_memory_p_t *array = data;
136 void *a = find;
137 void *b = array[n].data;
139 if(a < b) return -1;
140 else if(a > b) return 1;
142 return 0;
145 /*! Finds the position in which \a data is located in the memory structure,
146 or, if it is not present, the "closest" place. The closest place is the
147 position of the last element that is smaller than \a data. It is used as a
148 parameter to memory_insert_data().
150 \param data The memory block to search for.
151 \param pos Set to the actual or closest position of \a data within the
152 memory structure.
153 \return True if \a data is actually present in the memory structure, false
154 otherwise. Note that if \a data is not present, \a pos will still be
155 set.
157 static int memory_find_data(void *data, size_t *pos) {
158 #if 1
159 return binary_insertion_sort(memory.data, memory.n, data, pos,
160 memory_find_data_compare);
161 #else
162 size_t mid = (size_t)-1, first = 0, last = memory.n - 1;
164 *pos = 0;
166 if(!memory.n) return 0;
168 while(first <= last && last != (size_t)-1) {
169 mid = (first + last) / 2;
171 if(data < memory.data[mid].data) {
172 last = mid - 1;
174 else if(data > memory.data[mid].data) {
175 first = mid + 1;
177 else {
178 *pos = mid;
179 return 1;
183 if(data < memory.data[mid].data) {
184 *pos = mid;
186 else {
187 *pos = mid + 1;
190 return 0;
191 #endif
194 static int memory_find_last_data(void *data, size_t *pos) {
195 /* first check for the most common case: that data is the last index */
196 if(memory.n && data > memory.data[memory.n - 1].data) {
197 *pos = memory.n;
198 return 0;
201 return memory_find_data(data, pos);
204 size_t allocated_sdl_surface(SDL_Surface *surface) {
205 #ifdef RECORD_MAXIMUMS
206 size_t size = sizeof(*surface);
208 if(!surface) return size;
210 if(surface->format) {
211 size += sizeof(*surface->format);
212 if(surface->format->palette) {
213 size += sizeof(*surface->format->palette);
214 size += sizeof(*surface->format->palette->colors) *
215 surface->format->palette->ncolors;
219 if(surface->pixels) {
220 size += surface->h * surface->pitch
221 + surface->w * surface->format->BytesPerPixel;
224 /* surface->hwdata and surface->map not counted properly */
225 /*if(surface->hwdata) size += sizeof(*surface->hwdata);
226 if(surface->map) size += sizeof(*surface->map);*/
228 return size;
229 #else
230 return 0;
231 #endif
234 void decrement_allocated(size_t size) {
235 #ifdef RECORD_MAXIMUMS
236 sizenow -= size;
237 #endif
240 void increment_allocated(size_t size) {
241 #ifdef RECORD_MAXIMUMS
242 sizenow += size;
243 if(sizenow > maxsize) maxsize = sizenow;
244 #endif
247 /*! Increases the memory allocated for the memory structure. Called when a new
248 block needs to be added, but all of the allocated memory has been used up.
250 Doubles the currently allocated memory, which is wasteful for space but
251 much more efficient when many memory blocks are involved, which is usually
252 the case.
254 static void memory_increase_blocks(void) {
255 void *p;
257 if(memory.alloc == memory.n) {
258 if(!memory.alloc) memory.alloc = 1;
259 else memory.alloc *= 2;
261 p = realloc(memory.data, memory.alloc * sizeof(*memory.data));
262 if(!p) {
263 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
264 "Error allocating %lu struct xuni_memory_p_t's",
265 (unsigned long)memory.alloc);
266 return;
268 memory.data = p;
272 static void memory_insert_data(void *data, size_t pos, size_t size,
273 const char *func, enum xuni_memory_block_type_t type) {
275 memory_increase_blocks();
277 if(pos < memory.n) {
278 memmove(memory.data + pos + 1, memory.data + pos,
279 (memory.n - pos) * sizeof(*memory.data));
282 memory.data[pos].data = data;
283 #ifdef GARBAGE_COLLECT_ALL
284 memory.data[pos].count = 1;
285 #else
286 memory.data[pos].count = 2;
287 #endif
288 memory.data[pos].func = func;
289 memory.data[pos].size = size;
290 memory.data[pos].type = type;
292 memory.n ++;
295 static void memory_remove_data(size_t pos) {
296 #ifdef RECORD_MAXIMUMS
297 if(memory.n > maxcount) maxcount = memory.n;
298 decrement_allocated(memory.data[pos].size);
299 #endif
301 memmove(memory.data + pos, memory.data + pos + 1,
302 (memory.n - pos - 1) * sizeof(*memory.data));
304 memory.n --;
307 /*! Initializes the reference counter. */
308 void xuni_memory_initialize(void) {
309 /* can't do this unless this function is called before allocate_xuni() */
310 /*memory.data = 0;
311 memory.alloc = 0;
312 memory.n = 0;
313 memory.keepfreed = 1;*/
316 /*! Allocates a chunk of memory of \a size bytes, storing the pointer in the
317 list \c memory and assuming one reference to the block.
319 Replacement for \c malloc().
321 \param size The requested bytes of memory to allocate.
323 void *xuni_memory_allocate_func(size_t size, const char *func) {
324 void *data = malloc(size);
325 size_t pos;
327 if(!data) {
328 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
329 "Error allocating %lu bytes of memory\n", (unsigned long)size);
331 else {
332 #ifdef GARBAGE_COLLECT_ALL
333 if(!memory_find_last_data(data, &pos)) {
334 memory_insert_data(data, pos, size, func,
335 MEMORY_BLOCK_TYPE_NORMAL);
337 else if(!memory.data[pos].count) {
338 memory.data[pos].count ++;
339 memory.data[pos].type = MEMORY_BLOCK_TYPE_NORMAL;
340 memory.data[pos].size = size;
341 memory.data[pos].func = func;
343 else {
344 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
345 "Attempting to add already-existing block %p\n", data);
347 #endif
348 #ifdef RECORD_MAXIMUMS
349 increment_allocated(size);
350 #endif
353 /*memset(data, 0xff, size);*/
355 return data;
358 /*! Allocates a copy of the string \a data, just like the non-standard
359 function strdup().
360 \param data The string to make a dynamically allocated copy of.
361 \return A pointer to the new memory, initialized with the character data
362 in \a data.
364 char *xuni_memory_duplicate_string(const char *data) {
365 if(!data) return 0;
367 return xuni_memory_duplicate_string_len(data, strlen(data));
370 /*! Allocates \c len+1 bytes, copies \a data to this new memory, and returns
371 a pointer to the new memory.
373 This would be sort of like strndup(), if it existed.
375 \param data The character data to use. This need not be NULL-terminated.
376 \param len The number of characters to copy from \a data. A NULL is added
377 regardless, so that \c data[len] does not necessarily have to be
378 '\\0'.
379 \return A pointer to the new memory, initialized with the character data
380 in \a data.
382 char *xuni_memory_duplicate_string_len(const char *data, size_t len) {
383 char *p = xuni_memory_allocate(len + 1);
385 if(data && len) {
386 memcpy(p, data, len);
387 p[len] = 0;
389 else *p = 0;
391 return p;
394 #if 0
395 char *xuni_memory_insert_string(char *data, char *insert, size_t pos,
396 size_t len) {
398 size_t datalen, x;
399 char *p;
400 int usedata = data && *data;
401 int useinsert = insert && *insert && len;
403 printf(" xuni_memory_insert_string(\"%s\", \"%s\", %i, %i)\n",
404 data, insert, (int)pos, (int)len);
406 if(!usedata && !useinsert) return 0;
407 else if(usedata && useinsert) {
408 datalen = strlen(data);
410 p = xuni_memory_resize(data, datalen + len + 1);
411 if(!p) return data;
412 data = p;
414 /*strcpy(data, insert);*/
416 printf(" pos:%i datalen:%i len:%i\n", (int)pos,
417 (int)datalen, (int)len);
419 printf(" [%i->%i] '%c'\n", (int)pos, (int)(pos + len),
420 data[pos]);
421 memmove(data + 1, data, datalen + 1);
423 /*memmove(data + pos + len, data + pos, datalen - pos + 1);
424 memcpy(data + pos, insert, len);*/
426 printf(" -> \"%s\"\n", data);
428 return data;
430 else if(!usedata) {
431 return xuni_memory_duplicate_string(insert, strlen(insert));
433 else {
434 return xuni_memory_duplicate_string(data, strlen(data));
437 #endif
439 /*! Resizes the amount of dynamically allocated memory reserved for \a data
440 to \a size bytes, returning the pointer to the new memory. This pointer
441 may, but not necessarily, be the same value as the original \a data. If
442 it is a different value, the original data in \a data is copied over to
443 the new location.
445 When \a data is NULL, this function acts the same way as
446 \c xuni_memory_allocate(). Only works on memory blocks of type
447 MEMORY_BLOCK_TYPE_NORMAL.
449 Replacement for \c realloc().
451 \param data The block of memory to resize, or NULL if no block was
452 previously allocated.
453 \param size The number of bytes that \a data should be resized to point
455 \return The newly resized block of memory, possibly equal to \a data.
457 void *xuni_memory_resize_func(void *data, size_t size, const char *func) {
458 size_t pos;
459 void *p;
461 #ifdef GARBAGE_COLLECT_ALL
462 if(!size) {
463 if(memory_find_data(data, &pos)) {
464 if(!memory.data[pos].count) {
465 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
466 "Can't resize to zero a block with no remaining pointers:"
467 " %p", data);
469 else {
470 memory_remove_data(pos);
474 free(data);
476 return 0;
479 if(data && memory_find_data(data, &pos)) {
480 if(memory.data[pos].type != MEMORY_BLOCK_TYPE_NORMAL) {
481 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
482 "Can only resize normal memory blocks, not ones of type %lu",
483 (unsigned long)memory.data[pos].type);
484 return data;
487 if(!memory.data[pos].count) {
488 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
489 "Can't resize a block with no remaining pointers: %p", data);
490 return data;
493 p = realloc(data, size);
494 if(!p) {
495 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
496 "Error resizing pre-allocated memory block to %lu bytes",
497 (unsigned long)size);
498 return data;
501 if(p != data) {
502 memory_remove_data(pos);
503 if(!memory_find_last_data(p, &pos)) {
504 memory_insert_data(p, pos, size, func,
505 MEMORY_BLOCK_TYPE_NORMAL);
507 else if(!memory.data[pos].count) {
508 memory.data[pos].count ++;
509 memory.data[pos].type = MEMORY_BLOCK_TYPE_NORMAL;
510 memory.data[pos].size = size;
511 memory.data[pos].func = func;
513 else {
514 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
515 "Adding a block that already exists: %p", p);
519 return p;
521 else {
522 return xuni_memory_allocate(size);
524 #else
525 return realloc(data, size);
526 #endif
529 /*! Adds a reference to a block of memory allocated by some other allocation
530 function instead of xuni_memory_allocate() -- e.g.,
531 SDL_CreateRGBSurface().
532 \param data The pointer to add to the list of allocated blocks.
533 \param type The type of the pointer \a data which will be added to the
534 list of memory blocks.
536 void xuni_memory_add_block_func(void *data, const char *func,
537 enum xuni_memory_block_type_t type) {
539 size_t pos;
541 if(!data) return;
543 if(memory_find_last_data(data, &pos)) {
544 memory.data[pos].count ++;
546 else {
547 memory_insert_data(data, pos, 0, func, type);
551 /*! Increments the reference count for the pointer \a data, which points to a
552 previously (dynamically) allocated block of memory.
553 \param data The pointer for which to increase the reference count.
555 void xuni_memory_increment(void *data) {
556 size_t pos;
558 if(!data) return;
560 if(memory_find_data(data, &pos) && memory.data[pos].count) {
561 memory.data[pos].count ++;
563 else {
564 #ifdef GARBAGE_COLLECT_ALL
565 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
566 "xuni_memory_increment() passed an invalid pointer: %p", data);
567 #else
568 printf("Size of block %p not recorded\n", data);
569 memory_insert_data(data, pos, 0, __func__);
570 #endif
574 /*! Decrements the reference counter for the pointer \a data.
576 Low-level replacement for functions like free() and SDL_FreeSurface().
578 \param data The pointer to decrement the reference count for.
579 \return \a data if it needs to be freed, or NULL if it doesn't.
581 void *xuni_memory_decrement(void *data) {
582 size_t pos;
584 if(!data) return 0;
586 if(memory_find_data(data, &pos)) {
587 if(!memory.data[pos].count) {
588 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
589 "Trying to decrement a block with no pointers to it: %p",
590 data);
592 else if(!--memory.data[pos].count) {
593 memory_remove_data(pos);
594 return data;
597 #ifdef DEBUG_UNALLOCATED_DECREMENT
598 else {
599 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
600 "xuni_memory_decrement() passed an unallocated pointer: %p",
601 data);
603 #endif
605 return 0;
608 /*void xuni_memory_add_count(void *data, int count) {
609 size_t pos;
611 if(!data) return;
613 if(memory_find_data(data, &pos)) {
614 memory.data[pos].count += count;
616 else {
617 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
618 "xuni_memory_add_count() passed an invalid pointer: %p", data);
622 size_t xuni_memory_get_count(void *data) {
623 size_t pos;
625 if(!data) return (size_t)-1;
627 if(memory_find_data(data, &pos)) {
628 return memory.data[pos].count;
631 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
632 "xuni_memory_get_count() passed an invalid pointer: %p", data);
634 return (size_t)-1;
637 /*! Decrements the reference counter for the pointer \a data, freeing the data
638 pointed to by that same pointer with the correct free function if
639 necessary. The "correct free function" is determined from the type of
640 block, which was assigned when the block was allocated.
642 Replacement for free() and SDL_FreeSurface() etc.
644 \param data The pointer to decrement the reference count for.
646 void xuni_memory_free(void *data) {
647 size_t pos;
648 enum xuni_memory_block_type_t type;
650 if(!data) return;
652 if(memory_find_data(data, &pos)) {
653 if(!memory.data[pos].count) {
654 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
655 "Memory block freed too many times: %p", data);
657 else if(!--memory.data[pos].count) {
658 type = memory.data[pos].type;
660 xuni_memory_free_block(data, type);
662 if(!memory.keepfreed) memory_remove_data(pos);
665 #ifdef DEBUG_UNALLOCATED_DECREMENT
666 else {
667 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
668 "xuni_memory_free() passed an unallocated pointer: %p", data);
670 #endif
673 static void xuni_memory_free_block(void *data,
674 enum xuni_memory_block_type_t type) {
676 switch(type) {
677 case MEMORY_BLOCK_TYPE_NORMAL:
678 free(data);
679 break;
680 case MEMORY_BLOCK_TYPE_SDL_SURFACE:
681 SDL_FreeSurface(data);
682 break;
683 case MEMORY_BLOCK_TYPE_LOADSO_OBJECT:
684 /*xuni_loadso_free_object(data);*/
685 break;
686 default:
687 log_message(ERROR_TYPE_MEMORY, 0, __FILE__, __LINE__,
688 "Trying to free unknown memory block type: %lu",
689 (unsigned long)type);
693 void xuni_memory_keep_freed_blocks(int keep) {
694 memory.keepfreed = keep;
697 /*! Frees all memory still allocated that has been recorded by the reference
698 counter. Should probably only be called once, at the program's exit.
700 Other freeing functions like free_xuni() could make this function
701 unnecessary.
703 void xuni_memory_free_all(void) {
704 size_t x;
706 #ifdef RECORD_MAXIMUMS
707 printf("*** Maximum blocks of memory allocated: %lu\n",
708 (unsigned long)maxcount);
709 printf("*** Maximum amount of memory allocated: %lu\n",
710 (unsigned long)maxsize);
711 #endif
713 #ifdef DUMP_EXTRA_MEMORY
714 /*printf("Freeing all extra memory:\n");*/
715 #endif
717 for(x = 0; x < memory.n; x ++) {
718 if(!memory.data[x].count) continue;
720 #ifdef DUMP_EXTRA_MEMORY
721 printf("Freeing ");
722 print_block(x);
724 if(!strcmp(memory.data[x].func, "xuni_memory_duplicate_string")) {
725 printf("duplicated string: \"%s\"\n",
726 (char *)memory.data[x].data);
728 #endif
730 free(memory.data[x].data);
733 free(memory.data);
734 memory.n = 0;
737 static void print_block(size_t x) {
738 printf("([%lu], %p, #%lu, \"%s\", %lu bytes)\n", (unsigned long)x,
739 memory.data[x].data, (unsigned long)memory.data[x].count,
740 memory.data[x].func, (unsigned long)memory.data[x].size);
743 /*static void check_memory(void) {
744 size_t x;
746 for(x = 0; x + 1 < memory.n; x ++) {
747 if(memory.data[x].data >= memory.data[x+1].data) {
748 printf("*** Out of order: ");
749 print_block(x);
750 printf(" and ");
751 print_block(x+1);
756 /*static void dump_memory(void) {
757 size_t x;
759 printf("Memory dump:\n");
760 for(x = 0; x < memory.n; x ++) {
761 print_block(x);
765 #endif