Dynamic String Manipulation Nano-Library

Ángel Ortega

Are you bored of re-writing partial, defective memory growing string manipulation code in C, over and over? Don't suffer anymore. Cut and paste this into one of your C headers and never worry again.

Here it is:

/* dynamic string manipulation nano-library */
#ifndef ds_init
struct ds { char * d; int p; int s; };
#define ds_init(x) do { x.d = (char *)0; x.p = x.s = 0; } while(0)
#define ds_rewind(x) x.p = 0;
#define ds_free(x) do { if (x.d) free(x.d); ds_init(x); } while(0)
#define ds_redim(x) do { if (x.p >= x.s) x.d = realloc(x.d, x.s += 32); } while(0)
#define ds_poke(x,c) do { ds_redim(x); x.d[x.p++] = c; } while(0)
#define ds_pokes(x,t) do { char * p = t; while (*p) ds_poke(x, *p++); } while(0)
#endif /* ds_init */

It's just ten lines (including its name in a comment and two lines of redefining protection). They are implemented as macros; this means that you should not abuse them, as your code will be bloated. On the other hand, you don't have to worry about linking nor adding it to your building system. They use realloc() and free(), so be sure to include stdlib.h.

This example loads a file into a dynamic string:

FILE * f;
struct ds s;

if ((f = fopen("file.txt", "r")) != NULL) {
    int c;

    ds_init(s);

    while ((c = fget(f)) != EOF)
        ds_poke(s, c);
    ds_poke(s, '\0');

    fclose(f);
}
/* the loaded file is in s.d */

By using ds_poke(), you add a char into the string, that is automatically expanded as needed to make room for it. The struct ds has three elements: the dynamic string itself (d), the length of the string (p) and the real length of the allocated block (s). Blocks are expanded in multiples of 32 bytes.

Other available macros are ds_init(), to init a dynamic string, ds_free(), to free it, ds_rewind(), to restart the insertion of characters to the beginning of the string, and ds_pokes(), a convenience function to store a null-terminated string by calling ds_poke() repeatedly. ds_redim() ensures there is room for a new char; it's internal and you must not use it. As you can see, it has new datatypes, public, internal and convenience code; not too bad for ten lines.

The Dynamic String Manipulation Nano-Library has a sister, with the same name and purpose, but implemented as code instead of macros:

/* dynamic string manipulation: prototypes */
struct ds { char * d; int p; int s; };
void ds_init(struct ds * x);
void ds_rewind(struct ds * x);
void ds_free(struct ds * x);
void ds_poke(struct ds * x, int c);
void ds_pokes(struct ds * x, char * t);

/* dynamic string manipulation: implementation */
void ds_init(struct ds * x) { x->d = (char *)0; x->p = x->s = 0; }
void ds_rewind(struct ds * x) { x->p = 0; }
void ds_free(struct ds * x) { if (x->d != NULL) free(x->d); ds_init(x); }
void ds_redim(struct ds * x) { if (x->p >= x->s) x->d = realloc(x->d, x->s += 32); }
void ds_poke(struct ds * x, int c) { ds_redim(x); x->d[x->p++] = c; }
void ds_pokes(struct ds * x, char * t) { while (*t) ds_poke(x, *t++); }

The first stanza contains the prototypes, and the second one the code. Obviously, this version lacks the glamour of the previous one.