3
\$\begingroup\$

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 <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>

#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_cmpcb_t)(const void *a, const void *b);

/* return a malloc()ed string of `buf` in `ret` and its size, or -1 */
typedef int (*ary_joincb_t)(char **ret, const void *buf);

typedef void *(*ary_xalloc_t)(void *ptr, size_t nmemb, size_t size);
typedef void (*ary_xdealloc_t)(void *ptr);

/* struct size: 6x pointers + 4x size_t's + 1x type */
#define ary(type)                                       \
        {                                               \
                struct aryb s;                          \
                size_t len;    /* number of elements */ \
                type *buf;     /* array buffer */       \
                type *ptr;                              \
                type val;                               \
        }

struct aryb {
        size_t len;
        size_t alloc;
        size_t sz;
        void *buf;
        ary_elemcb_t ctor;
        ary_elemcb_t dtor;
        void *userp;
};

/* predefined callbacks */
void ary_cb_freevoidptr(void *buf, void *userp);
void ary_cb_freecharptr(void *buf, void *userp);

int ary_cb_cmpint(const void *a, const void *b);
int ary_cb_strcmp(const void *a, const void *b);

int ary_cb_inttostr(char **ret, const void *elem);

/* forward declarations */
void ary_freebuf(struct aryb *ary);
void *ary_detach(struct aryb *ary, size_t *ret);
int ary_shrinktofit(struct aryb *ary);
void *ary_splicep(struct aryb *ary, size_t pos, size_t rlen, size_t alen);
int ary_index(struct aryb *ary, size_t *ret, size_t start, const void *data,
              ary_cmpcb_t comp);
int ary_join(struct aryb *ary, char **ret, const char *sep,
             ary_joincb_t stringify);

extern ary_xalloc_t ary_xrealloc;

/**
 * ary_init() - initialize an array
 * @ary: typed pointer to the array
 * @hint: count of elements to allocate memory for
 *
 * Return: When successful 1, otherwise 0 if ary_grow() failed. Always returns
 *      1 if @hint is 0.
 *
 * Note!: Do not directly access elements that are not added to the array yet,
 *      only those with an index below @ary->len.
 */
#define ary_init(ary, hint)                                 \
        ((ary)->s.alloc = (ary)->s.len = (ary)->len = 0,    \
         (ary)->s.sz = sizeof(*(ary)->buf),                 \
         (ary)->s.ctor = (ary)->s.dtor = NULL,              \
         (ary)->s.buf = (ary)->s.userp = (ary)->buf = NULL, \
         ary_grow((ary), (hint)))

/**
 * ary_release() - release an array
 * @ary: typed pointer to the initialized array
 *
 * All elements are removed, the buffer is released and @ary is reinitialized
 * with `ary_init(@ary, 0)`.
 */
#define ary_release(ary)                  \
        do {                              \
                ary_freebuf(&(ary)->s);   \
                (void)ary_init((ary), 0); \
        } while (0)

/**
 * ary_setcbs() - set an array's constructor and destructor
 * @ary: typed pointer to the initialized array
 * @_ctor: routine that creates new elements
 * @_dtor: routine that removes elements
 */
#define ary_setcbs(ary, _ctor, _dtor) \
        ((ary)->s.ctor = (_ctor), (ary)->s.dtor = (_dtor), (void)0)

 /**
 * ary_setuserp() - set an array's user-pointer for the ctor/dtor
 * @ary: typed pointer to the initialized array
 * @ptr: pointer that gets passed to the callbacks
 */
#define ary_setuserp(ary, ptr) \
        ((ary)->s.userp = (ptr), (void)0)

/**
 * ary_setinitval() - set an array's value used to initialize new elements
 * @ary: typed pointer to the initialized array
 * @...: value that is used for new elements if @ary->ctor() is NULL
 *
 * Note!: @... is like in ary_push(). Also the init-value is left uninitialized
 *      when using ary_init(). However, it has to be specified when
 *      initializing an array with ARY_INIT().
 */
#define ary_setinitval(ary, ...) \
        ((ary)->val = (__VA_ARGS__), (void)0)

/**
 * ary_attach() - attach a buffer to an array
 * @ary: typed pointer to the initialized array
 * @nbuf: pointer to a malloc()ed buffer
 * @nlen: number of elements pointed to by @nbuf
 * @nalloc: number of elements the buffer can hold
 *
 * The buffer @nbuf is henceforth owned by @ary and cannot be relied upon
 * anymore and also must not be free()d directly.
 */
