Stricks
Experimental managed C-strings library.
v0.2.0
📙 API
Why ?
C strings are hard, unassisted and risky.
Appending while keeping track of length, null-termination, realloc, etc...
They can also be slowed by excessive (sometimes implicit) calls to strlen
.
Say you're making a forum engine, where a page is a fixed buffer.
How to safely add posts without truncation ?
The hard way
char page[PAGE_SZ];
// Keep track
size_t page_len = 0;
while(1) {
char* user = db("user");
char* text = db("text");
// Will null be counted ? Lookup `snprintf`...
// In fact it won't !
int post_len = snprintf (
// Keep track
page + page_len,
// Will it be null-terminated ? Lookup `snprintf`...
// In fact it will !
PAGE_SZ - page_len,
"<div>%s<br>%s</div>", user, text
);
// Don't forget that '+1' !
if (page_len + post_len + 1 > PAGE_SZ) {
// Does not fit. Undo.
page[page_len] = 0;
break;
}
// Keep track
page_len += post_len;
}
The stricky way
stx_t page = stx_new(PAGE_SZ);
while(1) {
char* user = db("user");
char* text = db("text");
if (stx_catf(page, "<div>%s<br>%s</div>", user, text) <= 0)
break;
}
Quick sample
See example/forum.c for a Stricks implementation of the 'forum'.
make && cd example && ./forum
Principle
The stx_t
(or "strick") type is just a normal char*
string.
typedef char* stx_t;
The trick 😉 lies before the stx_t address :
Header {
cap;
len;
cookie;
flags;
char data[];
}
Header takes care of the string state and bounds-checking.
The stx_t
type points directly to the data
member.
This technique is used notably in antirez SDS.
Header and data occupy a single block of memory (an "SBlock"),
avoiding the further indirection you find in typical {len,*str}
schemes.
The SBlock is invisible to the user, who only passes stx_t
to and from the API.
Of course, being really a char*
, a strick can be passed to any
(non-modifying) <string.h>
function.
The above Header layout is simplified. In reality, Stricks defines several header types to optimize space for short strings, and houses the cookie and flags attributes in a separate struct
.
example usage
stx_t s = stx_from("Stricks", 0);
stx_cata(s, " rule!");
printf("%s\n", s);
//> Stricks rule!
Security
No memory fault should be possible through the Stricks API.
All methods check for a valid Header.
If not found, no action is taken and a falsy value gets returned.
(See stx_free)
API
stx_free
stx_reset
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 ...
#define STX_FREE ...
stx_new
Allocates and inits a new strick of capacity cap
.
stx_t stx_new (const size_t cap)
stx_from
Creates a new strick with at most n
bytes from src
.
stx_t stx_from (const char* src, const size_t n)
If n
is zero, strlen(src)
is used.
Capacity gets trimmed down to length.
stx_t s = stx_from("Stricks", 0);
stx_show(s);
// cap:7 len:7 data:'Stricks'
stx_dup
Creates a duplicate strick of src
.
stx_t stx_dup (const 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_cap
Current capacity accessor.
size_t stx_cap (const stx_t s)
stx_len
Current length accessor.
size_t stx_len (const stx_t s)
stx_spc
Remaining space.
size_t stx_spc (const stx_t s)
stx_reset
Sets data length to zero.
void stx_reset (const 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 underlying SBlock.
🍰 Security : Once the SBlock 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.
// Double-free
stx_free(s);
// No action.
How it works 🔧
On first call, stx_free(s)
zeroes-out the header, erasing the cookie
canary.
All subsequent API calls check for the canary, find it dead, then do nothing.
stx_resize
Change capacity.
bool stx_resize (stx_t *pstx, const 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_trim
Removes white space, left and right.
void stx_trim (const 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
receives the returned array length.
stx_t s = stx_from("foo, bar", 0);
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'
stx_equal
Compares a
and b
's data string.
bool stx_equal (const stx_t a, const stx_t b)
- Capacities are not compared.
- Faster than
memcmp
since stored lengths are compared first.
stx_show
void stx_show (const stx_t s)
Utility. Prints the state of s
.
stx_show(foo);
// cap:8 len:5 data:'hello'
stx_check
bool stx_check (const 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, const 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.
Usage
// app.c
#include <stdio.h>
#include "stx.h"
int main() {
char name[] = "Alco";
stx_t msg = stx_new(100);
stx_catf(msg, "Hello! My name is %s.", name);
puts(msg);
return 0;
}
$ gcc app.c libstx -o app && ./app
Hello! My name is Alco.
Build & unit-test
make && make check
TODO
- utf8
- Slices / StringView
- More high-level methods