I've been writing a lot of C. Whilst doing so I have been questioning my sanity. Am I an awful programmer? You know what, I think I might be. Kudos to all those devs that created monumental feats with C. Because it is not an easy tool to use. That said, I do enjoy writing C, just as long as I don't have a deadline or any business critical software to deliver. But when I say enjoy, I mean enjoyment in the sense of using a sycthe to cut a lawn, whilst my lawnmower watches on. It's lovely using the sycthe, but at the end of the day when I am exhausted and cut only a quarter of the grass that I could have cut with a mower in half the time, I do feel a great sense of achievement. I'm exhausted and broken, but happy. StringBuffer Anyway here is a string buffer implementation that I extracted from my project. You can see the up to date source code at BrianDouglasIE/c_string_buffer. The replace and remove were particularly tricky to implement. My brain was on fire trying to figure out how to juggle the memory about correctly. Header #ifndef STRING_BUFFER_H #define STRING_BUFFER_H #include /// @brief A dynamic string buffer that supports common string operations. typedef struct { size_t size; ///< The current size (length) of the string. size_t capacity; ///< The total allocated capacity of the buffer. char * data; ///< Pointer to the character data. } StringBuffer; /// @brief Represents all positions where a match was found. typedef struct { size_t * positions; ///< Array of match positions. size_t count; ///< Number of matches found. } MatchResult; /// @brief Represents the result of splitting a string. typedef struct { char ** parts; ///< Array of substrings resulting from the split. size_t count; ///< Number of parts. } SplitResult; // Lifecycle /// @brief Initializes a new, empty StringBuffer. /// @return A pointer to the newly allocated StringBuffer. StringBuffer * StringBuffer_init (); /// @brief Frees memory used by the given StringBuffer. /// @param buf Pointer to the StringBuffer to be freed. void StringBuffer_free (StringBuffer * buf ); /// @brief Clears the contents of the StringBuffer without freeing the object. /// @param buf Pointer to the StringBuffer to be cleared. void StringBuffer_clear (StringBuffer * buf ); // Operations /// @brief Prints the contents of the StringBuffer to stdout. /// @param buf Pointer to the StringBuffer to be printed. void StringBuffer_print ( const StringBuffer * buf ); /// @brief Appends text to the end of the StringBuffer. /// @param buf Pointer to the StringBuffer. /// @param text Null-terminated string to append. void StringBuffer_append (StringBuffer * buf , const char * text ); /// @brief Prepends text to the beginning of the StringBuffer. /// @param buf Pointer to the StringBuffer. /// @param text Null-terminated string to prepend. void StringBuffer_prepend (StringBuffer * buf , const char * text ); /// @brief Removes the first occurrence of text from the buffer starting from a given index. /// @param buf Pointer to the StringBuffer. /// @param text Null-terminated string to remove. /// @param from Index to start the search from. void StringBuffer_remove (StringBuffer * buf , const char * text , size_t from ); /// @brief Replaces the first occurrence of a substring with another string, starting from a given index. /// @param buf Pointer to the StringBuffer. /// @param original Substring to be replaced. /// @param update Replacement string. /// @param from Index to start the search from. void StringBuffer_replace (StringBuffer * buf , const char * original , const char * update , size_t from ); // Search & Split /// @brief Finds the index of the first occurrence of text starting from a given index. /// @param buf Pointer to the StringBuffer. /// @param text Substring to search for. /// @param from Index to start the search from. /// @return The index of the first occurrence, or -1 if not found. int StringBuffer_index_of ( const StringBuffer * buf , const char * text , size_t from ); /// @brief Finds all occurrences of a substring starting from a given index. /// @param buf Pointer to the StringBuffer. /// @param text Substring to match. /// @param from Index to start the search from. /// @return A pointer to a MatchResult containing all match positions. MatchResult * StringBuffer_match_all ( const StringBuffer * buf , const char * text , size_t from ); /// @brief Splits the buffer into parts using the given delimiter. /// @param buf Pointer to the StringBuffer. /// @param delimiter String delimiter to split by. /// @return A pointer to a SplitResult containing the parts. SplitResult * StringBuffer_split ( const StringBuffer * buf , const char * delimiter ); // Cleanup /// @brief Frees the memory used by a MatchResult. /// @param matches Pointer to the MatchResult to free. void MatchResult_free (MatchResult * matches ); /// @brief Frees the memory used by a SplitResult. /// @param split Pointer to the SplitResult to free. void SplitResult_free (SplitResult * split ); #endif Implementation #include "string_buffer.h" #include #include #include #define INITIAL_CAPACITY 16 static int ensure_capacity (StringBuffer * buf , size_t required ) { if (required <= buf->capacity) return 1 ; size_t new_capacity = buf->capacity ? buf->capacity : INITIAL_CAPACITY; while (new_capacity < required) { new_capacity *= 2 ; } char * new_data = realloc (buf->data, new_capacity); if ( ! new_data) { perror ( "realloc" ); return 0 ; } buf->data = new_data; buf->capacity = new_capacity; return 1 ; } StringBuffer * StringBuffer_init () { StringBuffer * buf = malloc ( sizeof (StringBuffer)); if ( ! buf) { perror ( "malloc" ); return NULL ; } buf->size = 0 ; buf->capacity = 0 ; buf->data = NULL ; return buf; } void StringBuffer_free (StringBuffer * buf ) { if ( ! buf) return ; free (buf->data); free (buf); } void StringBuffer_clear (StringBuffer * buf ) { if ( ! buf || ! buf->data) return ; buf->data[ 0 ] = ' \0 ' ; buf->size = 0 ; } void StringBuffer_print ( const StringBuffer * buf ) { if ( ! buf || ! buf->data) return ; printf ( "size: %zu " , buf->size); printf ( "data: %s " , buf->data); } void StringBuffer_append (StringBuffer * buf , const char * text ) { if ( ! buf || ! text) return ; size_t text_len = strlen (text); if ( ! ensure_capacity (buf, buf->size + text_len + 1 )) return ; memcpy (buf->data + buf->size, text, text_len + 1 ); buf->size += text_len; } void StringBuffer_prepend (StringBuffer * buf , const char * text ) { if ( ! buf || ! text) return ; size_t text_len = strlen (text); if ( ! ensure_capacity (buf, buf->size + text_len + 1 )) return ; memmove (buf->data + text_len, buf->data, buf->size + 1 ); memcpy (buf->data, text, text_len); buf->size += text_len; } int StringBuffer_index_of ( const StringBuffer * buf , const char * text , size_t from ) { if ( ! buf || ! text || ! buf->data || from >= buf->size) return - 1 ; char * match = strstr (buf->data + from, text); return match ? ( int )(match - buf->data) : - 1 ; } MatchResult * StringBuffer_match_all ( const StringBuffer * buf , const char * text , size_t from ) { if ( ! buf || ! text || ! buf->data || from >= buf->size) return NULL ; MatchResult * matches = malloc ( sizeof (MatchResult)); if ( ! matches) return NULL ; matches->positions = NULL ; matches->count = 0 ; int index = StringBuffer_index_of (buf, text, from); while (index != - 1 ) { size_t * new_positions = realloc (matches->positions, sizeof ( size_t ) * (matches->count + 1 )); if ( ! new_positions) { MatchResult_free (matches); return NULL ; } matches->positions = new_positions; matches->positions[matches->count ++ ] = index; index = StringBuffer_index_of (buf, text, index + 1 ); } return matches; } void StringBuffer_remove (StringBuffer * buf , const char * text , size_t from ) { if ( ! buf || ! text || ! buf->data || from >= buf->size) return ; size_t text_len = strlen (text); int index = StringBuffer_index_of (buf, text, from); while (index != - 1 ) { size_t tail_len = buf->size - (index + text_len); memmove (buf->data + index, buf->data + index + text_len, tail_len + 1 ); buf->size -= text_len; index = StringBuffer_index_of (buf, text, index); } } void StringBuffer_replace (StringBuffer * buf , const char * original , const char * update , size_t from ) { if ( ! buf || ! original || ! update || ! buf->data || from >= buf->size) return ; size_t original_len = strlen (original); size_t update_len = strlen (update); if (original_len == 0 || update_len == ( size_t ) - 1 ) return ; int index = StringBuffer_index_of (buf, original, from); while (index != - 1 ) { if (update_len > original_len) { if ( ! ensure_capacity (buf, buf->size + (update_len - original_len) + 1 )) return ; } size_t tail_len = buf->size - (index + original_len); memmove (buf->data + index + update_len, buf->data + index + original_len, tail_len + 1 ); memcpy (buf->data + index, update, update_len); buf->size = buf->size - original_len + update_len; index = StringBuffer_index_of (buf, original, index + update_len); } } SplitResult * StringBuffer_split ( const StringBuffer * buf , const char * delimiter ) { if ( ! buf || ! delimiter || ! buf->data) return NULL ; char * copy = strdup (buf->data); if ( ! copy) return NULL ; SplitResult * result = malloc ( sizeof (SplitResult)); if ( ! result) { free (copy); return NULL ; } result->parts = NULL ; result->count = 0 ; char * token = strtok (copy, delimiter); while (token) { char ** tmp = realloc (result->parts, sizeof ( char * ) * (result->count + 1 )); if ( ! tmp) { SplitResult_free (result); free (copy); return NULL ; } result->parts = tmp; result->parts[result->count] = strdup (token); if ( ! result->parts[result->count]) { SplitResult_free (result); free (copy); return NULL ; } result->count ++ ; token = strtok ( NULL , delimiter); } free (copy); return result; } void MatchResult_free (MatchResult * matches ) { if ( ! matches) return ; free (matches->positions); free (matches); } void SplitResult_free (SplitResult * split ) { if ( ! split) return ; for ( size_t i = 0 ; i < split->count; i ++ ) { free (split->parts[i]); } free (split->parts); free (split); } Tests