#define ary_attach(ary, nbuf, nlen, nalloc)       \
        do {                                      \
                ary_freebuf(&(ary)->s);           \
                (ary)->s.buf = (ary)->buf = nbuf; \
                (ary)->s.len = (ary)->len = nlen; \
                (ary)->s.alloc = nalloc;          \
        } while (0)

/**
 * ary_detach() - detach an array's buffer
 * @ary: typed pointer to the initialized array
 * @size: pointer that receives @ary's length, can be NULL
 *
 * A directly following ary_release() is not needed.
 *
 * Return: The array buffer of @ary. If @ary's has no allocated memory, NULL is
 *      returned. You have to free() the buffer, when you no longer need it.
 */
#define ary_detach(ary, size)                                             \
        ((ary)->ptr = (ary_detach)(&(ary)->s, (size)), (ary)->buf = NULL, \
         (ary)->len = 0, (ary)->ptr)

/**
 * ary_grow() - allocate new memory in an array
 * @ary: typed pointer to the initialized array
 * @extra: count of extra elements
 *
 * Ensure that @ary can hold at least @extra more elements.
 *
 * Return: When successful 1, otherwise 0 if realloc() failed.
 */
#define ary_grow(ary, extra) \
        ((ary_grow)(&(ary)->s, (extra)) ? ((ary)->buf = (ary)->s.buf, 1) : 0)

/**
 * ary_shrinktofit() - release unused allocated memory in an array
 * @ary: typed pointer to the initialized array
 *
 * Return: When successful 1, otherwise 0 if realloc() failed. The array remains
 *      valid in either case.
 */
#define ary_shrinktofit(ary) \
        ((ary_shrinktofit)(&(ary)->s) ? ((ary)->buf = (ary)->s.buf, 1) : 0)

/**
 * ary_setlen() - set an array's length
 * @ary: typed pointer to the initialized array
 * @nlen: new number of elements @ary holds
 *
 * If @nlen is above @ary's current length, new elements are added, either by
 * calling @ary->ctor() on them or by using the array's (possibly uninitialized)
 * init-value. Respectively, if @nlen is below @ary's current length,
 * @ary->dtor() is called on all elements above the new length.
 * However, the array is never reallocated and @nlen is truncated to not exceed
 * `@ary.len + ary_avail(@ary)`.
 */
#define ary_setlen(ary, nlen)                                                  \
        do {                                                                   \
                size_t len = (nlen), i;                                        \
                if ((ary)->s.len < len) {                                      \
                        if ((ary)->s.alloc < len)                              \
                                len = (ary)->s.alloc;                          \
                        if ((ary)->s.ctor) {                                   \
                                for (i = (ary)->s.len; i < len; i++)           \
                                        (ary)->s.ctor(&(ary)->buf[i],          \
                                                      (ary)->s.userp);         \
                        } else {                                               \
                                for (i = (ary)->s.len; i < len; i++)           \
                                        (ary)->buf[i] = (ary)->val;            \
                        }                                                      \
                } else if ((ary)->s.len > len && (ary)->s.dtor) {              \
                        for (i = len; i < (ary)->s.len; i++)                   \
                                (ary)->s.dtor(&(ary)->buf[i], (ary)->s.userp); \
                }                                                              \
                (ary)->s.len = (ary)->len = len;                               \
        } while (0)

/**
 * ary_push() - add a new element to the end of an array
 * @ary: typed pointer to the initialized array
 * @...: value to push
 *
 * Return: When successful 1, otherwise 0 if ary_grow() failed.
 *
 * Note!: @... is only a single value, it's denoted as varargs in order to cope
 *      with struct-literals, additionally, it is not evaluated if ary_push()
 *      fails (so e.g. `strdup(s)` has no effect on failure).
 */
#define ary_push(ary, ...)                                                   \
        (((ary)->s.len == (ary)->s.alloc) ?                                  \
         ary_grow((ary), 1) ?                                                \
         ((ary)->buf[(ary)->len++, (ary)->s.len++] = (__VA_ARGS__), 1) : 0 : \
         ((ary)->buf[(ary)->len++, (ary)->s.len++] = (__VA_ARGS__), 1))

/**
 * ary_pushp() - add a new element slot to the end of an array (pointer)
 * @ary: typed pointer to the initialized array
 *
 * Return: When successful a pointer to the new element slot, otherwise NULL if
 *      ary_grow() failed.
 */
#define ary_pushp(ary)                                      \
        (((ary)->s.len == (ary)->s.alloc) ?                 \
         ary_grow((ary), 1) ?                               \
         &(ary)->buf[(ary)->len++, (ary)->s.len++] : NULL : \
         &(ary)->buf[(ary)->len++, (ary)->s.len++])

