Stricks
Managed C strings library.
Why ?
Because handling C strings is tedious and error-prone.
Appending while keeping track of length, null-termination, realloc, etc...
Speed is also a concern with excessive (sometimes implicit) calls to strlen
.
Principle
The stx_t
(or "strick") type is just a normal char*
string.
typedef const char* stx_t;
The trick
Header {
cap;
len;
canary;
flags;
char data[];
}
Header takes care of the string state and bounds.
The stx_t
type points directly to the data
member.
Header and data occupy a single block of memory (an "SBlock"),
avoiding the indirection you find in {len,*str}
schemes.
This technique is used notably in antirez SDS.
The SBlock is invisible to the user, who only passes stx_t
to and from.
The convenience is, being really char*
, stricks can be passed to any (non-modifying) <string.h>
function.
The above layout is simplified. In reality, Stricks uses two header types to optimize space, and houses the canary and flags in a separate struct
.
Security
Stricks aims at limiting memory faults through the API :
- Typedef
const char*
forces the user to cast when she wants to write. - All API methods check for a valid Header.
- If invalid, no action is taken and a falsy value is returned.
(See stx_free)
Usage
// app.c
#include <stdio.h>
#include "stx.h"
int main() {
stx_t s = stx_from("Stricks");
stx_append_alloc (&s, " are treats!");
printf(s);
return 0;
}
$ gcc app.c libstx -o app && ./app
Stricks are treats!
Sample
example/forum.c implements a mock forum with a fixed size page buffer.
When the next post would truncate, the buffer is flushed.
make && cd example && ./forum
Build & unit-test
make && make check
API
stx_new
stx_from
stx_from_len
stx_dup
stx_load
stx_free
stx_reset
stx_update
stx_trim
stx_show
stx_resize
stx_check
stx_equal
stx_split
stx_append / stx_cat
stx_append_count / stx_ncat
stx_append_format / stx_catf
stx_append_alloc / stx_cata
stx_append_count_alloc / stx_ncata
Custom allocator and destructor can be defined with
#define STX_MALLOC my_allocator
#define STX_REALLOC my_realloc
#define STX_FREE my_free
stx_new
Allocates and inits a new strick of capacity cap
.
stx_t stx_new (size_t cap)
stx_from
Creates a new strick by copying string src
.
stx_t stx_from (const char* src)
Capacity is adjusted to length.
stx_t s = stx_from("Stricks");
stx_show(s);
// cap:7 len:7 data:'Stricks'
stx_from_len
Creates a new strick with at most len
bytes from src
.
stx_t stx_from_len (const char* src, size_t len)
If len > strlen(src)
, the resulting capacity is len
.
Capacity gets trimmed down to length.
stx_t s = stx_from_len("Stricks", 7);
stx_show(s);
// cap:7 len:7 data:'Stricks'
stx_t s = stx_from_len("Stricks", 10);
stx_show(s);
// cap:10 len:7 data:'Stricks'
stx_t s = stx_from_len("Stricks", 3);
stx_show(s);
// cap:3 len:3 data:'Str'
stx_dup
Creates a duplicate strick of src
.
stx_t stx_dup (stx_t src)
Capacity gets trimmed down to length.
stx_t s = stx_new(16);
stx_cat(s, "foo");
stx_t dup = stx_dup(s);
stx_show(dup);
// cap:3 len:3 data:'foo'
stx_load
Read string from file.
stx_t stx_load (const char* src_path)
stx_cap
Current capacity accessor.
size_t stx_cap (stx_t s)
stx_len
Current length accessor.
size_t stx_len (stx_t s)
stx_spc
Remaining space.
size_t stx_spc (stx_t s)
stx_reset
Sets data length to zero.
void stx_reset (stx_t s)
stx_t s = stx_new(16);
stx_cat(s, "foo");
stx_reset(s);
stx_show(s);
// cap:16 len:0 data:''
stx_free
void stx_free (stx_t s)
Releases the enclosing SBlock.
Once the block is freed, no use-after-free or double-free should be possible through the Strick API :
stx_t s = stx_new(16);
stx_append(s, "foo");
stx_free(s);
// Use-after-free
stx_append(s, "bar");
// No action. Returns 0.
printf("%zu\n", stx_len(s));
// 0
// Double-free
stx_free(s);
// No action.
On first call, stx_free(s)
zeroes-out the header, erasing the canary
.
All subsequent API calls check the canary and do nothing if dead.
stx_resize
Change capacity.
bool stx_resize (stx_t *pstx, size_t newcap)
- If increased, the passed reference may get transparently updated.
- If lowered below length, data gets truncated.
Returns: true/false
on success/failure.
stx_t s = stx_new(3);
int rc = stx_cat(s, "foobar"); // -> -6
if (rc<0) stx_resize(&s, -rc);
stx_cat(s, "foobar");
stx_show(s);
// cap:6 len:6 data:'foobar'
stx_update
Sets len
straight in case data was modified from outside.
void stx_update (stx_t s)
stx_trim
Removes white space, left and right.
void stx_trim (stx_t s)
Capacity remains the same.
stx_split
Splits a strick or string on separator sep
into an array of stricks.
stx_t*
stx_split (const void* s, const char* sep, unsigned int *outcnt)
*outcnt
gets the array length.
stx_t s = stx_from("foo, bar");
unsigned cnt = 0;
stx_t* list = stx_split(s, ", ", &cnt);
for (int i = 0; i < cnt; ++i) {
stx_show(list[i]);
}
// cap:3 len:3 data:'foo'
// cap:3 len:3 data:'bar'
Or more comfortably (using the list sentinel)
while (part = *list++) {
stx_show(part);
}
stx_equal
Compares a
and b
's data string.
bool stx_equal (stx_t a, stx_t b)
- Capacities are not compared.
- Faster than
memcmp
since stored lengths are compared first.
stx_show
void stx_show (stx_t s)
Utility. Prints the state of s
.
stx_show(foo);
// cap:8 len:5 data:'hello'
stx_check
bool stx_check (stx_t s)
Check if s has a valid header.
stx_append
stx_cat
int stx_append (stx_t dst, const char* src)
Appends src
to dst
.
- No reallocation.
- Nothing done if input exceeds remaining space.
Return code :
rc >= 0
on success, as change in length.rc < 0
on potential truncation, as needed capacity.rc = 0
on error.
stx_t s = stx_new(5);
stx_cat(s, "abc"); //-> 3
printf("%s", s); // "abc"
stx_cat(s, "def"); //-> -6 (needs capacity = 6)
printf("%s", s); // "abc"
stx_append_count
stx_ncat
int stx_ncat (stx_t dst, const char* src, size_t n)
Appends at most n
bytes from src
to dst
.
- No reallocation.
- if
n
is zero,strlen(src)
is used. - Nothing done if input exceeds remaining space.
Return code :
rc >= 0
on success, as change in length.rc < 0
on potential truncation, as needed capacity.rc = 0
on error.
stx_t s = stx_new(5);
stx_ncat(s, "abc", 2); //-> 2
printf("%s", s); // "ab"
stx_append_format
stx_catf
int stx_catf (stx_t dst, const char* fmt, ...)
Appends a formatted c-string to dst
, in place.
- No reallocation.
- Nothing done if input exceeds remaining space.
Return code :
rc >= 0
on success, as increase in length.rc < 0
on potential truncation, as needed capacity.rc = 0
on error.
stx_t foo = stx_new(32);
stx_catf (foo, "%s has %d apples", "Mary", 10);
stx_show(foo);
// cap:32 len:18 data:'Mary has 10 apples'
stx_append_alloc
stx_cata
size_t stx_cata (stx_t *pdst, const char* src)
Appends src
to *pdst
.
- If over capacity,
*pdst
gets reallocated. - reallocation reserves 2x the needed memory.
Return code :
rc = 0
on error.rc >= 0
on success, as change in length.
stx_t s = stx_new(3);
stx_cat(s, "abc");
stx_cata(s, "def"); //-> 3
stx_show(s); // "cap:12 len:6 data:'abcdef'"
stx_append_count_alloc
stx_ncata
size_t stx_ncata (stx_t *pdst, const char* src, size_t n)
Append n
bytes of src
to *pdst
.
- If n is zero,
strlen(src)
is used. - If over capacity,
*pdst
gets reallocated.
Return code :
rc = 0
on error.rc >= 0
on success, as change in length.
TODO
- Slices / StringView
- Utf-8 ?
- More high-level methods ?