Augmented strings for C
đź“™ API
Stricks make C strings easy and fast.
Managing bounds, storage and reallocations is automated,
without forcing a structured type on the user.
typedef const char* stx_t;
A strick looks like a normal char*
and can be passed to <string.h>
functions.
Metadata are hidden in a prefix header :
Header and string occupy a continuous memory block,
avoiding the further indirection of {len,*str}
schemes.
This technique is notably used in SDS,
now part of Redis.
#include <stdio.h>
#include "stx.h"
int main() {
stx_t s = stx_from("Treats or...");
stx_append(&s, "Stricks!");
printf(s);
return 0;
}
$ gcc app.c stx -o app && ./app
Treats or...Stricks!
src/example.c implements a mock forum with fixed size pages.
When the next post would truncate, the buffer is flushed.
make && ./bin/example
make && make check
make && make bench
(may use 1GB+ RAM)
Stricks is (much) faster than SDS.
On Intel Core i3 :
init and free
---------------
8 bytes strings :
SDS 568 ms
Stricks 445 ms
256 bytes strings :
SDS 667 ms
Stricks 654 ms
append
---------------
8 bytes strings :
SDS 643 ms
Stricks 372 ms
256 bytes strings :
SDS 449 ms
Stricks 171 ms
append format
---------------
SDS 832 ms
Stricks 795 ms
split and join
---------------
8 bytes strings :
SDS 580 ms
Stricks 224 ms
256 bytes strings :
SDS 714 ms
Stricks 75 ms
C++ benchmark :
(depends on libbenchmark-dev)
make && make benchcpp
On Thinkpad T420 with cpupower frequency-set --governor performance
:
------------------------------------------------------------
Benchmark Time CPU Iterations
------------------------------------------------------------
SDS_from/8 33 ns 33 ns 21363820
SDS_from/64 29 ns 29 ns 23942248
SDS_from/512 29 ns 29 ns 23982314
SDS_from/4096 271 ns 271 ns 2585840
SDS_from/32768 2738 ns 2738 ns 255377
STX_from/8 21 ns 21 ns 33747602
STX_from/64 20 ns 20 ns 35346041
STX_from/512 20 ns 20 ns 35170533
STX_from/4096 170 ns 170 ns 4104671
STX_from/32768 1462 ns 1462 ns 467821
SDS_append/8 14 ns 14 ns 48313704
SDS_append/64 12 ns 12 ns 57793801
SDS_append/512 12 ns 12 ns 57707384
SDS_append/4096 1617 ns 1617 ns 426224
SDS_append/32768 13175 ns 13174 ns 50770
STX_append/8 9 ns 9 ns 72565984
STX_append/64 9 ns 9 ns 74644788
STX_append/512 10 ns 10 ns 75197471
STX_append/4096 886 ns 886 ns 776564
STX_append/32768 7035 ns 7033 ns 86241
SDS_split_join/8 13 us 13 us 52485
SDS_split_join/64 42 us 42 us 16515
SDS_split_join/512 263 us 263 us 2647
SDS_split_join/4096 2050 us 2050 us 339
SDS_split_join/32768 17462 us 17461 us 40
STX_split_join/8 5 us 5 us 152980
STX_split_join/64 6 us 6 us 112486
STX_split_join/512 16 us 16 us 43388
STX_split_join/4096 108 us 108 us 6412
STX_split_join/32768 1707 us 1707 us 404
stx_new
stx_from
stx_from_len
stx_dup
stx_split
stx_join
stx_join_len
stx_append
stx_append_strict
stx_append_fmt
stx_append_fmt_strict
stx_resize
stx_adjust
stx_trim
stx_reset
stx_cap
stx_len
stx_spc
stx_equal
stx_dbg
Custom allocators can be defined with
#define STX_MALLOC my_malloc
#define STX_REALLOC my_realloc
#define STX_FREE my_free
Create a strick of capacity cap
.
stx_t stx_new (size_t cap)
stx_t s = stx_new(10);
stx_dbg(s);
//cap:10 len:0 data:""
Create a strick by copying string src
.
stx_t stx_from (const char* src)
stx_t s = stx_from("Bonjour");
stx_dbg(s);
// cap:7 len:7 data:'Bonjour'
Create a strick by copying srclen
bytes from src
.
stx_t stx_from_len (const void* src, size_t srclen)
This function is binary : exactly srclen
bytes will be copied, NULs included.
If you need the stx.len
property to reflect its C-string length, use stx_adjust
on the result.
stx_t s = stx_from_len("Hello!", 4);
printf(s); // 'Hell'
Create a duplicate strick of src
.
stx_t stx_dup (stx_t src)
Capacity is adjusted to length.
stx_t s = stx_new(16);
stx_append(&s, "foo");
stx_t dup = stx_dup(s);
stx_dbg(dup);
// cap:3 len:3 data:'foo'
Split src
on separator sep
into an array of stricks of resulting length *outcnt
.
stx_t*
stx_split (const char* src, const char* sep, int* outcnt);
int cnt = 0;
stx_t* list = stx_split("foo|bar", "|", &cnt);
for (int i = 0; i < cnt; ++i) {
stx_dbg(list[i]);
}
// cap:3 len:3 data:'foo'
// cap:3 len:3 data:'bar'
Or using the list sentinel :
while (part = *list++) {
stx_dbg(part);
}
Core split method with arbitrary lengths.
stx_t*
stx_split_len (const char* src, size_t srclen, const char* sep, size_t seplen, int* outcnt)
Join the stricks list
of length count
using separator sep
into a new strick.
stx_t stx_join (stx_t *list, int count, const char* sep);
int count = 0;
stx_t* parts = stx_split ("foo|bar", "|", &count);
stx_t joined = stx_join (parts, count, "|");
printf(joined); // "foo|bar"
Same with known separator length.
stx_t stx_join_len (stx_t *list, int count, const char* sep, size_t seplen);
stx_cat
size_t stx_append (stx_t* dst, const void* src, size_t srclen)
Appends len
bytes from src
to *dst
.
- If over capacity,
*dst
gets reallocated. - reallocation reserves 2x the needed memory.
Return code :
rc = 0
on error.rc >= 0
on success, as new length.
stx_t s = stx_from("abc");
stx_append(&s, "def"); //-> 6
stx_dbg(s);
// cap:12 len:6 data:'abcdef'
stx_cats
long long stx_append_strict (stx_t dst, const char* src, size_t srclen)
Appends len
bytes from src
to dst
, in place.
- No reallocation.
- Nothing done if input exceeds remaining space.
Return code :
rc >= 0
on success, as new length.rc < 0
on potential truncation, as needed capacity.rc = 0
on error.
stx_t s = stx_new(5);
stx_append_strict(s, "abc"); //-> 3
printf(s); // "abc"
stx_append_strict(s, "def"); //-> -6 (would need capacity = 6)
printf(s); // "abc"
stx_catf
size_t stx_append_fmt (stx_t* dst, const char* fmt, ...)
Appends a formatted string to *dst
.
- If over capacity,
*dst
gets reallocated. - reallocation reserves 2x the needed memory.
Return code :
rc = 0
on error.rc >= 0
on success, as new length.
stx_t foo = stx_new(32);
stx_append_fmt (&foo, "%s has %d apples", "Mary", 10);
stx_dbg(foo);
// cap:32 len:18 data:'Mary has 10 apples'
stx_catfs
long long stx_append_fmt_strict (stx_t dst, const char* fmt, ...)
Appends a formatted string to dst
, in place.
- No reallocation.
- Nothing done if input exceeds remaining space.
Return code :
rc >= 0
on success, as new length.rc < 0
on potential truncation, as needed capacity.rc = 0
on error.
stx_t foo = stx_new(32);
stx_append_fmt (foo, "%s has %d apples", "Mary", 10);
stx_dbg(foo);
// cap:32 len:18 data:'Mary has 10 apples'
Releases the enclosing memory block.
void stx_free (stx_t s)
stx_t s = stx_from("foo");
stx_free(s);
Releases a list of parts generated by stx_split.
void stx_list_free (const stx_t* list)
int cnt = 0;
stx_t* list = stx_split("foo|bar", "|", &cnt);
// (..use list..)
stx_list_free(list);
Sets length to zero.
void stx_reset (stx_t s)
stx_t s = stx_new(16);
stx_append_strict(s, "foo");
stx_reset(s);
stx_dbg(s);
// cap:16 len:0 data:''
Change capacity.
int stx_resize (stx_t *pstx, size_t newcap)
- If increased, the passed reference may get updated.
- If lowered below length, data gets truncated.
Returns: true/false
on success/failure.
stx_t s = stx_new(3);
int rc = stx_append_strict(s, "foobar"); // -> -6
if (rc<0) stx_resize(&s, -rc);
stx_append_strict(s, "foobar");
stx_dbg(s);
// cap:6 len:6 data:'foobar'
Sets len
straight in case data was modified from outside.
void stx_adjust (stx_t s)
stx_t s = stx_from("foobar");
// out-of-API modification
((char*)s)[3] = '\0';
stx_dbg(s);
// cap:6 len:6 data:'foo' WRONG LEN!
stx_adjust(s);
stx_dbg(s);
// cap:6 len:3 data:'foo' FAITHFUL
Removes white space, left and right, preserving capacity.
void stx_trim (stx_t s)
stx_t s = stx_from(" foo ");
stx_trim(s);
stx_dbg(s);
// cap:5 len:3 data:'foo'
Current capacity accessor.
size_t stx_cap (stx_t s)
Current length accessor.
size_t stx_len (stx_t s)
Remaining space.
size_t stx_spc (stx_t s)
Compares a
and b
's data string.
int stx_equal (stx_t a, stx_t b)
- Capacities are not compared.
- Faster than
memcmp
since stored lengths are compared first.
Printing the state of a strick.
stx_t s = stx_from("foo");
stx_dbg(foo);
// cap:3 len:3 data:'foo'