/**
 * ary_pop() - remove the last element of an array
 * @ary: typed pointer to the initialized array
 * @ret: pointer that receives the popped element's value, can be NULL
 *
 * If @ret is NULL, @ary->dtor() is called for the element to be popped.
 *
 * Return: When successful 1, otherwise 0 if there were no elements to pop.
 */
#define ary_pop(ary, ret)                                             \
        ((ary)->s.len ?                                               \
         ((void *)(ret) != NULL) ?                                    \
         (*(((void *)(ret) != NULL) ? (ret) : &(ary)->val) =          \
          (ary)->buf[--(ary)->s.len], (ary)->len--, 1) :              \
         (ary)->s.dtor ?                                              \
         ((ary)->s.dtor(&(ary)->buf[--(ary)->s.len], (ary)->s.userp), \
          (ary)->len--, 1) :                                          \
         ((ary)->s.len--, (ary)->len--, 1) : 0)

/**
 * ary_shift() - remove the first element of an array
 * @ary: typed pointer to the initialized array
 * @ret: pointer that receives the shifted element's value, can be NULL
 *
 * If @ret is NULL, @ary->dtor() is called for the element to be shifted.
 *
 * Return: When successful 1, otherwise 0 if there were no elements to shift.
 */
#define ary_shift(ary, ret)                                                 \
        ((ary)->s.len ?                                                     \
         ((void *)(ret) != NULL) ?                                          \
         (*(((void *)(ret) != NULL) ? (ret) : &(ary)->val) = (ary)->buf[0], \
          memmove(&(ary)->buf[0], &(ary)->buf[1],                           \
                  --(ary)->s.len * (ary)->s.sz), (ary)->len--, 1) :         \
         (ary)->s.dtor ?                                                    \
         ((ary)->s.dtor(&(ary)->buf[0], (ary)->s.userp),                    \
          memmove(&(ary)->buf[0], &(ary)->buf[1],                           \
                  --(ary)->s.len * (ary)->s.sz), (ary)->len--, 1) :         \
         (memmove(&(ary)->buf[0], &(ary)->buf[1],                           \
                  --(ary)->s.len * (ary)->s.sz), (ary)->len--, 1) : 0)

/**
 * ary_unshift() - add a new element to the beginning of an array
 * @ary: typed pointer to the initialized array
 * @...: value to unshift
 *
 * Return: When successful 1, otherwise 0 if ary_grow() failed.
 *
 * Note!: @... is like in ary_push().
 */
#define ary_unshift(ary, ...) \
        (ary_unshiftp(ary) ? (*(ary)->ptr = (__VA_ARGS__), 1) : 0)

/**
 * ary_unshiftp() - add a new element slot to the beginning of an array
 * @ary: typed pointer to the initialized array
 *
 * Return: When successful a pointer to the new element slot, otherwise NULL if
 *      ary_grow() failed.
 */
#define ary_unshiftp(ary) \
        ary_splicep((ary), 0, 0, 1)

/**
 * ary_splice() - add/remove elements from an array
 * @ary: typed pointer to the initialized array
 * @pos: index at which to add/remove
 * @rlen: number of elements to remove
 * @data: pointer to new elements
 * @dlen: number of new elements to add
 *
 * Return: When successful 1, otherwise 0 if there were new elements to add but
 *      ary_grow() failed (the array remains unchanged in this case).
 */
#define ary_splice(ary, pos, rlen, data, dlen)                        \
        (ary_splicep((ary), (pos), (rlen), (dlen)) ?                  \
         (memcpy((ary)->ptr, (data) ? (void *)(data) : (void *)(ary), \
                 (data) ? (dlen) * (ary)->s.sz : 0), 1) : 0)

/**
 * ary_splicep() - add element slots/remove elements from an array
 * @ary: typed pointer to the initialized array
 * @pos: index at which to add/remove
 * @rlen: number of elements to remove
 * @alen: number of new element slots to add
 *
 * Return: When successful a pointer to the first new element slot (position of
 *      the last removed element), otherwise NULL if there were new elements
 *      slots to allocate but ary_grow() failed (the array remains unchanged in
 *      this case).
 */
#define ary_splicep(ary, pos, rlen, alen)                                     \
        (((ary)->ptr = (ary_splicep)(&(ary)->s, (pos), (rlen), (alen))) ?     \
         ((ary)->buf = (ary)->s.buf, (ary)->len = (ary)->s.len, (ary)->ptr) : \
         NULL)

