Lots of languages have something that lets you build up a dynamically-sized string with minimal overhead. C doesn't, and I found myself using code that did that manually in a couple of places, so I packaged it into a class. Note that I've only implemented functionality I'm using.
stringbuilder.h
:
#ifndef CONCATEN_STRINGBUILDER_H
#define CONCATEN_STRINGBUILDER_H
#include <stddef.h>
#include <stdbool.h>
struct stringbuilder_s;
typedef struct stringbuilder_s *stringbuilder_t;
stringbuilder_t sb_new(size_t);
bool sb_append(stringbuilder_t, char);
char *sb_as_string(stringbuilder_t);
void sb_free(stringbuilder_t);
#endif //CONCATEN_STRINGBUILDER_H
stringbuilder.c
:
#include <stdlib.h>
#include <string.h>
#include "stringbuilder.h"
struct stringbuilder_s {
char *mem;
size_t count;
size_t cap;
};
typedef struct stringbuilder_s *stringbuilder_t;
stringbuilder_t sb_new(size_t init_cap) {
stringbuilder_t ret = malloc(sizeof(struct stringbuilder_s));
if (!ret) return NULL;
ret->mem = calloc(init_cap, sizeof(char));
if (!ret->mem) return NULL;
ret->cap = init_cap;
ret->count = 0;
return ret;
}
#define LOAD_FACTOR 2
bool sb_append(stringbuilder_t to, char c) {
to->mem[to->count] = c;
++to->count;
if (to->count == to->cap) {
char *new_mem = realloc(to->mem, to->cap * LOAD_FACTOR);
if (!new_mem) {
return false;
}
memset(new_mem + to->cap, 0, to->cap);
to->mem = new_mem;
to->cap *= LOAD_FACTOR;
}
return true;
}
char *sb_as_string(stringbuilder_t sb) {
return sb->mem;
}
void sb_free(stringbuilder_t sb) {
free(sb->mem);
free(sb);
}
I'm interested specifically in:
- Performance. This code gets called a lot. I want it to be as fast as possible.
- Memory safety. While I'm fairly sure that this doesn't leak memory (assuming it's used properly), I'm not confident, and I'm not sure how to check.
- Edge cases. It works, as far as I can tell, but that doesn't mean that it's bug-free.
Tested here.
sb_new
: ifret = malloc(...)
succeeds, butret->mem = calloc(...)
fails (returnsNULL
), the memory atret
is never freed. \$\endgroup\$