After years of criticizing others, I've finally found the time and worked up the courage to polish up one of my bits of code and solicit criticisms of my own.
This is a simple dynamic-string library that I wrote for one of my old C projects (don't judge too harshly if you look into it -- it's a few years old, and I've just begun revising it).
Some notes on the design:
- Its interface is fairly heavily influenced by C++'s
std::string
- Internally, it manages a dynamically allocated character buffer
- It is encoding agnostic -- it thinks in bytes, not true characters
- For academic interest, I went ahead and did a naive small string optimization
Please note that this is not intended to be a fully functioning, Swiss army knife of a string library. The functionality is very limited, and that's the way I intended it. All it is used for is corralling an HTTP request response into something a bit safer and more flexible than a bare manually managed c-string. There is a possibility that it may grow more complex though, so anything in the interface that could prohibit that would be of great interest to me.
To my knowledge, it is fully compliant to the C99 standard.
string_buffer.h
#ifndef UPMON_STRING_BUFFER_H
#define UPMON_STRING_BUFFER_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdbool.h>
typedef struct StringBuffer {
char* str;
size_t len;
size_t cap;
char small_str[64];
} StringBuffer;
void string_buffer_init(StringBuffer* s);
void string_buffer_cleanup(StringBuffer* s);
const char* string_buffer_cstr(const StringBuffer* s);
size_t string_buffer_length(const StringBuffer* s);
bool string_buffer_set_bytes(StringBuffer* s, const char* str, size_t len);
bool string_buffer_set_cstr(StringBuffer* s, const char* str);
bool string_buffer_set_string_buffer(StringBuffer* dst, const StringBuffer* src);
bool string_buffer_append_bytes(StringBuffer* s, const char* str, size_t len);
bool string_buffer_append_cstr(StringBuffer* s, const char* str);
bool string_buffer_append_string_buffer(StringBuffer* dst, const StringBuffer* src);
void string_buffer_clear(StringBuffer* s);
#ifdef __cplusplus
}
#endif
#endif
string_buffer.c
#include "string_buffer.h"
#include <stdlib.h>
#include <string.h>
void string_buffer_init(StringBuffer* s) {
s->small_str[0] = '\0';
s->str = NULL;
s->len = 0;
s->cap = sizeof(s->small_str);
}
void string_buffer_cleanup(StringBuffer* s) {
free(s->str);
s->len = 0;
s->cap = sizeof(s->small_str);
}
const char* string_buffer_cstr(const StringBuffer* s) {
return (s->cap <= sizeof(s->small_str)) ? s->small_str : s->str;
}
static char* string_buffer_buf(StringBuffer* s) {
return (char*) string_buffer_cstr(s);
}
size_t string_buffer_length(const StringBuffer* s) {
return s->len;
}
// Currently this is a hidden function, but if ever necessitated, it can be
// exposed in the interface.
static bool string_buffer_reserve(StringBuffer* s, size_t min_cap) {
if (s->cap >= min_cap) {
return true;
}
char* new_buf = realloc(s->str, min_cap);
if (new_buf == NULL) {
return false;
}
// If we're moving from small_str to a buffer, we need to copy over the small_str.
if (s->str == NULL) {
memcpy(new_buf, s->small_str, sizeof(s->small_str));
}
s->str = new_buf;
s->cap = min_cap;
return true;
}
bool string_buffer_set_bytes(StringBuffer* s, const char* str, size_t len) {
if (!string_buffer_reserve(s, len + 1)) {
return false;
}
char* buf = string_buffer_buf(s);
memcpy(buf, str, len);
buf[len] = '\0';
s->len = len;
return true;
}
bool string_buffer_set_cstr(StringBuffer* s, const char* str) {
return string_buffer_set_bytes(s, str, strlen(str));
}
bool string_buffer_set_string_buffer(StringBuffer* dst, const StringBuffer* src) {
return string_buffer_set_bytes(dst, string_buffer_cstr(src), string_buffer_length(src));
}
bool string_buffer_append_bytes(StringBuffer* s, const char* str, size_t len) {
if (!string_buffer_reserve(s, s->len + len + 1)) {
return false;
}
char* dst = string_buffer_buf(s) + s->len;
memcpy(dst, str, len);
dst[len] = '\0';
s->len += len;
return true;
}
bool string_buffer_append_cstr(StringBuffer* s, const char* str) {
return string_buffer_append_bytes(s, str, strlen(str));
}
bool string_buffer_append_string_buffer(StringBuffer* dst, const StringBuffer* src) {
return string_buffer_append_bytes(dst, string_buffer_cstr(src), string_buffer_length(src));
}
void string_buffer_clear(StringBuffer* s) {
s->len = 0;
string_buffer_buf(s)[0] = '\0';
}
sizeof(StringBuffer)-1
bytes of the structure itself (via a union). I think I got this trick from libc++: The first bit indicates whether the SSO is active, the rest of the first byte is used as the size of the SSO string. This of course requires that the size is the first field. \$\endgroup\$