I'm trying to implement a simple type-generic Vector with macros using the newest features of C23. Looking for any advice about macro design and pitfalls because I generally avoid macros at all costs. Wondering how to extend this code to handle more complex data types as well.
Also, I'm not particularly happy with the hacky int vector_abort()
used in the vector_at()
ternary. Any portable advice here is greatly appreciated. Furthermore, if anyone has a way to make opaque structures with macros I would be very interested.
A major problem that I immediately notice is that there isn't an easy way to tell if vector_push()
was successful. However, return values aren't permitted for macros and calling abort()
or exit()
doesn't seem great in this instance.
C23 features used:
nullptr
constant from<stddef.h>
true
andfalse
keywordsfree_sized()
from<stdlib.h>
= {}
empty initializerfunction()
empty function argument declarationtypeof
operatorN3003
Improved Rules for Tag Compatibility
vector.h
#ifndef VECTOR_H #define VECTOR_H #include <errno.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> int vector_abort(void) { errno = EINVAL; fprintf(stderr, "ERROR: %s.\n" "Invalid vector index.\n", strerror(errno)); errno = 0; abort(); return 0; } #define vector(TYPE) struct vector_##TYPE {size_t length; size_t capacity; TYPE* array;} #define vector_new(TYPE, CAPACITY) {.length = 0, .capacity = CAPACITY, .array = calloc(CAPACITY, sizeof(TYPE))} #define vector_delete(VECTOR) \ do { \ VECTOR.length = 0; \ VECTOR.capacity = 0; \ free_sized(VECTOR.array, (VECTOR.length * sizeof(*(VECTOR.array)))); \ VECTOR.array = nullptr; \ } while (false) #define vector_size(VECTOR) VECTOR.length #define vector_at(VECTOR, INDEX) (VECTOR.length > INDEX) ? VECTOR.array[INDEX] : vector_abort() #define vector_pop(VECTOR) if (VECTOR.length > 0) VECTOR.length-- #define vector_push(VECTOR, VALUE) \ do { \ if (VECTOR.length == VECTOR.capacity) { \ typeof(VECTOR.array) new_array = realloc(VECTOR.array, (VECTOR.capacity * 2 * sizeof(*(VECTOR.array)))); \ if (!new_array) break; \ VECTOR.capacity *= 2; \ VECTOR.array = new_array; \ } \ VECTOR.array[VECTOR.length] = VALUE; \ VECTOR.length++; \ } while (false) #endif
main.c
#include "./vector.h" #include <stdio.h> int main(void) { vector(int) v = {}; // or use = vector_new(type, capacity) vector_push(v, 1); vector_push(v, 2); vector_push(v, 3); for (size_t i = 0; i < vector_size(v); ++i) { printf("Test Push %d\n", vector_at(v, i)); } vector_pop(v); for (size_t i = 0; i < vector_size(v); ++i) { printf("Test Pop %d\n", vector_at(v, i)); } vector_delete(v); for (size_t i = 0; i < vector_size(v); ++i) { printf("Test Delete %d\n", vector_at(v, i)); } return EXIT_SUCCESS; }
Polyfill
Here is a free_sized()
polyfill if your implementation doesn't support it yet.
void free_sized(void* ptr, size_t /*size*/) { free(ptr); }
Compiler Flags
-Wall
-Wextra
-Werror
-pedantic-errors
-std=c2x
vector_delete
appears to use thelength
field after it is set to 0 in a call tofree_sized
. I believe it should be usingcapacity
notlength
, and should be using them before setting them to zero. (Which, if they're both set to zero it doesn't really matter which you use, does it? But still...)\$\endgroup\$