HiveBrain v1.2.0
Get Started
← Back to all entries
patterncMinor

A small generic array in C

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
arraygenericsmall

Problem

The generic arrays in C I found on the net used either of these methods:

  • The preprocesser with #define and #include (a lot of repetition and different names when declaring multiple types, but type-safe).



  • void *-pointers (no type safety).



  • void *-pointers and callbacks that fetch specific datatypes from the pointers (troublesome since every datatype needs callbacks and slow due to a lot of callback calls when copying multiple values).



  • Typed pointers for direct access and casting them to void or char in general array functions (generic and partly type-safe), actually results in UB since you can't simply alter any pointer to a pointer by dereferencing it as void or char as they may not share the same internal representation.



I wanted it more template-like, without much preparation, only one name for many types and also without repeating the datatype on every push/pop/etc, still type-safe and also standard compliant (no implementation-defined/undefined behaviour or GNU extensions).

I came up with a struct with a typed pointer and heavy preprocesser use, a short example code looks like this:

struct ary(int) a;

ary_init(&a, 0);
ary_push(&a, 30);
ary_push(&a, 20);
ary_push(&a, 10);
ary_sort(&a, ary_cb_cmpint);
ary_reverse(&a);
ary_release(&a);


The full code is here, mostly in ary.h and the rest in ary.c. Below there is the relevant part of the array with the less important functionality (ary_insert, ary_sort, ary_unique, ...) and predefined comp-/stringify-callbacks omitted.

I'd like to know whether it's bad designed or not useful at all, and if there are errors in the code like UB, bad idioms, etc.

ary.h

``
#ifndef ARY_H
#define ARY_H

#include
#include
#include
#include
#include
#include

#define ARY_GROWTH_FACTOR 2.0

/ construct/destruct the element pointed to by
buf /
typedef void (ary_elemcb_t)(void buf, void *userp);

/ the same as the
qsort` comparison function /
typedef int (*ary_cm

Solution

Some lesser observations,

-
With heavy use of macros,, care should be applied to insure exactly one use of a macro argument as the macro arguments could be complex and have side effects which only should be evaluated once.

//#define ary_attach(ary, nbuf, nlen, nalloc)       \
//        do {                                      \
//                ary_freebuf(&(ary)->s);           \
//                 (ary)->s.buf = (ary)->buf = nbuf;\
// ...
#define ary_attach(ary, nbuf, nlen, nalloc)       \
        do {                                      \
          struct aryb *ary__ = ary;  // add       \
          ary_freebuf(&ary__->s);                 \
          ary__->s.buf = ary__->buf = nbuf;       \
 ...


-
Avoid magic numbers like 32. Why 32?

// static const size_t snprintf_bufsize = 32;

// Maximum buffer size of  string version of `int` log10(bitwidth)
// Other formula are more precise, but better than guessing buffer needs.
static const size_t snprintf_bufsize = sizeof(int)*CHAR_BIT/3 + 3;

int ary_cb_inttostr(char **ret, const void *elem) {
  if (!(*ret = ary_xrealloc(NULL, snprintf_bufsize, 1)))
    return -1;
  return snprintf(*ret, snprintf_bufsize, "%d", *(int *)elem);
}


-
As free(NULL) is OK, IMO, any free-like function should also handle NULL.

void ary_freebuf(struct aryb *ary) {
  if (ary == NULL) return; // add
  if (ary->len && ary->dtor) {


-
ary.c should include files like strcmp.h and not count on #include "ary.h" to have included them.

-
Consider simpler code, as below and in other places.

// size_t seplen = sep ? strlen(sep) : 0, i, len;
size_t seplen = sep ? strlen(sep) : 0;
size_t i, len;


-
Without clear code understanding, I have a bit concern about strbuf.buf[strbuf.len - 1]. Should strbuf.len == 0, disastrous result would occur.

-
Uncertain: Comments on # lines is not portable. I'll have to research this.

#endif /* ARY_H */


-
Be careful about ary_xrealloc(), which calls realloc(). Should ary->len == 0 , return NULL is not an out-of-memory

buf = ary_xrealloc(ary->buf, ary->len, ary->sz);
// if (!buf) return 0;
if (buf == NULL && ary->len > 0 && ary->sz > 0) return 0;

Code Snippets

//#define ary_attach(ary, nbuf, nlen, nalloc)       \
//        do {                                      \
//                ary_freebuf(&(ary)->s);           \
//                 (ary)->s.buf = (ary)->buf = nbuf;\
// ...
#define ary_attach(ary, nbuf, nlen, nalloc)       \
        do {                                      \
          struct aryb *ary__ = ary;  // add       \
          ary_freebuf(&ary__->s);                 \
          ary__->s.buf = ary__->buf = nbuf;       \
 ...
// static const size_t snprintf_bufsize = 32;

// Maximum buffer size of  string version of `int` log10(bitwidth)
// Other formula are more precise, but better than guessing buffer needs.
static const size_t snprintf_bufsize = sizeof(int)*CHAR_BIT/3 + 3;

int ary_cb_inttostr(char **ret, const void *elem) {
  if (!(*ret = ary_xrealloc(NULL, snprintf_bufsize, 1)))
    return -1;
  return snprintf(*ret, snprintf_bufsize, "%d", *(int *)elem);
}
void ary_freebuf(struct aryb *ary) {
  if (ary == NULL) return; // add
  if (ary->len && ary->dtor) {
// size_t seplen = sep ? strlen(sep) : 0, i, len;
size_t seplen = sep ? strlen(sep) : 0;
size_t i, len;
#endif /* ARY_H */

Context

StackExchange Code Review Q#133376, answer score: 2

Revisions (0)

No revisions yet.