I'm in the process of writing a Generic Container Library in C called CGCL (https://github.com/ta5578/C-Generic-Container-Library). One of the generic containers I'm implementing is the vector container. I'm writing this library to improve my knowledge of different data structures, improve my C coding skills, improve my skills with generic programming in C (as opposed to templates from C++), and also hopefully grow the library into something akin to the STL containers from C++ that all C developers can use when they need to use a common data structure (C doesn't have a starndard data structure library). I would like a review on the following (in this order of importance):
- Correctness
- Performance Considerations (both memory and time improvements)
- Code Style
For some background information, the library naming convention for the container functions is something like:
gcl_<container>_<operation>
where the containers themselves are named like this: gcl_<container>
.
For reference, I'll be including some of the utility headers that contain some of the typedefs and enums I use in the code below. The actual code for the vector is in cgcl_vector.h/.c however.
cgcl_util.h
#ifndef CGCL_UTIL_H
#define CGCL_UTIL_H
#include <stdbool.h>
typedef int (*GCLCompare)(const void *, const void *);
typedef bool(*GCLIsEqual)(const void *, const void *);
#endif
cgcl_errors.h
#ifndef CGCL_ERRORS_H
#define CGCL_ERRORS_H
typedef enum GCLError {
eNoErr = 0,
eInvalidArg,
eFailedAlloc,
eInvalidOperation,
eOutOfBoundsAccess
} GCLError;
#endif
cgcl_vector.h
#ifndef CGCL_VECTOR_H
#define CGCL_VECTOR_H
#include "cgcl_errors.h"
#include "cgcl_util.h"
#include <stddef.h>
#include <stdbool.h>
typedef struct gcl_vector___ gcl_vector;
gcl_vector *gcl_vector_init();
GCLError gcl_vector_push_back(gcl_vector *v, void *elem);
GCLError gcl_vector_insert(gcl_vector *v, void *elem, size_t pos);
GCLError gcl_vector_popback(gcl_vector *v);
GCLError gcl_vector_erase(gcl_vector *v, size_t pos);
GCLError gcl_vector_reserve(gcl_vector *v, size_t n);
void gcl_vector_clear(gcl_vector *v);
void *gcl_vector_find(const gcl_vector *v, const void *value, GCLIsEqual isEqual);
void gcl_vector_remove(gcl_vector *v, void *elem, GCLIsEqual isEqual);
GCLError gcl_vector_set(gcl_vector *v, void *elem, size_t pos);
void *gcl_vector_get(const gcl_vector *v, size_t pos);
bool gcl_vector_empty(const gcl_vector *v);
size_t gcl_vector_size(const gcl_vector *v);
size_t gcl_vector_capacity(const gcl_vector *v);
size_t gcl_vector_max_size(void);
void *gcl_vector_front(const gcl_vector *v);
void *gcl_vector_back(const gcl_vector *v);
void gcl_vector_destroy(gcl_vector *v);
void gcl_vector_foreach(gcl_vector *v, void *(*callback)(void *));
void gcl_vector_sort(gcl_vector *v, GCLCompare sortCompare);
#endif
cgcl_vector.c
#include "cgcl_vector.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
struct gcl_vector___ {
void **data;
size_t size, capacity;
};
static const size_t INITIAL_VECTOR_CAPACITY = 100;
static const size_t MAX_VECTOR_SIZE = UINTMAX_MAX - 1;
gcl_vector *gcl_vector_init()
{
return calloc(1, sizeof(gcl_vector));
}
static GCLError gcl_vector_realloc(gcl_vector *v, size_t newElemCount)
{
void *newData = realloc(v->data, newElemCount * sizeof(*(v->data)));
if (!newData) {
return eFailedAlloc;
}
v->data = newData;
v->capacity = newElemCount;
return eNoErr;
}
static size_t gcl_vector_find_index(gcl_vector *v, void *elem, GCLIsEqual isEqual)
{
for (size_t i = 0; i < v->size; ++i) {
if (isEqual(elem, v->data[i])) {
return i;
}
}
return v->size + 1;
}
GCLError gcl_vector_push_back(gcl_vector *v, void *elem)
{
if (v->size >= gcl_vector_max_size()) {
return eInvalidOperation;
}
GCLError e = eNoErr;
if (v->size == v->capacity) {
const size_t newElemCount = v->capacity == 0 ? INITIAL_VECTOR_CAPACITY :
v->capacity * 2;
e = gcl_vector_realloc(v, newElemCount);
}
if (e == eNoErr) {
v->data[v->size++] = elem;
}
return e;
}
GCLError gcl_vector_insert(gcl_vector *v, void *elem, size_t pos)
{
if (v->size >= gcl_vector_max_size()) {
return eInvalidOperation;
}
GCLError e = eNoErr;
if (v->size == v->capacity) {
const size_t newElemCount = v->capacity == 0 ? INITIAL_VECTOR_CAPACITY :
v->capacity * 2;
e = gcl_vector_realloc(v, newElemCount);
}
if (e == eNoErr) {
memmove(&v->data[pos + 1], &v->data[pos],
(v->size - pos) * sizeof(*(v->data)));
v->data[pos] = elem;
++v->size;
}
return e;
}
GCLError gcl_vector_popback(gcl_vector *v)
{
if (gcl_vector_empty(v)) {
return eInvalidOperation;
}
v->data[--v->size] = NULL;
return eNoErr;
}
void *gcl_vector_find(const gcl_vector *v, const void *value, GCLIsEqual isEqual)
{
for (size_t i = 0; i < v->size; ++i) {
if (isEqual(value, v->data[i])) {
return v->data[i];
}
}
return NULL;
}
void gcl_vector_remove(gcl_vector *v, void *elem, GCLIsEqual isEqual)
{
const size_t foundIndex = gcl_vector_find_index(v, elem, isEqual);
if (foundIndex == v->size + 1) {
return;
}
memmove(&v->data[foundIndex], &v->data[foundIndex + 1],
(v->size - foundIndex) * sizeof(*(v->data)));
--v->size;
}
GCLError gcl_vector_erase(gcl_vector *v, size_t pos)
{
if (pos > v->size - 1) {
return eOutOfBoundsAccess;
}
for (size_t i = pos; i < v->size; ++i) {
v->data[i] = NULL;
}
v->size = pos;
return eNoErr;
}
GCLError gcl_vector_reserve(gcl_vector *v, size_t n)
{
return n > v->capacity ? gcl_vector_realloc(v, n) : eInvalidOperation;
}
GCLError gcl_vector_set(gcl_vector *v, void *elem, size_t pos)
{
if (pos > v->size - 1 || gcl_vector_empty(v)) {
return eOutOfBoundsAccess;
}
v->data[pos] = elem;
return eNoErr;
}
void *gcl_vector_get(const gcl_vector *v, size_t pos)
{
if (pos > v->size - 1 || gcl_vector_empty(v)) {
return NULL;
}
return v->data[pos];
}
void gcl_vector_clear(gcl_vector *v)
{
memset(v->data, 0, v->size * sizeof(*(v->data)));
v->size = 0;
}
bool gcl_vector_empty(const gcl_vector *v)
{
return v->size == 0;
}
size_t gcl_vector_size(const gcl_vector *v)
{
return v->size;
}
size_t gcl_vector_capacity(const gcl_vector *v)
{
return v->capacity;
}
size_t gcl_vector_max_size(void)
{
return MAX_VECTOR_SIZE;
}
void *gcl_vector_front(const gcl_vector *v)
{
if (gcl_vector_empty(v)) {
return NULL;
}
return *(v->data);
}
void *gcl_vector_back(const gcl_vector *v)
{
if (gcl_vector_empty(v)) {
return NULL;
}
return v->data[v->size - 1];
}
void gcl_vector_foreach(gcl_vector *v, void *(*callback)(void *))
{
for (size_t i = 0; i < v->size; ++i) {
callback(v->data[i]);
}
}
void gcl_vector_sort(gcl_vector *v, GCLCompare sortCompare)
{
qsort(v->data, v->size, sizeof(*(v->data)), sortCompare);
}
void gcl_vector_destroy(gcl_vector *v)
{
free(v->data);
free(v);
}