pstr — a set of cautious and easy-to-use C string functions
This is a set of functions that allows you to more easily work with static C strings while preventing buffer overflows and truncation, so you don't have to worry worrying about safety as much. pstr has two principles:
1. If a string doesn't fit, it stops what it's doing and tells you
Functions like strcat, strncat and strlcat try to fit as much of the source string
into the destination buffer as possible. This means that you either end up with safety
issues like buffer overflows, or in the best case scenario, truncated strings, which can
also present problems.
strlcat(dest, "this string is way too big", 10);
// concatenated `dest` is truncated, and you have to do an extra check to detect thispstr never overflows (hopefully), never truncates strings, and always adds the "\0"
terminator. Most functions return a bool to represent whether the operation succeeded.
For example, in pstr_cat, if the strings don't fit into the buffer, it returns false
without changing anything.
pstr_cat(dest, 10, "this string is way too big");
// returns false without changing `dest`2. It's easier to work with (than <string.h> functions)
Even when everything fits inside your buffer and you don't have to worry about safety, doing simple things such as concatenating multiple strings can be a pain in C. pstr functions make this a little bit simpler. For example:
pstr_vcat(dest, dest_size, " Hello", " there ", name, "!", NULL);pstr also comes with tests which you can run with make run-test, for
what that's worth.
Documentation
pstr is very small, so I would recommend directly copying pstr.h and pstr.c into your
project.
It works with standard C strings, so you can simply create a stack-allocated string as
you normally would, e.g. char address[30];.
Usage instructions are included below, and you can find documentation of all functions in pstr.h.
Concatenation
Concatenating strings is probably the most common operation, for me at least. The nicest
way to do it using pstr is by using pstr_vcat(), which takes a destination buffer and
its size, as well as a list of strings, followed by NULL. Remember to write the NULL
at the end or bad things will happen. As with all pstr functions, the "\0" terminator
is added automatically to the string.
char address[35];
char street[] = "Rheingasse 5";
char city[] = "Basel";
char country[] = "Switzerland";
pstr_vcat(address, 35, street, ", ", city, ", ", country, NULL);
// `address` now contains "Rheingasse 5, Basel, Switzerland\0"Like most pstr methods, pstr_vcat() returns a bool which tells you whether the
operation succeeded or not. pstr_vcat() returns false if there wasn't enough space,
and you should check for this.
char address[25];
char street[] = "Rheingasse 5";
char city[] = "Basel";
char country[] = "Switzerland";
if (!pstr_vcat(address, 25, street, ", ", city, ", ", country, NULL)) {
// `address` is not big enough, so we'll end up here, and you can do something to handle
// the problem
// `address` is now "\0"
}
Most pstr methods leave the destination buffer unchanged if an operation failed.
pstr_vcat() will make the destination buffer an empty string if it fails.
If you'd like the standard, old-fashioned two-string concatenation, there's also
pstr_cat().
char message[13];
char greeting[] = "Hello";
assert(pstr_cat(message, 13, greeting, " there!"));
// `message` now contains "Hello there!\0"
// We had just enough space to fit the "\0" inCopying
pstr_copy() works much like strcpy(), but does not overflow, does not truncate, and
only performs the copy if there is enough space, otherwise it leaves the destination
buffer untouched.
char message[15];
memcpy(message, "hello\0", 6);
if (!pstr_copy(message, 15, "welcome!")) {
// Whoops, couldn't make the message
}
// We had enough space, so `message` now contains `"welcome!\0"`You can also choose to only copy a certain number of characters with pstr_copy_n.
char message[15];
memcpy(message, "hello\0", 6);
if (!pstr_copy-n(message, 15, "welcome!", 2)) {
// Whoops, couldn't make the message
}
// `message` now contains "we\0"Splitting
pstr_split_on_first_occurrence() will split a string into two parts, on the first
occurrence of a separator.
char list[15];
memcpy(list, "cats,seashells\0", 15);
char first_item[15];
char second_item[15];
if (!pstr_split_on_first_occurrence(
list,
first_item, 15,
second_item, 15,
','
)) {
// Not enough room...
};
// `first_item` now contains "cats"
// `second_item` now contains "seashells"Slicing
You can slice from an index to the end with pstr_slice_from(), from the start to an
index with pstr_slice_to(), and between two indices with pstr_slice(). End indices
are exclusive, so slices take the form [start, end). The string is modified in-place.
These methods can fail if you give them indices that are out of range, in which case they
return false, so please check the returned value.
char message[15];
memcpy(message, ",,Mallard!\0", 11);
pstr_slice_to(message, 9); // ",,Mallard\0"
pstr_slice_from(message, 2); // "Mallard!\0"
pstr_slice(message, 2, 9); // "Mallard\0"Trimming
You can trim whitespace, or any specific character, from the start, end, or start and end of a string. The string is changed in-place. These methods cannot fail so they return nothing.
char message[15];
memcpy(message, " Strasbourg\n");
pstr_ltrim(message) // "Strasbourg\n";
pstr_rtrim(message) // " Strasbourg";
pstr_trim(message) // "Strasbourg";
memcpy(message, ",,Strasbourg,,");
pstr_ltrim_char(message, ','); // "Strasbourg,,"
pstr_rtrim_char(message, ','); // ",,Strasbourg"
pstr_trim_char(message, ','); // "Strasbourg"int64 to string
pstr_from_int64() can be used to easily convert a number to a string. The length of
this new string will be returned in the size_t variable pointed to by the last
parameter. Your destination buffer should be at least 21 characters long, to fit all
possible 64-bit strings. If there isn't enough space in your destination buffer, it will
return false, and set your destination buffer to "\0".
char number[21];
size_t number_length;
pstr_from_int64(number, 21, -4815162342, &number_length);
// `number` is now "-4815162342"Comparisons
You can easily check whether two strings are equal.
char name[] = "Bobby";
if (pstr_eq(name, "Bobby")) {
// ...
}You can also check if a string is empty.
char name[] = "";
if (pstr_is_empty(name)) {
// ...
}And there's also a method to get the length of a string. It just calls strlen(), but
it's included for visual consistency.
size_t len = pstr_len("Locarno");Starts/ends with
You can easily check if a string starts or ends with a character or another string.
pstr_starts_with_char("Magpie!", 'M'); // true
pstr_starts_with("Magpie!", "Mag"); // true
pstr_ends_with_char("Magpie!", 'e'); // false
pstr_ends_with("Magpie!", "pie!"); // trueOther utilities
There are a few utility methods.
pstr_clear(str) sets a string to "\0", effectively emptying it.
pstr_is_valid(str, str_buffer_size) check if str is valid by checking whether or not
it has a null terminator within str_buffer_size bytes.
FAQ
I've found a safety issue with one of the functions!
Issues and pull requests are welcome! :)
Why not use a dynamic string library such as sds?
sds is great, and is a better solution in many cases, but it's sometimes nice to be able
to work with static strings --- they're easy to serialize to disk, don't need a
malloc() call, are better in embedded environments and so on. Plus, for a lot of simple
string handling situations, you don't need your strings to be able to grow to any length.
It's nice to be able to work with static strings without dealing with awful <string.h>
functions with strcat.
Why is it called pstr?
I wrote it for my game engine Peony.
Credits
Icon by irasutoya
