Some people prefer to use the "rope" data structure to store a string of characters of unbounded length, rather than a contiguous string (C string).
A simplified rope can be defined something like:
#include <stdio.h>
struct non_leaf_rope_node{
char zero;
union rope* left_substring;
union rope* right_substring;
// real rope implementations have a few more items here
};
#define rope_leaf_max ( sizeof( struct non_leaf_rope_node ) )
typedef union rope {
char rope_data[ rope_leaf_max ];
struct non_leaf_rope_node pointers;
} rope;
void print( union rope *node ){
if( node->rope_data[0] != '\0' ){
// short literal data
fputs( node->rope_data, stdout );
}else{
// non-root node
print( node->pointers.left_substring );
print( node->pointers.right_substring );
};
};
// other details involving malloc() and free() go here
int main(void){
rope left = { "Hello," };
rope right = { " World!" };
rope root = {0,0,0};
root.pointers.left_substring = &left;
root.pointers.right_substring = &right;
print( &root );
return 0;
};
A rope with less than rope_leaf_max characters is stored the same as a null-terminated C string.
A rope containing more than rope_leaf_max characters is stored as a root non_leaf_rope_node pointing to the left and right sub-strings, (which may in turn point to left and right sub-sub-strings),
eventually pointing to leaf nodes,
and the leaf nodes each contain at least one character of the full string.
A rope always stores at least one character, so we can always tell:
If the first byte of a rope node is non-zero, that node is a leaf node storing literal characters.
If the first byte of a rope node is zero, that node stores pointers to left and right sub-strings.
(Real rope implementations often have a third kind of rope node).
Often using ropes requires less total RAM space than using C strings.
(A node containing a phrase such as "New York City"
can be re-used multiple times in one rope, or in some implementations shared between two ropes).
Sometimes using ropes is faster than using C strings.