Here is my type safe dynamic array. I'm eventually going to make a hash table in C. I'm just wondering if anything seems wrong or out of place. Thanks.
#ifndef DARRAY_HEADER_INCLUDED
#define DARRAY_HEADER_INCLUDED
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#define darray_deff(name, type)\
struct name {\
size_t capacity;\
size_t size;\
type *data;\
};\
void name##_grow(struct name *const _array, size_t const _capacity);\
void name##_reserve(struct name *const _array, size_t const _capacity);\
void name##_destroy(struct name *const _array);\
void name##_push(struct name *const _array, type const _value);\
void name##_push_unintialized(struct name *const _array, size_t const _amount);\
void name##_pop(struct name *const _array);\
type *name##_begin(struct name *const _array);\
type *name##_end(struct name *const _array);\
void name##_clear(struct name *const _array);\
int name##_empty(struct name *const _array);\
void name##_zero(struct name *const _array);\
\
void name##_grow(struct name *const _array, size_t const _capacity) {\
type *data = realloc(_array->data, _capacity * sizeof(type));\
assert(data);\
_array->data = data;\
_array->capacity = _capacity;\
}\
void name##_reserve(struct name *const _array, size_t const _capacity) {\
if(_capacity > _array->capacity) {\
name##_grow(_array, _capacity);\
}\
}\
void name##_destroy(struct name *const _array) {\
free(_array->data);\
_array->capacity = 0;\
_array->size = 0;\
}\
void name##_push(struct name *const _array, type const _value) {\
if(_array->size == _array->capacity) {\
size_t const capacity = (_array->capacity ? _array->capacity : 1);\
name##_grow(_array, capacity * 2);\
}\
_array->data[_array->size] = _value;\
++_array->size;\
}\
void name##_push_unintialized(struct name *const _array, size_t const _amount) {\
if(_array->size + _amount > _array->capacity) {\
size_t const new_capacity = _array->size + _amount;\
name##_grow(_array, new_capacity);\
}\
_array->size = _array->size + _amount;\
}\
inline void name##_pop(struct name *const _array) {\
--_array->size;\
}\
inline type *name##_begin(struct name *const _array) {\
return _array->data;\
}\
inline type *name##_end(struct name *const _array) {\
return _array->data + _array->size;\
}\
inline void name##_clear(struct name *const _array) {\
_array->size = 0;\
}\
inline int name##_empty(struct name *const _array) {\
return _array->size == 0;\
}\
void name##_zero(struct name *const _array) {\
memset(_array->data, 0, sizeof(type) * _array->size);\
}\
#endif // DARRAY_HEADER_INCLUDED
```
-
3\$\begingroup\$ Could you please provide your test case file as well. Macros without any demonstration of usage are difficult to review. \$\endgroup\$pacmaninbw– pacmaninbw ♦2022年06月13日 15:24:10 +00:00Commented Jun 13, 2022 at 15:24
1 Answer 1
Reduce clutter
const
of an object serves little purpose in declarations other than to clutter code. This is the interface users often reference to understand the code. Make it easier for them.
// void name##_grow(struct name *const _array, size_t const _capacity);
void name##_grow(struct name * _array, size_t _capacity);
Add const
to enquiry functions
// int name##_empty(struct name *const _array);
int name##_empty(const struct name *const _array);
// ^^^^^
Use bool
for logical results
#include <stdbool.h>
// int name##_empty(struct name *const _array);
bool name##_empty(struct name *const _array);
Aside: personally I like isempty()
vs. empty()
.
Tolerate a NULL
on destroy
As free(NULL)
is OK, allow name##_destroy(NULL)
to simplify clean-up.
Guard against popping an empty table in name##_pop()
Missing name##_shrink()
It differs from name##_grow()
in that shrinking to 0 and a result of NULL
is OK.
I'd expect a good hash table function set to shrink selectively as well as grow.
Useful to add name##_apply()
int apply(array, state, func)
applies function func(state, &element)
to each element of the array.
Apply to each element as long as return value is 0. With a non-zero return, stop applying to the rest and return that value.
Very useful for printing each element and iterating the array.
pop()
misnamed
I'd except a pop to return a value.
// void name##_pop(struct name *const _array)
type name##_pop(struct name *const _array)
Deeper
There are advantages for having a hash table size that is a prime - better hashing. To that end, for .capacity
, I'd use an index into a look-up table of select primes - typical primes just smaller than powers-of-2.
-
\$\begingroup\$ For shrink would i use a combination of free( if given capacity is 0 ) and realloc( if given capacity is less than current capacity )? \$\endgroup\$Lead Vaxeral– Lead Vaxeral2022年06月14日 04:57:06 +00:00Commented Jun 14, 2022 at 4:57
-
\$\begingroup\$ Im actually confused by "Tolerate Null on destroy". \$\endgroup\$Lead Vaxeral– Lead Vaxeral2022年06月14日 06:14:08 +00:00Commented Jun 14, 2022 at 6:14
-
-
\$\begingroup\$ @LeadVaxeral
name##_destroy(NULL)
and is bad it attempts_array->data
,_array->capacity
, ... Code could test_array == NULL
first. \$\endgroup\$chux– chux2022年06月14日 13:18:11 +00:00Commented Jun 14, 2022 at 13:18