The Melodist»Blog

Small C-String Helper Library (5/31/2017)

Hey, everyone!

I recently wrote up a quick header file that provides some useful operations pertaining to C-strings using the CRT. It's just at an early starting point, and I'll probably only implement additional features as I need them in my project, but it's already quite useful. If you have any suggestions, see any problems with the code, or want me to implement something, let me know.

Here's the code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#ifndef _DSTRING_H
#define _DSTRING_H

#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>

#define _DSTRING_STARTING_CAP 16

#define ds_len(a) ds_size(a)
#define ds_length(a) ds_size(a)
#define ds_free(a) _ds_free(&a)

typedef char *dstr;
typedef char *dstring;

inline uint32_t *_ds_raw(char *dstr) {
    return dstr ? ((uint32_t *)dstr) - 2 : NULL;
}

inline uint32_t ds_size(char *dstr) {
    return dstr ? _ds_raw(dstr)[0] : 0;
}

inline uint32_t ds_cap(char *dstr) {
    return dstr ? _ds_raw(dstr)[1] : 0;
}

inline char *_ds_grow(char *dstr, uint32_t required_chars) {
    if(ds_cap(dstr) < required_chars) {
        uint32_t new_cap = ds_cap(dstr) > _DSTRING_STARTING_CAP ? ds_cap(dstr) : _DSTRING_STARTING_CAP;
        while(required_chars >= new_cap) {
            new_cap = 3 * (new_cap / 2);
        }
        dstr = (char *)((uint32_t *)realloc(_ds_raw(dstr), new_cap * sizeof(char) + 2 * sizeof(uint32_t)) + 2);
        *(_ds_raw(dstr) + 1) = new_cap;
    }
    return dstr;
}

inline char *ds_new(const char *str, ...) {
    char *dstr = NULL;
    va_list args;
    va_start(args, str);
    size_t required_len = vsnprintf(NULL, 0, str, args) + 1;
    va_end(args);

    dstr = _ds_grow(dstr, required_len);

    va_start(args, str);
    vsprintf(dstr, str, args);
    va_end(args);

    *(_ds_raw(dstr)) = required_len;
    return dstr;
}

inline void _ds_free(char **str) {
    if(*str) {
        free(_ds_raw(*str));
    }
    *str = NULL;
}

inline char *ds_add_s(char *str, const char *add) {
    if(str) {
        uint32_t new_len = ds_size(str) + strlen(add) + 1;
        str = _ds_grow(str, new_len);
        strcpy(str + ds_size(str), add);
        *(_ds_raw(str)) = new_len;
    }
    else {
        str = ds_new("%s", add);
    }
    return str;
}

inline char *ds_add_c(char *str, char c) {
    char c_str[2] = { c, 0 };
    str = ds_add_s(str, c_str);

    return str;
}

inline char *ds_add_i(char *str, int i) {
    char *i_str = ds_new("%i", i);
    str = ds_add_s(str, i_str);
    ds_free(i_str);

    return str;
}

inline char *ds_add_f(char *str, double f) {
    char *i_str = ds_new("%lf", f);
    str = ds_add_s(str, i_str);
    ds_free(i_str);

    return str;
}

inline char *ds_erase(char *str, uint32_t i) {
    str = (char *)memmove(str + i, str + i + 1, ds_size(str) - i - 1);
    (_ds_raw(str))[0]--;

    if((_ds_raw(str))[0] < 1) {
        ds_free(str);
    }

    return str;
}

#endif


Here's some sample usage:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
dstring a = ds_new("This is a string.");
dstring b = ds_new("This is a string with an int. %i", 12345);
dstring c = ds_new("This is a string with a double. %f", 123.45);

dstring d = NULL;
d = ds_add_s(d, "I can add a string to a string.");

dstring e = NULL;
d = ds_add_i(d, 12345); //or an int (or a double or a char)

dstring f = ds_new("I can erase from a string too");
f = ds_erase(f, 0);

//you might want to free everything once you're done
ds_free(a);
ds_free(b);
ds_free(c);
ds_free(d);
ds_free(e);
ds_free(f);


-Delix
Mārtiņš Možeiko, Edited by Mārtiņš Možeiko on
You don't need "l" modifier for "%f" formatter. %f accepts double as argument - l has no effect. For <C99 it's actually not valid modifier (not sure if it is undefined behavior or not).

You don't need to allocate in ds_add_i/ds_add_f functions in same way you don't need to allocate in ds_add_c function:
1
2
3
    char i_str[32];
    sprintf(i_str, "%i", i);
    return ds_add_s(str, i_str);

Also you could improve ds_add_s by directly accepting varargs:
ds_add_s(char* str, const char* add, ...).
Then do vsprintf instead of strcpy. Then you will be able to call ds_add with whatever type and formatter directly.
Ryan Fleury,
Thanks for the suggestions, mmozeiko; all good ones. :) Will post updated code soon!