Nasty Hack
#Nasty Hack II grant that it's a hack but I'd like to know of a platform where the following doesn't fix the alignment issue:
#Doing It Properly
Doing It Properly
#include <stdint.h>//Defines MAX_SIZE
#include <stddef.h>//Defines the little known offsetof macro.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>//Defines memcpy(.,.,.). I know! I know!
//Defined as a macro so we can implement a bit of dynamic type safety.
#define da_push_back(DA,ELEM) da_push_back_(&(DA),&(ELEM),sizeof(ELEM))
//Best known practice for determining type alignment in C.
#define alignment_of(type) offsetof(struct { char w;type v;},v)
#define da_new(type,slots) da_new_(sizeof(type),slots,alignment_of(type),#type)
//Overwhelming convention is to use zero as 'success' and non-zero as 'error'.
//This allows the definition of diagnostic error codes and meaningful constructs
// like int error=da_thing(); if(error){ handle error... }
const int DA_SUCCESS=0;
const int DA_ERROR=1;
struct dynamic_struct;
typedef struct dynamic_struct* dynamic_array;
typedef struct {
size_t size;
size_t position;
size_t slots;
size_t offset;//This will be store the amount of dynamically calculated alignment padding.
const char* name;//Optional run-time type info. See stringizing # in macro.
} Index;
dynamic_array da_new_(size_t size,size_t slots,size_t align,const char* name){
//Get alignment for both elements & Index.
size_t falign=alignment_of(Index);
if(falign<align){
falign=align;
}
//offset will be the padding to go before Index.
const size_t over=sizeof(Index)%falign;
const size_t offset=over==0?0:falign-over;
//Allocate the padding the Index and the slots.
const size_t headersize=offset+sizeof(Index);
char*const block=malloc(headersize+size*slots);
if(block==NULL){
return NULL;//allocation failed - behave like malloc(.).
}
//Notice we are padding at the start.
//If we pad between Index and the elements we get in to a muddle.
//To find the index from the array we need the offset but it's in the Index!
//It's important to remember this offset so we can free the right point at the end.
Index* index=(Index*)(block+offset);
index->size=size;
index->position=0;
index->slots=slots;
index->offset=offset;
index->name=name;
return (dynamic_array)(block+headersize);
}
Index* da_get_index(dynamic_array da){
return ((Index*)da)-1;
}
const char* da_get_typename(dynamic_array da){
return da_get_index(da)->name;
}
size_t da_get_slots(dynamic_array da){
return da_get_index(da)->slots;
}
int da_delete(dynamic_array da){
//remember kids only render unto free(.) what malloc(.) has rendered unto thee.
//Or calloc(.) or realloc(.) obviously. But you get the point.
//We need to find the bottom of the block including any offset padding.
Index*const index=da_get_index(da);
const size_t offset=index->offset;
void*const block=((char*const)index)-offset;
free(block);
return DA_SUCCESS;
}
//We pass in sizecheck from the macro as a bit of type safety.
//Obviously this is no guarantee but will at least protect against some gross errors.
int da_push_back_(dynamic_array* da,const void*const element,const size_t sizecheck){
Index* index=da_get_index(*da);
const size_t size=index->size;
const size_t position=index->position;
const size_t slots=index->slots;
if(sizecheck!=size){
return DA_ERROR;//element not compatible with array...
}
if(position>=slots){
size_t newslots;
if(slots>SIZE_MAX/2){
if(slots==SIZE_MAX){
return DA_ERROR;//Exceeded size_t.
}
newslots=SIZE_MAX;
}else{
newslots=slots==0?1:slots*2;
}
const size_t offset=index->offset;
const size_t headersize=offset+sizeof(Index);
void*block=((char*)index)-offset;
char* newblock=realloc(block,headersize+newslots*size);
if(newblock==NULL){
return DA_ERROR;//Re-allocation failed.
}
*da=(dynamic_array)(newblock+headersize);
index=da_get_index(*da);
index->slots=newslots;
}
char* array=(char*)*da;
memcpy(array+position*size,element,size);
++(index->position);
return DA_SUCCESS;
}
void* da_get_array(dynamic_array da){
return da;
}
int main(void) {
int errors=0;
dynamic_array array=da_new(double,3);
double val=-2.718;
errors+=da_push_back(array,val);
double*access=da_get_array(array);
access[1]=1234.0;
access[2]=3.1415926535;
printf("type-name is \"%s\"\n",da_get_typename(array));
printf("%p %f %f %f\n",(void *)access,access[0],access[1],access[2]);
da_delete(array);
dynamic_array grow=da_new(int,3);
if(da_get_slots(grow)<3){
++errors;
}
int vint=99;
errors+=da_push_back(grow,vint);
vint=88;
errors+=da_push_back(grow,vint);
vint=77;
errors+=da_push_back(grow,vint);
//Now we make it grow...
vint=66;
errors+=da_push_back(grow,vint);
if(da_get_slots(grow)<4){
++errors;
}
int* ints=da_get_array(grow);
if(ints[0]!=99||ints[1]!=88||ints[2]!=77||ints[3]!=66){
++errors;
}
printf("%d %d %d %d\n",ints[0],ints[1],ints[2],ints[3]);
da_delete(grow);
dynamic_array longd=da_new(long double,10);
//Stringizing will 'normalise' the white-space in the typename.
//The source has multiple white-space characters
if(strcmp("long double",da_get_typename(longd))!=0){
++errors;
}
da_delete(longd);
//But it will consider aliases to be different.
//Notice how these two arrays have the same type but not type-name.
dynamic_array uinta=da_new(unsigned,10);
if(strcmp("unsigned",da_get_typename(uinta))!=0){
++errors;
}
dynamic_array uintb=da_new(unsigned int,10);
if(strcmp("unsigned int",da_get_typename(uintb))!=0){
++errors;
}
da_delete(uinta);
da_delete(uintb);
if(errors!=0){
printf("ERRORS - %d\n",errors);
}else{
printf("** SUCCESS **\n");
}
return errors==0?EXIT_SUCCESS:EXIT_FAILURE;
}