/**
 * ary_index() - get the first occurrence of an element in an array
 * @ary: typed pointer to the initialized array
 * @ret: pointer that receives the element's position, can be NULL
 * @start: position to start looking from
 * @data: pointer to the data to look for
 * @comp: comparison function, if NULL then memcmp() is used
 *
 * Return: When successful 1 and @ret is set to the position of the element
 *      found, otherwise 0 and @ret is uninitialized.
 */
#define ary_index(ary, ret, start, data, comp)                       \
        ((ary)->ptr = (data), (ary_index)(&(ary)->s, (ret), (start), \
                                          (ary)->ptr, (comp)))

/**
 * ary_join() - join all elements of an array into a string
 * @ary: typed pointer to the initialized array
 * @ret: pointer that receives a pointer to the new string
 * @sep: pointer to the null-terminated separator
 * @stringify: stringify function, if NULL then @ary is assumed to be a char *-
 *      array
 *
 * Return: When successful length of @ret, otherwise -1 with `*@ret == NULL` if
 *      realloc() failed. You have to free() *@ret, when you no longer need it.
 */
#define ary_join(ary, ret, sep, stringify) \
        (ary_join)(&(ary)->s, (ret), (sep), (stringify))

static inline int (ary_grow)(struct aryb *ary, size_t extra)
{
        const double factor = ARY_GROWTH_FACTOR;
        size_t alloc;
        void *buf;

        if (ary->len + extra <= ary->alloc)
                return 1;
        if (ary->alloc * factor < ary->len + extra)
                alloc = ary->len + extra;
        else
                alloc = ary->alloc * factor;
        buf = ary_xrealloc(ary->buf, alloc, ary->sz);
        if (!buf)
                return 0;
        ary->alloc = alloc;
        ary->buf = buf;
        return 1;
}

/* .... */

#endif /* ARY_H */

ary.c

#include "ary.h"

static void *ary_xrealloc_builtin(void *ptr, size_t nmemb, size_t size)
{
        return realloc(ptr, nmemb * size);
}

ary_xalloc_t ary_xrealloc = ary_xrealloc_builtin;
static ary_xdealloc_t ary_xfree = free;

void ary_cb_freevoidptr(void *buf, void *userp)
{
        (void)userp;
        ary_xfree(*(void **)buf);
}

void ary_cb_freecharptr(void *buf, void *userp)
{
        (void)userp;
        ary_xfree(*(char **)buf);
}

int ary_cb_cmpint(const void *a, const void *b)
{
        int x = *(const int *)a, y = *(const int *)b;

        return x > y ? 1 : x < y ? -1 : 0;
}

int ary_cb_strcmp(const void *a, const void *b)
{
        return strcmp(*(char **)a, *(char **)b);
}

static const size_t snprintf_bufsize = 32;

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->len && ary->dtor) {
                ary_elemcb_t dtor = ary->dtor;
                char *elem = ary->buf;
                void *userp = ary->userp;
                size_t i;

                for (i = ary->len; i--; elem += ary->sz)
                        dtor(elem, userp);
        }
        ary_xfree(ary->buf);
}

void *(ary_detach)(struct aryb *ary, size_t *ret)
{
        void *buf;

        (ary_shrinktofit)(ary);
        buf = ary->buf;
        if (ret)
                *ret = ary->len;
        ary->alloc = ary->len = 0;
        ary->buf = NULL;
        return buf;
}

int (ary_shrinktofit)(struct aryb *ary)
{
        void *buf;

        if (ary->alloc == ary->len)
                return 1;
        if (ary->len) {
                buf = ary_xrealloc(ary->buf, ary->len, ary->sz);
                if (!buf)
                        return 0;
        } else {
                ary_xfree(ary->buf);
                buf = NULL;
        }
        ary->alloc = ary->len;
        ary->buf = buf;
        return 1;
}

void *(ary_splicep)(struct aryb *ary, size_t pos, size_t rlen, size_t alen)
{
        char *buf;

        if (pos > ary->len)
                pos = ary->len;
        if (rlen > ary->len - pos)
                rlen = ary->len - pos;
        if (alen > rlen && !(ary_grow)(ary, alen - rlen))
                return NULL;
        buf = (char *)ary->buf + (pos * ary->sz);
        if (rlen && ary->dtor) {
                ary_elemcb_t dtor = ary->dtor;
                char *elem = buf;
                void *userp = ary->userp;
                size_t i;

                for (i = rlen; i--; elem += ary->sz)
                        dtor(elem, userp);
        }
        if (rlen != alen && pos < ary->len)
                memmove(buf + (alen * ary->sz), buf + (rlen * ary->sz),
                        (ary->len - pos - rlen) * ary->sz);
        ary->len = ary->len - rlen + alen;
        return buf;
}

