I've seen few similar post about dynamic array in C
released as macros
, but I tried a new approach to make it looks more like a template, wrapped in a big macros. However I need a review for suggestions or improvements also. Here is the trivial implementation:
dynarray_t.h
#ifndef DYNARRAY_T_H
#define DYNARRAY_T_H
#include <stdlib.h> /* malloc, calloc, realloc */
//in case no initsize is 0 or less we will assert
#define DARRAY(T, N, INITSIZE, MOD) \
static const char __attribute__((unused)) \
N##_sassertsizeless[INITSIZE <=0 ? -1 : 1]; \
typedef struct \
{ \
size_t size, count; \
T* pData; \
} N##_t; \
MOD N##_t* self_##N; \
\
static N##_t* N##_t##_init(void) \
{ \
N##_t* pN = (N##_t*)malloc(sizeof(N##_t)); \
if (!pN) return 0x00; \
else { \
pN->pData = (T*)calloc(INITSIZE, sizeof(T)); \
if (!pN->pData) { free(pN); return 0x00; } \
else { \
pN->count = 0; \
pN->size = INITSIZE; \
return pN; } \
} \
} \
\
static void N##_t##_wiffull(N##_t* _this) \
{ \
if (!(_this->count < _this->size-1)) { \
T* t = (T*)realloc(_this->pData, \
sizeof(T)* _this->size * 2); \
if (t) { \
_this->pData = t; \
_this->size *= 2; \
} \
} \
} \
\
static void N##_t##_resizeto(N##_t* _this, size_t ns) \
{ \
if (ns > _this->size-1) { \
T* t = (T*)realloc(_this->pData, \
sizeof(T)* ns * 2); \
if (t) { \
_this->pData = t; \
_this->size = ns * 2; \
} \
} \
} \
\
static void N##_t##_add(T item, N##_t* _this) \
{ \
N##_t##_wiffull(_this); \
*(_this->pData+_this->count) = item; \
_this->count++; \
} \
\
static T* N##_t##_getat(unsigned int idx, N##_t* _this) \
{ \
if (idx < _this->count) \
return &_this->pData[idx]; \
else return 0x00; \
} \
\
static void N##_t##_cleanup(N##_t* _this) \
{ \
if (_this) { \
if (_this->pData) free(_this->pData); \
_this->pData = 0x00; \
free(_this); \
_this = 0x00; \
} \
} \
static void N##_t##_add_at(T item, size_t idx, N##_t* _this) \
{ \
N##_t##_resizeto(_this, idx); \
*(_this->pData+idx) = item; \
_this->count++; \
} \
#endif // DYNARRAY_T_H
And some simple example usage:
#include "dynarray_t.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFF_SZ 83
typedef struct _str_t {
char data[BUFF_SZ];
} str_t;
DARRAY(str_t, readBuff, 101,);
int main(void)
{
int i;
self_readBuff = readBuff_t_init(); // init
for(i=0; i < 100; i++) { // fill
str_t t = {{0}};
snprintf(t.data, sizeof (t.data), "Test line [%d]", i);
readBuff_t_add(t, self_readBuff);
}
int s = self_readBuff->size;
for(i=0; i < self_readBuff->size; i++) { // read element at(index)
printf("%s\r\n", readBuff_t_getat(i, self_readBuff)->data);
}
readBuff_t_cleanup(self_readBuff);
return 0;
}
Also please refer to C
language only! Not interested in talking for C++
, I am quite aware how to work it on template
. I need something similar for C
, so please give me an advice for design, or spot pitfalls if any.
2 Answers 2
Use Common Definitions Rather Than Hard Coded Values
I agree with @pm100 about NULL
, it is much more common to use NULL
rather than 0x00. Very early C++ compilers also used NULL
rather than nullptr
.
Since stdlib.h
is already included, the exit constants EXIT_SUCCESS
and EXIT_FAILURE
are availble, this would make the code more readable and maintainable.
Most modern C and C++ compilers will add a final return 0;
to the code so the return in main()
isn't strictly necessary.
Prefer size_t
When the Variable Can Be Used As an Index
In main the variable i
should be declared as size_t
rather than int
. If you compile -Wall you will find that the comparison between i
and self_readBuff->size
yields a type mismatch warning between int
and size_t
.
In the declaration of N##t_getat(unsigned int idx, N##_t* _this)
the unsigned int
should also be size_t
.
Prefer Local Variables Over Global Variables
I would suggest a separate macro to define the variable of the proper type so that it can be used in a function rather than having a global variable.
In main()
it would be better if self_readBuff
was declared locally rather than as a static variable globally. The variable ``self_##N` is not used anywhere else globally.
Only Code What is Necessary
The header file string.h
is not necessary and slows down compile time. The variable s
in main()
is never referenced. int s = self_readBuff->size;
Keep it Simple
I would have defined each function as a separate macro and then included all of them in a single macro for ease of debugging and possible separate use. It will also make the code easier to maintain if each function can be maintained separately.
-
1\$\begingroup\$ Note that using a signed type instead of
size_t
will allow the compiler to optimize more aggressively, but it may also leads bugs. If you use an unsigned type for array/pointer indices, always usesize_t
. Also, don't optimize prematurely. \$\endgroup\$yyny– yyny2020年08月02日 12:57:11 +00:00Commented Aug 2, 2020 at 12:57 -
1\$\begingroup\$ Don't you mean "Only include what is necessary"? \$\endgroup\$einpoklum– einpoklum2020年08月02日 15:25:12 +00:00Commented Aug 2, 2020 at 15:25
-
\$\begingroup\$ @einpoklum Mostly, but that would leave out the referenced variable
s
. \$\endgroup\$2020年08月02日 15:30:55 +00:00Commented Aug 2, 2020 at 15:30 -
1\$\begingroup\$ Good suggestions. Also I am thinking how convinient would be to make a dynamic array of that dynamic array, guess I will play a bit more taking note of your suggestions. \$\endgroup\$Ilian Zapryanov– Ilian Zapryanov2020年08月02日 17:04:09 +00:00Commented Aug 2, 2020 at 17:04
my 2 cents worth
v nice clean code.
I would have called the generated variable N not self_N. That looks peculiar , plus all the other generated names are N## something, having something##N is also odd. In the macro call I said I wanted it called 'readBuff' so call it that.
the use of 0x00 for null is certainly correct buts its the first time I have ever seen it, its not idiomatic. I would say NULL (or plain 0).
Did you consider the possibility of creating the struct on the stack or statically? I mean there is no reason to place it on the heap. It doesn't grow and you don't need variable numbers of them.
-
\$\begingroup\$ Thank you. Will consider it. Feel free to add a snippet \$\endgroup\$Ilian Zapryanov– Ilian Zapryanov2020年08月01日 19:35:12 +00:00Commented Aug 1, 2020 at 19:35
_t
to the type? Shouldn't the user decide whether they want areadBuff
or areadBuff_t
? \$\endgroup\$