As a small project to refresh my C and data-structure knowledge, I decided to write a generic ArrayList
(as in Java). This is my most current version:
ArrayList.h:
#ifndef ARRAYLIST_H #define ARRAYLIST_H struct ArrayList; // ArrayList_create: {{{ // Creates a new ArrayList in memory and returns a pointer to it. // // @param element_size The size of a single element to be stored in the // ArrayList // @require element_size != 0 // // @return A pointer to the created ArrayList. // NULL if something went wrong with the allocation }}} struct ArrayList *ArrayList_create(size_t element_size); // ArrayList_destroy: {{{ // Takes a pointer to an ArrayList and destroys it and its associated data. // All data pointers previously acquired through ArrayList_get become invalid. // // @param list The ArrayList to destroy }}} void ArrayList_destroy(struct ArrayList *list); // ArrayList_getSize: {{{ // Get the current number of elements stored in @param list. // // @param list ArrayList to get the size from // @require list != NULL // // @return The number of elements currently stored in @param list }}} size_t ArrayList_getSize(const struct ArrayList *list); // ArrayList_isEmpty: {{{ // Returns whether ArrayList @param list is empty. // // @param list ArrayList to check emptiness on // @require list != NULL // // @return Non-zero when empty, zero otherwise }}} int ArrayList_isEmpty(const struct ArrayList *list); // ArrayList_trimToSize: {{{ // Trims the capacity of @param list to be the list's current size. // // @param list ArrayList to trim capacity from // @require list != NULL // // @return Non-zero on successful trim, zero otherwise }}} int ArrayList_trimToSize(struct ArrayList *list); // ArrayList_append: {{{ // Appends @param element to @param list. The element gets copied. // // @param list ArrayList to append @param element to // @require list != NULL // @param element Element to append @param list // @require list != NULL // // @return Non-zero on successful append, zero otherwise }}} int ArrayList_append(struct ArrayList *list, const void *element); // ArrayList_add: {{{ // Adds element to the specific index of ArrayList. // // @param list ArrayList to add @param element to // @require list != NULL // @param index Index to place @param element in @param list // @require index <= ArrayList_getSize(list) // @param element The element to add // @require element != NULL // // @return Non-zero on successful add, zero otherwise }}} int ArrayList_add(struct ArrayList *list, size_t index, const void *element); // ArrayList_remove: {{{ // Removes the element stored at specific index from ArrayList. // // @param list ArrayList to remove @param index from // @require list != NULL // @param index Index to remove from @param list // @require index < ArrayList_getSize(list) // // @return Non-zero on successful remove, zero otherwise }}} int ArrayList_remove(struct ArrayList *list, size_t index); // ArrayList_getElement: {{{ // Get pointer to element stored in ArrayList. // // @param list ArrayList to get element from // @param list != NULL // @param index Index of stored element in @param list // @param index < ArrayList_getSize(list) // // @return Pointer to element at given index in ArrayList }}} void *ArrayList_get(const struct ArrayList *list, size_t index); #endif /* ARRAYLIST_H */
ArrayList.c:
#include <stdlib.h> #include <string.h> #include <assert.h> #include "ArrayList.h" struct ArrayList { size_t elem_size; // Size of single element to be stored. size_t size; // Number of currently stored elements. size_t capacity; // Number of elements fitting into allocated memory. char *data; // Pointer to allocated memory. }; #define INITIAL_CAPACITY 10 struct ArrayList *ArrayList_create(size_t element_size) { assert(element_size != 0); struct ArrayList *list = calloc(1, sizeof(struct ArrayList)); if (!list) { return NULL; } list->data = calloc(INITIAL_CAPACITY, element_size); if (!list->data) { free(list); return NULL; } list->capacity = INITIAL_CAPACITY; list->elem_size = element_size; return list; } void ArrayList_destroy(struct ArrayList *list) { assert(list != NULL); free(list->data); free(list); } size_t ArrayList_getSize(const struct ArrayList *list) { assert(list != NULL); return list->size; } int ArrayList_isEmpty(const struct ArrayList *list) { assert(list != NULL); return list->size == 0; } int ArrayList_trimToSize(struct ArrayList *list) { assert(list != NULL); // No overflow check, since size <= capacity char * const data = realloc(list->data, list->size * list->elem_size); if (!data) { return 0; } list->data = data; list->capacity = list->size; return 1; } static int ArrayList_ensureCapacity(struct ArrayList *list, size_t capacity) { assert(list != NULL); while(list->capacity < capacity) { // Increase by factor 1.5 const size_t new_capacity = list->capacity + (list->capacity >> 1); // Check for addition overflow (well defined) // and multiplication overflow (prevent) if (new_capacity < list->capacity || SIZE_MAX / list->elem_size < new_capacity) { return 0; } char * const data = realloc(list->data, new_capacity * list->elem_size); if (!data) { return 0; } list->data = data; list->capacity = new_capacity; } return 1; } int ArrayList_append(struct ArrayList *list, const void *element) { assert(list != NULL); assert(element != NULL); return ArrayList_add(list, list->size, element); } int ArrayList_add(struct ArrayList *list, size_t index, const void *element) { assert(list != NULL); assert(index <= list->size); assert(element != NULL); if (index > list->size || !ArrayList_ensureCapacity(list, list->size + 1)) { return 0; } char * const elementDest = list->data + index * list->elem_size; char * const moveDest = elementDest + list->elem_size; // Valid call (even for list->size - index == 0 - case of append) memmove(moveDest, elementDest, list->elem_size * (list->size - index)); memcpy(elementDest, element, list->elem_size); list->size++; return 1; } int ArrayList_remove(struct ArrayList *list, size_t index) { assert(list != NULL); assert(index < list->size); char *const dest = list->data + index * list->elem_size; char *const source = list->data + (index + 1) * list->elem_size; size_t len = list->size - (index + 1); memmove(dest, source, len); list->size--; return 1; } void *ArrayList_get(const struct ArrayList *list, size_t index) { assert(list != NULL); assert(index < list->size); return list->data + (index * list->elem_size); }
I wrote a small unit test for the most basic functionality as well:
ArrayListTest.h:
#include <inttypes.h> #include <assert.h> #include <stdio.h> #include "ArrayList.h" void ArrayListTest_initialState(void); void ArrayListTest_changeSize(void); void ArrayListTest_AddAppendRemove(void); // ArrayListTest_initialState() {{{ void ArrayListTest_initialState() { struct ArrayList *list = ArrayList_create(sizeof(uint32_t)); if (list) { assert(ArrayList_getSize(list) == 0); assert(ArrayList_isEmpty(list)); ArrayList_destroy(list); } else { printf("Warning: ArrayList in %s couldn't get created\n", "ArrayListTest_initialState()"); } } // }}} // ArrayListTest_changeSize() {{{ void ArrayListTest_changeSize() { struct ArrayList *list = ArrayList_create(sizeof(uint32_t)); if (list) { assert(ArrayList_getSize(list) == 0); assert(ArrayList_isEmpty(list)); uint32_t element = 0; ArrayList_add(list, 0, &element); assert(ArrayList_getSize(list) == 1); assert(!ArrayList_isEmpty(list)); ArrayList_add(list, 0, &element); assert(ArrayList_getSize(list) == 2); assert(!ArrayList_isEmpty(list)); ArrayList_append(list, &element); assert(ArrayList_getSize(list) == 3); assert(!ArrayList_isEmpty(list)); ArrayList_remove(list, 2); assert(ArrayList_getSize(list) == 2); assert(!ArrayList_isEmpty(list)); ArrayList_remove(list, 0); assert(ArrayList_getSize(list) == 1); assert(!ArrayList_isEmpty(list)); ArrayList_remove(list, 0); assert(ArrayList_getSize(list) == 0); assert(ArrayList_isEmpty(list)); ArrayList_destroy(list); } else { printf("Warning: ArrayList in %s couldn't get created\n", "ArrayListTest_changeSize()"); } } // }}} // ArrayListTest_AddAppendRemove() {{{ void ArrayListTest_AddAppendRemove() { struct ArrayList *list = ArrayList_create(sizeof(uint32_t)); if (list) { uint32_t element; element = 0; ArrayList_add(list, 0, &element); assert(*(uint32_t *)ArrayList_get(list, 0) == 0); element = 10; ArrayList_append(list, &element); assert(*(uint32_t *)ArrayList_get(list, 0) == 0); assert(*(uint32_t *)ArrayList_get(list, 1) == 10); element = 12; ArrayList_add(list, 1, &element); assert(*(uint32_t *)ArrayList_get(list, 0) == 0); assert(*(uint32_t *)ArrayList_get(list, 1) == 12); assert(*(uint32_t *)ArrayList_get(list, 2) == 10); ArrayList_remove(list, 1); assert(*(uint32_t *)ArrayList_get(list, 0) == 0); assert(*(uint32_t *)ArrayList_get(list, 1) == 10); ArrayList_remove(list, 0); assert(*(uint32_t *)ArrayList_get(list, 0) == 10); ArrayList_destroy(list); } else { printf("Warning: ArrayList in %s couldn't get created\n", "ArrayListTest_AddAppendRemove()"); } } // }}} int main() { ArrayListTest_initialState(); puts("ArrayListTest_initialState successful."); ArrayListTest_changeSize(); puts("ArrayListTest_changeSize successful."); ArrayListTest_AddAppendRemove(); puts("ArrayListTest_AddAppendRemove successful."); return 0; }
The code compiles (using clang
and C99
) without any warnings:
clang ArrayListTest.c ArrayList.c -o ArrayListTest -Weverything -std=c99 -pedantic-errors
I'm currently mostly concerned about the following things:
- Readability. The intend of every piece of code is clear to the reader.
- Correctness. Everything is working as it should (I think it's clear what should happen).
- Universality. The data structure is capable to hold any kind of data (I'm currently not sure about the alignment of function pointers).
- Portability. The data structure doesn't break on specific machines.
- Performance. What can be done to improve the performance of this code?