C Strings: A 50-Year Mistake - by Trần Thành Long
The Long Blog
SubscribeSign in
C Strings: A 50-Year Mistake<br>Why C strings suck—and why length-based strings are the right design.
Trần Thành Long<br>Jul 11, 2026
Share
One design choice in C that now feels outdated—and arguably one of its biggest mistakes—is the use of null-terminated strings.1 Treating strings as pointers to character “streams” and relying on a terminating NULL character may have made more sense in the 1970s due to the memory and performance constraints of the time, but nowadays there’s basically no reason to keep using it.<br>The correct modern choice adopted by most newer languages and popular frameworks is length-based strings—basically, a struct with a pointer and a size.<br>struct String<br>u8* data;<br>u64 size;<br>};
#define Str(s) (String){ (u8*)(s), sizeof(s) - 1 }<br>#define SArg(s) (int)((s).size), ((s).data)
bool StrCmp(String a, String b);<br>// ...
// Somewhere in code<br>String a = Str("hello");<br>String b = Str("world");
if (!StrCmp(a, b))<br>printf("a is %.*s and b is %.*s\n", SArg(a), SArg(b));
But why is this that much better, though?
1. Losing valuable information
A string’s length is an extremely valuable piece of information. By not storing it, you force all code that uses the string to either repeatedly call strlen or iterate over it byte by byte. Both approaches are inefficient. Both approaches lead to worse overall code.<br>The code is also extremely error-prone, since checking and enforcing the length at runtime becomes much harder, slower, and more cumbersome. Debugging also suffers, since communicating this valuable information to other tools and analyzers becomes a nightmare (e.g., viewing a char* versus a char[N] versus a char*[N] is handled inconsistently across tooling).<br>I personally believe that many of C’s memory-related issues and overflow bugs stem largely from this outdated design choice.2 Others may disagree, arguing that a sentinel is sufficient in most cases, making it unnecessary to store a string's length. However, without knowing the length, strings become more like linked lists, with a serial access pattern rather than the random access of an array.<br>Proponents of C strings often defend this consequence by arguing that such an iterative style is actually preferable because it encourages single-pass algorithms over multi-pass traversals, under the assumption that fewer passes over a string are inherently more efficient.<br>What they're missing is that most programmers, especially beginners, do not naturally think in those terms. They typically write code that assumes the string length is cheaply available. I would also argue that most common string routines are more naturally expressed this way. As a result, many unnecessary multiple passes over the same string in modern programs are caused by repeated calls to strlen. Furthermore, even aside from that, the assumption that fewer passes are always better is simply incorrect and comes from a misunderstanding of how modern CPUs work.<br>There are some advantages to enforcing a sentinel-based design though, and we will examine them in greater detail later.<br>2. String lengths now have different meanings
One of the most commonly used standard library functions in C is probably snprintf. To refresh your memory, here’s its prototype:<br>int snprintf(char *str, size_t size, const char *format, ...);
Ok, can you tell me what the integer it takes and the integer it returns are for?<br>If you’re inexperienced in C and not paying too much attention—maybe just skimming through documentation, reading example code, or recalling something you heard before—you might answer something similar to: snprintf takes the size of the output buffer as an argument and returns the length of the string it would have produced.<br>But once you actually sit down and use it, that answer is no longer sufficient. You start wondering: what does “length” actually mean here?3 Does it include the null terminator, or not?<br>And that is the gist of the problem. By requiring every string to end with a null byte, the concept of “string length” becomes subtly ambiguous in practice. This means that whenever you work with4 a string-related function—whether in the standard library, a large production codebase, or some third-party libraries—you constantly have to ask yourself a series of questions: Does this function write the null terminator for me? Do I need to allocate space for the string plus the null byte? And does the size argument—or the return value—include or exclude it?<br>What’s even worse is that the C standard library is not entirely consistent in how it defines “length”, which makes it even harder to guess which one is which reliably. snprintf is a good example of this: the buffer and its size must account for the null terminator. Many people correctly infer this and naturally assume that the return value follows the same rule. But they would be wrong. In reality, the two behave in opposite ways: the input integer...