Bug: Initial allocation not cleared to zero
Your initial allocation of sb->mem uses malloc instead of calloc, so its contents are uninitialized. If you then append a few characters and call sb_as_string(), you will get back a string that is not properly terminated. You should use calloc instead.
Minor bug
If your call to realloc fails, your buffer will be incorrect because it will no longer be null terminated (you just appended a character to the last spot). You should either rewrite a '\0' to the end of the buffer if realloc fails, or do the realloc before you append the character.
Argument check
When creating a string buffer, you should handle the case where init_cap is passed in as 0. You can set it to some default value in that case. Right now, an initial capacity of 0 will cause a crash down the line because your append function will append to a zero length buffer without ever reallocating.
Usability
I would much prefer an append function that took a string argument instead of a character argument. I'm not sure that I would ever need to append one character at a time.
Also, it might be nice to have a to_string type function that returns the string but also frees the stringbuilder. The way you currently have it, you can retrieve the string, but if you subsequently free the stringbuilder it will also free the string you just retrieved. That makes it difficult to use the string because its lifetime is tied to the lifetime of the stringbuilder.
I've written a bunch of code that prints values of various different types which works great but rather than printing to stdout I'd like to capture the result in a big string. In other languages this would be achieved by appending to a string builder or appending strings to an array and then concatenating them all. Is there an idiomatic way to do this in vanilla C? If not, what library would you recommend (M1/M2 Mac OS and Raspberry Pi)?
strings - StringBuilder in C - Code Review Stack Exchange
c# - StringBuilder: how to get the final String? - Stack Overflow
How to convert from string to stringbuilder? - Post.Byes - Bytes
stringbuilder to string[]
Bug: Initial allocation not cleared to zero
Your initial allocation of sb->mem uses malloc instead of calloc, so its contents are uninitialized. If you then append a few characters and call sb_as_string(), you will get back a string that is not properly terminated. You should use calloc instead.
Minor bug
If your call to realloc fails, your buffer will be incorrect because it will no longer be null terminated (you just appended a character to the last spot). You should either rewrite a '\0' to the end of the buffer if realloc fails, or do the realloc before you append the character.
Argument check
When creating a string buffer, you should handle the case where init_cap is passed in as 0. You can set it to some default value in that case. Right now, an initial capacity of 0 will cause a crash down the line because your append function will append to a zero length buffer without ever reallocating.
Usability
I would much prefer an append function that took a string argument instead of a character argument. I'm not sure that I would ever need to append one character at a time.
Also, it might be nice to have a to_string type function that returns the string but also frees the stringbuilder. The way you currently have it, you can retrieve the string, but if you subsequently free the stringbuilder it will also free the string you just retrieved. That makes it difficult to use the string because its lifetime is tied to the lifetime of the stringbuilder.
To be more comprehensive, the line in sb_append():
memset(new_mem + to->cap, 0, to->cap);
should become:
memset(new_mem + to->cap, 0, (to->cap) * (LOAD_FACTOR - 1));
That matters when LOAD_FACTOR is set to something greater than 2.
thanks for sharing this code.
You can use .ToString() to get the String from the StringBuilder.
When you say "it's faster to concatenate strings with a StringBuilder", this is only true if you are repeatedly (I repeat - repeatedly) concatenating to the same object.
If you're just concatenating 2 strings and doing something with the result immediately as a string, there's no point to using StringBuilder.
I just stumbled on Jon Skeet's nice write up of this:
https://jonskeet.uk/csharp/stringbuilder.html
If you are using StringBuilder, then to get the resulting string, it's just a matter of calling ToString() (unsurprisingly).