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.
17 /* A minimalistic implementation which never frees any memory, save when
18 realloc()'ing a block to a smaller size.
22 size_t allocated_sdl_surface(SDL_Surface
*surface
) {
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"
40 void *xuni_memory_allocate_func(size_t size
, const char *func
) {
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);
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
) {
72 void xuni_memory_add_count(void *data
, int count
) {
76 size_t xuni_memory_get_count(void *data
) {
80 void xuni_memory_free(void *data
) {
84 void xuni_memory_keep_freed_blocks(int keep
) {
88 void xuni_memory_free_all(void) {
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;
105 struct xuni_memory_p_t
{
110 enum xuni_memory_block_type_t type
;
113 struct xuni_memory_t
{
114 struct xuni_memory_p_t
*data
;
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
;
137 void *b
= array
[n
].data
;
140 else if(a
> b
) return 1;
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
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
157 static int memory_find_data(void *data
, size_t *pos
) {
159 return binary_insertion_sort(memory
.data
, memory
.n
, data
, pos
,
160 memory_find_data_compare
);
162 size_t mid
= (size_t)-1, first
= 0, last
= memory
.n
- 1;
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
) {
174 else if(data
> memory
.data
[mid
].data
) {
183 if(data
< memory
.data
[mid
].data
) {
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
) {
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);*/
234 void decrement_allocated(size_t size
) {
235 #ifdef RECORD_MAXIMUMS
240 void increment_allocated(size_t size
) {
241 #ifdef RECORD_MAXIMUMS
243 if(sizenow
> maxsize
) maxsize
= sizenow
;
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
254 static void memory_increase_blocks(void) {
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
));
263 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
264 "Error allocating %lu struct xuni_memory_p_t's",
265 (unsigned long)memory
.alloc
);
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();
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;
286 memory
.data
[pos
].count
= 2;
288 memory
.data
[pos
].func
= func
;
289 memory
.data
[pos
].size
= size
;
290 memory
.data
[pos
].type
= type
;
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
);
301 memmove(memory
.data
+ pos
, memory
.data
+ pos
+ 1,
302 (memory
.n
- pos
- 1) * sizeof(*memory
.data
));
307 /*! Initializes the reference counter. */
308 void xuni_memory_initialize(void) {
309 /* can't do this unless this function is called before allocate_xuni() */
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
);
328 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
329 "Error allocating %lu bytes of memory\n", (unsigned long)size
);
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
;
344 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
345 "Attempting to add already-existing block %p\n", data
);
348 #ifdef RECORD_MAXIMUMS
349 increment_allocated(size
);
353 /*memset(data, 0xff, size);*/
358 /*! Allocates a copy of the string \a data, just like the non-standard
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
364 char *xuni_memory_duplicate_string(const char *data
) {
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
379 \return A pointer to the new memory, initialized with the character data
382 char *xuni_memory_duplicate_string_len(const char *data
, size_t len
) {
383 char *p
= xuni_memory_allocate(len
+ 1);
386 memcpy(p
, data
, len
);
395 char *xuni_memory_insert_string(char *data
, char *insert
, size_t pos
,
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);
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
),
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
);
431 return xuni_memory_duplicate_string(insert
, strlen(insert
));
434 return xuni_memory_duplicate_string(data
, strlen(data
));
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
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
) {
461 #ifdef GARBAGE_COLLECT_ALL
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:"
470 memory_remove_data(pos
);
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
);
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
);
493 p
= realloc(data
, size
);
495 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
496 "Error resizing pre-allocated memory block to %lu bytes",
497 (unsigned long)size
);
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
;
514 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
515 "Adding a block that already exists: %p", p
);
522 return xuni_memory_allocate(size
);
525 return realloc(data
, size
);
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
) {
543 if(memory_find_last_data(data
, &pos
)) {
544 memory
.data
[pos
].count
++;
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
) {
560 if(memory_find_data(data
, &pos
) && memory
.data
[pos
].count
) {
561 memory
.data
[pos
].count
++;
564 #ifdef GARBAGE_COLLECT_ALL
565 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
566 "xuni_memory_increment() passed an invalid pointer: %p", data
);
568 printf("Size of block %p not recorded\n", data
);
569 memory_insert_data(data
, pos
, 0, __func__
);
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
) {
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",
592 else if(!--memory
.data
[pos
].count
) {
593 memory_remove_data(pos
);
597 #ifdef DEBUG_UNALLOCATED_DECREMENT
599 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
600 "xuni_memory_decrement() passed an unallocated pointer: %p",
608 /*void xuni_memory_add_count(void *data, int count) {
613 if(memory_find_data(data, &pos)) {
614 memory.data[pos].count += count;
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) {
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);
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
) {
648 enum xuni_memory_block_type_t type
;
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
667 log_message(ERROR_TYPE_MEMORY
, 0, __FILE__
, __LINE__
,
668 "xuni_memory_free() passed an unallocated pointer: %p", data
);
673 static void xuni_memory_free_block(void *data
,
674 enum xuni_memory_block_type_t type
) {
677 case MEMORY_BLOCK_TYPE_NORMAL
:
680 case MEMORY_BLOCK_TYPE_SDL_SURFACE
:
681 SDL_FreeSurface(data
);
683 case MEMORY_BLOCK_TYPE_LOADSO_OBJECT
:
684 /*xuni_loadso_free_object(data);*/
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
703 void xuni_memory_free_all(void) {
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
);
713 #ifdef DUMP_EXTRA_MEMORY
714 /*printf("Freeing all extra memory:\n");*/
717 for(x
= 0; x
< memory
.n
; x
++) {
718 if(!memory
.data
[x
].count
) continue;
720 #ifdef DUMP_EXTRA_MEMORY
724 if(!strcmp(memory
.data
[x
].func
, "xuni_memory_duplicate_string")) {
725 printf("duplicated string: \"%s\"\n",
726 (char *)memory
.data
[x
].data
);
730 free(memory
.data
[x
].data
);
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) {
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: ");
756 /*static void dump_memory(void) {
759 printf("Memory dump:\n");
760 for(x = 0; x < memory.n; x ++) {