I'm getting tired of reading templated c++ code so I am dipping my feet in c. Here is a small, not full, implementation of an expandable vector. Right now I need only push_back
and the ability to grow on demand, no pop
or removal that's why it's missing.
Just upfront, yes I did consider making the vector metadata and the dynamic memory one block of memory, I just didn't like having to pass a double pointer to enable the reallocation. If there is another option i'd like to hear it.
With regard to initialization I was wondering what the more preferred way of doing it the way i did it ...
vector* v = vector_allocate(); // allocate data in v and v itself
vector_free(v); // free data in v and v itself
v = NULL;
or
vector v;
vector_initialize(&v); // allocate data in v
vector_free(&v); // free data in v
// v is now unusable ...
Code is written for Visual Studio, i did not compile under gcc.
#ifdef _MSC_VER
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
#else
#include <stdlib.h>
#define _CrtDumpMemoryLeaks() ((int)0)
#endif
#include <assert.h>
#include <stdio.h>
#include <string.h>
typedef struct {
size_t capacity;
size_t data_size;
size_t size;
char* data;
} vector;
// Allocate the data structure for the vector
vector* vector_allocate(size_t data_size) {
vector* result = malloc(sizeof *result);
if (result) {
result->data = malloc(data_size);
result->capacity = 1;
result->data_size = data_size;
result->size = 0;
if (!result->data)
{
free(result);
result = NULL;
}
}
return result;
}
// Free the memory of the vector, the pointer to the vector is invalid after this
void vector_free(vector* v)
{
if (v)
{
free(v->data);
v->data = NULL;
free(v);
}
}
// Return the element at index, does not do a range check
void* vector_get(vector* v, size_t index) {
return &(v->data[index * v->data_size]);
}
// Return the element at index, return NULL if index is out of range for the vector
inline void* vector_get_checked(vector* v, size_t index) {
return (index >= 0 && index < v->size) ? &(v->data[index * v->data_size]) : NULL;
}
// if capacity < new_capacity realloc up to new_capacity
void vector_reserve(vector* v, size_t new_capacity) {
if (new_capacity <= v->size) return;
void* new_data = realloc(v->data, new_capacity*v->data_size);
if (new_data) {
v->capacity = new_capacity;
v->data = new_data;
}
else {
abort();
}
}
// Puts an element data[size * data_size], will reserve more space if size == capacity
void vector_push_back(vector* v, void* data) {
if (v->size >= v->capacity) {
size_t new_capacity = (v->capacity > 0) ? (size_t)(v->capacity * 2) : 1;
vector_reserve(v, new_capacity);
}
memcpy(vector_get(v,v->size), data, v->data_size);
++v->size;
}
// Here there be tests ...
void vector_test_alloc_free()
{
printf("vector_test_alloc_free: ");
vector* v = vector_allocate(sizeof(int));
assert(v != NULL);
assert(v->capacity == 1);
assert(v->size == 0);
assert(v->data_size == sizeof(int));
assert(v->data != NULL);
printf("OK\n");
vector_free(v);
}
void vector_test_insert_read_int()
{
printf("vector_test_insert_read_int: ");
int val1 = 0xabcdabcd;
int val2 = 0xeffeeffe;
vector* v = vector_allocate(sizeof(int));
vector_push_back(v,&val1);
assert(v->size == 1);
assert(v->capacity == 1);
int* p = vector_get(v, 0);
assert(*p == val1);
vector_push_back(v, &val2);
assert(*p == val1);
assert(*(p + 1) == val2);
printf("OK\n");
vector_free(v);
}
void vector_test_insert_read_struct()
{
struct data {
double d;
int i;
};
printf("vector_test_insert_read_struct: ");
vector* v = vector_allocate(sizeof(struct data));
struct data d1 = { 0.05, 123 };
struct data d2 = { -1.9999e10, -9000 };
vector_push_back(v, &d1);
vector_push_back(v, &d2);
struct data* p = vector_get(v, 0);
assert((*p).d == d1.d); // Bitcopy should be exactly equal
assert((*p).i == d1.i);
p = vector_get(v, 1);
assert((*p).i == d2.i);
assert((*p).d == d2.d);
printf("OK\n");
vector_free(v);
}
void vector_test_safe_get()
{
printf("vector_test_safe_get: ");
vector* v = vector_allocate(sizeof(int));
int val = 0xff;
vector_push_back(v, &val);
vector_push_back(v, &val);
assert(NULL == vector_get_checked(v, -1));
assert(NULL == vector_get_checked(v, 2));
assert(val == *(int*)(vector_get_checked(v, 1)));
printf("OK\n");
vector_free(v);
}
void vector_test_reserve()
{
printf("vector_test_reserve: ");
vector* v = vector_allocate(sizeof(int));
assert(v->capacity == 1);
vector_reserve(v, 10);
assert(v->capacity == 10);
// if we didn't assign the correct space VS will shout about
// overwriting memory in DEBUG
int* p = (int*)v->data;
for (int i = 0; i < 10; ++i)
{
*p = i;
++p;
}
printf("OK\n");
vector_free(v);
}
int main(int arc, const char* argv[])
{
vector_test_alloc_free();
vector_test_insert_read_int();
vector_test_insert_read_struct();
vector_test_safe_get();
vector_test_reserve();
_CrtDumpMemoryLeaks();
return 0;
}
Any input is appreciated, especially with regard to colloquial c ...