int (ary_index)(struct aryb *ary, size_t *ret, size_t start, const void *data,
                ary_cmpcb_t comp)
{
        size_t i;
        char *elem = (char *)ary->buf + (start * ary->sz);

        for (i = start; i < ary->len; i++, elem += ary->sz) {
                if (comp ? !comp(elem, data) : !memcmp(elem, data, ary->sz)) {
                        if (ret)
                                *ret = i;
                        return 1;
                }
        }
        return 0;
}

int (ary_join)(struct aryb *ary, char **ret, const char *sep,
               ary_joincb_t stringify)
{
        struct ary_char strbuf;
        char *elem = (char *)ary->buf, *tmp;
        size_t seplen = sep ? strlen(sep) : 0, i, len;
        int tmplen, tmpret;

        if (!ary_init(&strbuf, 1024))
                goto error;
        for (i = 0; i < ary->len; i++, elem += ary->sz) {
                if (stringify) {
                        tmplen = stringify(&tmp, elem);
                        if (tmplen > 0) {
                                tmpret = ary_splice(&strbuf, strbuf.len, 0,
                                                    tmp, tmplen);
                                ary_xfree(tmp);
                                if (!tmpret)
                                        goto error;
                        } else if (!tmplen) {
                                ary_xfree(tmp);
                        }
                } else {
                        tmp = *(char **)elem;
                        if (!tmp)
                                continue;
                        if (!ary_splice(&strbuf, strbuf.len, 0, tmp,
                                        strlen(tmp)))
                                goto error;
                }
                if (seplen) {
                        if (!ary_splice(&strbuf, strbuf.len, 0, sep, seplen))
                                goto error;
                }
        }
        if (!strbuf.len) {
                if (!(*ret = strdup("")))
                        goto error;
                return 0;
        }
        ary_setlen(&strbuf, strbuf.len - seplen + 1);
        strbuf.buf[strbuf.len - 1] = '\0';
        ary_shrinktofit(&strbuf);
        *ret = ary_detach(&strbuf, &len);
        return (int)len;

error:
        ary_release(&strbuf);
        *ret = NULL;
        return -1;
}

/* .... */
\$\endgroup\$
  • \$\begingroup\$ The /* ... */ suggests that you've omitted some of the code. Is the entire project contained in the question as it is? If so, what's been snipped? \$\endgroup\$ – Nic Hartley Jun 30 '16 at 4:42
  • \$\begingroup\$ I have edited my question to answer yours. \$\endgroup\$ – netcat Jun 30 '16 at 8:19
2
\$\begingroup\$

Some lesser observations,

  1. 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;       \
     ...
    
  2. 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);
    }
    
  3. 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) {
    
  4. ary.c should include files like strcmp.h and not count on #include "ary.h" to have included them.

  5. 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;
    
  6. Without clear code understanding, I have a bit concern about strbuf.buf[strbuf.len - 1]. Should strbuf.len == 0, disastrous result would occur.

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

    #endif /* ARY_H */
    
  8. 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;
    
\$\endgroup\$
  • \$\begingroup\$ Thanks for your observations. 1. This works for struct aryb, but not for ary itself as it's generic. So you can't guarantee that aryis always evaluated only once, also many macros are one-liners to return a value, a do {} while-construct wouldn't be possible in this case. That's why I included the note "It's not safe to pass parameters that have side effects". 2. I chose 32 because I have multiple callbacks like inttostr, doubletostr, voidptrtostrand I thought that this would be a reasonable size for their buffers. \$\endgroup\$ – netcat Jul 2 '16 at 14:19
  • \$\begingroup\$ 3. However, ary_freebuf frees the array's buffer, not the array itself. So in free(NULL) the NULL would be the array's buffer, not the array itself, and this works because it's simply forwarded to ary_xfree(ary->buf). 6. That's what the if (!strbuf.len) { is for. I'll look into the rest, thanks. \$\endgroup\$ – netcat Jul 2 '16 at 14:19
  • \$\begingroup\$ @netcat #6 - I did not notice if (!strbuf.len) {, that part of code is good. Confident strbuf.len + 1 >= seplen alwasy true in ary_setlen(&strbuf, strbuf.len - seplen + 1);? \$\endgroup\$ – chux Jul 2 '16 at 16:20
  • \$\begingroup\$ Yes, because if there are elements and a separator, then strbuf ends with the separator and it gets cut off with that line. \$\endgroup\$ – netcat Jul 3 '16 at 2:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.