43 Strings
What to know first
Looking back
Chapter 8 said the string representation C chose is NUL termination, “planting a marker at the end”, and foreshadowed its three prices (the length must be counted, a NUL cannot be held as content, and without the marker a scan runs past the end). Now that you have learned arrays (chapter 39) — how would you define a C string exactly, yourself?
A. A char array, somewhere in which there is a byte of value 0 (the NUL character, '\0'), plus the agreement that “the string” means from the start to just before that marker — that is all. There is no separate string type. An array, and an agreement. This chapter treats the use and the cost of that agreement.
The need for this chapter, and its context
By the end of this chapter
NULL/nullptr, the NUL character; chapter 37), the last, the NUL character, gets its formal treatment here.The questions this chapter answers
- Why did C not attach
const? - What of a string literal’s type in the newest standard and the ones to come?
- To handle Hangul properly — to work in units of characters — what must be done?
43.1 What a string is — a line of characters and an end mark#
A good share of the data programs handle is text. Names, file paths, commands, messages, one line of a configuration file. Text is characters standing in a row, and programmers call such “a row of characters” a string. Hello world’s "Hello, world!\n" was our first string.
Many other languages have a separate string type, which knows its length, grows, and joins to others. C has no such type. A C string is not a type but an agreement on top of an array. The standard writes the agreement like this.
“ A string is a contiguous sequence of characters terminated by and including the first null character. … A pointer to a string is a pointer to its initial (lowest addressed) character. The length of a string is the number of bytes preceding the null character and the value of a string is the sequence of the values of the contained characters, in order.1 ”
The standard’s definition contains four phrases, and this whole chapter is their consequences.
| Phrase in the definition | Consequence |
|---|---|
| a contiguous sequence of characters | a string lives inside a char array (chapter 39). All the array rules — decay, the boundary — apply |
| terminated by the first null character | the only way to know the end is a byte whose value is 0 ('\0', NUL). The length is written nowhere |
| and including it | holding "hi" takes 3 bytes, not 2. Forgetting that byte is the most common off-by-one overflow |
| a pointer to it points to the initial character | passing a string to a function means passing one address, that of the first character. Neither the length nor the container size goes with it |
Table 43.1 — What the definition of a string says
“Mark the end instead of writing the length” is the choice C made among the several representations seen in chapter 8. It saved memory and kept the structure simple, and its price is the subject of chapter 44 and the whole of this book’s Part XIII. Note too that “length” means a number of bytes — not of characters. The very next demo shows the difference.
43.2 What a C string really is — an array, and an agreement#
char greet[] = "안녕"; — initialise an array with a string literal and the compiler builds the array by appending '\0' after the bytes of the characters. The demonstration dissects it.
examples-en/strings/str.c
#include <stdio.h>
#include <string.h>
int main(void)
{
/* Korean text is kept here on purpose: the point is that a character
and a byte are not the same thing. */
char greet[] = "안녕"; /* two characters — but how many bytes? */
printf("strlen(\"안녕\") = %zu\n", strlen(greet));
for (size_t i = 0; i < strlen(greet); i += 1) {
printf("%02X ", (unsigned char)greet[i]);
}
printf("\n");
printf("sizeof greet = %zu (the NUL terminator included)\n", sizeof greet);
return 0;
}
Output
strlen("안녕") = 6
EC 95 88 EB 85 95
sizeof greet = 7 (the NUL terminator included)
The output shows three things: the length, the bytes, and the size of the container. strlen (the length measurement of the standard <string.h>) answered 6 — “안녕” is two characters, six bytes (exactly chapter 8′s UTF-8 table: Hangul syllables live in the 3-byte range). The byte dump EC 95 88 EB 85 95 shows those six bytes themselves, and sizeof greet is 7 — the size of a container that also holds the NUL marker. Here the program’s output disproves chapter 8′s misconception (“one character = one byte”).
strlen’s cost can now be stated exactly too — as chapter 8 foretold, NUL termination does not write the length down, so strlen walks one slot at a time until it meets the marker. The longer the string the longer it takes (proportional to the slot count), which is why code calling strlen in a loop condition every time is a classic performance trap (the demonstration’s loop is in fact that pattern — harmless for a short string, but for a long one the practice is to take the length into a variable first).
43.3 String literals — read-only ground#
Instead of initialising an array you may store a literal’s address in a pointer — and here lies an important difference:
char buf[] = "you may change this"; /* copied into my array — modifiable */
const char *msg = "you must not change this"; /* points at the literal itself */A string literal is itself read-only data baked into the program — attempting to modify it is outside the contract, and in a modern environment it usually collapses on the spot (literals being placed in a write-forbidden region — chapter 4′s protected zone doing another kindness). So the rule is to declare pointers to literals as const char* — where chapter 24′s const was documentation saying “I will not change this”, here it works as a lock that stops, at compile time, the mistake of trying to change what must not be changed.
43.3.1 A string literal’s type is not const — C’s odd place#
And here lies C’s famous contradiction. It must not be modified, and yet its type carries no const. The type of "abcdef" is not const char[7] but plain char[7].
char *p = "hello"; /* C: it passes without a warning */
p[0] = 'H'; /* but this is undefined behaviour */That is, the compiler does not block it. “Not modifiable” exists only as a contract, not as a type, and breaking it is chapter 56′s undefined behaviour — it usually crashes on the spot because the literal sits in a write-protected region, but that is the implementation’s kindness, not the language’s guarantee.
C++ differs. In C++ a string literal’s type is const char[N], and the first line above is a compile error. This item belongs in the list of “differences between the two languages” that chapter 111 draws up.
Q. Why did C not attach const?
A. So as not to break existing code. The word const itself entered the standard only with C89 (chapter 24), and by then the world already had mountains of code putting string literals into a char *. The moment the literal’s type became const char[N], all of that code would be subject to diagnosis.
It is the same circumstance as gets’s funeral taking twenty years (chapter 79 tells it) — the standard is an institution that must protect existing code, so when “the right type” and “code already written” collide it leans towards the latter. C++, first standardised in 1998, carried no such burden and could attach const from the start (chapter 111′s “siblings, not parent and child” is confirmed here too).
So discipline stands in for the language. A pointer at a literal is always declared const char *. Then a human has written in what the type cannot say, and from there the compiler enforces it. GCC’s and Clang’s -Wwrite-strings is an option that changes a literal’s type to const char[N] outright, but it is not the default because warnings pour out of old code.
Q. What of a string literal’s type in the newest standard and the ones to come?
A. It remains unchanged through C23. A string literal’s type is still char[N] and modifying one is still undefined behaviour. C23 changed two things while leaving this place alone — settling u8"..."‘s element type as char8_t and pinning down two’s complement are both separate from this clause.
Nor is there any sign of change in the next edition (C2y). The reason is the one above — changing the type now would make half a century of code subject to diagnosis. The standard’s approach here has been not to change the type but to let the tools warn, and that is likely to remain so.
Remember it like this — the type is char[N], the contract is “do not modify”, and the defence is const char * plus compiler warnings.
43.4 A literal is an array too#
The previous section said a literal may be copied into an array or pointed at by a pointer, and one step further there is a surprising fact — a string literal is itself already an array. To give its exact type, "abcdef" is char[7] (six characters + the NUL).
examples-en/strings/literal.c
/* What a string literal really is — an array, adjacent ones join, and the prefixes. */
#include <stdio.h>
#include <inttypes.h> /* PRIu64 — a standard macro built on the joining rule */
#include <wchar.h>
/* the idiom of giving a format fragment a name — it joins in the printf below */
#define TEMP_FMT "%.1f"
#define ID_FMT "%d"
/* assembling a version string — the value is written in one place only */
#define MY_PROGRAM_VERSION "v3.1.2"
#define PROGRAM_TITLE "my_program " MY_PROGRAM_VERSION
/* a version kept as numbers becomes a string through a double indirection (chapter 65) */
#define VER_MAJOR 3
#define VER_MINOR 1
#define STR_RAW(x) #x
#define STR(x) STR_RAW(x)
#define VERSION_FROM_NUMBERS "v" STR(VER_MAJOR) "." STR(VER_MINOR)
int main(void)
{
/* ── (1) a literal is an array ───────────────────────────── */
printf("sizeof \"abcdef\" = %zu (six characters + NUL)\n", sizeof "abcdef");
printf("sizeof \"\" = %zu (an empty string is still one NUL)\n", sizeof "");
/* being an array, it takes a subscript — chapter 39's a[i] == *(a+i) as it is */
printf("\"abcdef\"[3] = %c\n", "abcdef"[3]);
printf("3[\"abcdef\"] = %c <- the subscript commutes (it means the same)\n", 3["abcdef"]);
/* ── (2) adjacent literals join into one ─────────────────── */
printf("%s\n", "abc" "def"); /* -> "abcdef" */
printf("sizeof(\"abc\" \"def\") = %zu\n", sizeof("abc" "def"));
/* writing a long message across lines — no backslashes needed */
const char *help =
"usage: tool [options] file\n"
" -v verbose\n"
" -o output file\n";
printf("%s", help);
/* ── (3) naming the fragments and assembling them ────────── */
puts(PROGRAM_TITLE);
printf("assembled from numbers: %s\n", VERSION_FROM_NUMBERS);
int id = 7;
double temp = 36.5;
printf("id = " ID_FMT ", temp = " TEMP_FMT "\n", id, temp);
/* the standard library uses the same technique — format macros for fixed-width integers */
uint64_t total = 1234567890123ULL;
printf("total = %" PRIu64 "\n", total);
/* ── (4) prefixes — the letter that settles the encoding ─── */
printf("sizeof \"AB\" = %zu (char)\n", sizeof "AB");
printf("sizeof u8\"AB\" = %zu (UTF-8)\n", sizeof u8"AB");
printf("sizeof L\"AB\" = %zu (wchar_t is %zu bytes)\n",
sizeof L"AB", sizeof(wchar_t));
printf("sizeof u\"AB\" = %zu (UTF-16)\n", sizeof u"AB");
printf("sizeof U\"AB\" = %zu (UTF-32)\n", sizeof U"AB");
/* join one with a prefix and one without and the prefixed one wins */
const wchar_t *w = L"wide" " and narrow";
printf("L\"wide\" \" and narrow\" -> length %zu (it becomes a wide string)\n",
wcslen(w));
return 0;
}
Output
sizeof "abcdef" = 7 (six characters + NUL)
sizeof "" = 1 (an empty string is still one NUL)
"abcdef"[3] = d
3["abcdef"] = d <- the subscript commutes (it means the same)
abcdef
sizeof("abc" "def") = 7
usage: tool [options] file
-v verbose
-o output file
my_program v3.1.2
assembled from numbers: v3.1
id = 7, temp = 36.5
total = 1234567890123
sizeof "AB" = 3 (char)
sizeof u8"AB" = 3 (UTF-8)
sizeof L"AB" = 12 (wchar_t is 4 bytes)
sizeof u"AB" = 6 (UTF-16)
sizeof U"AB" = 12 (UTF-32)
L"wide" " and narrow" -> length 15 (it becomes a wide string)
The output’s first two lines are that confirmation. sizeof "abcdef" is 7 and sizeof "" is 1 — not a pointer’s size (8) but the array’s size. A literal decays into a pointer as seen in chapter 39 because sizeof is the exception to decay, the array’s real size shows.
Something amusing follows from its being an array. Because it is an array, you can apply a subscript to it.
"abcdef"[3] /* 'd' */And recalling the identity a[i] ≡ *(a + i) learned in chapter 39, since addition commutes, *(a + i) ≡ *(i + a) ≡ i[a]. That is,
3["abcdef"] /* 'd' as well — entirely legal grammatically */The example printing d for both confirms it. There is no use for it in practice (it merely bewilders the reader) and it is notation seen only in obfuscation contests, but no example shows more clearly that the array subscript is not a decoration of the grammar but another notation for pointer arithmetic.
43.5 Concatenation — adjacent literals become one#
Write two string literals side by side and the compiler joins them into one.
"abc" "def" /* -> "abcdef" — no comma, no operator */The example confirms it: sizeof("abc" "def") is 7 (3+3+NUL). This happens not in the preprocessor but in translation phase 6 (chapter 65′s table) — so a string fragment produced by a macro joins with the literal beside it too.
There are two uses.
① Writing a long sentence over several lines. It is cleaner than joining lines with a backslash — because the indentation does not go inside the string.
const char *help =
"usage: tool [options] file\n"
" -v verbose\n"
" -o output file\n";② Keeping fragments under names and assembling them. This is the use most often seen in practice, and it appears with three faces.
Assembling a version string. The value is written in one place and woven in elsewhere.
#define MY_PROGRAM_VERSION "v3.1.2"
#define PROGRAM_TITLE "my_program " MY_PROGRAM_VERSION
puts(PROGRAM_TITLE); /* my_program v3.1.2 */
puts("build: " __DATE__ " " __TIME__); /* with the compiler's own literals too */The point is that raising the version means mending one line. It weaves just as well with literals the compiler predefines (__DATE__, treated in chapter 65), so many programs build their banner and --version output this way.
Keeping format fragments under names. The same technique applied to printf’s format.
#define ID_FMT "%d"
#define TEMP_FMT "%.1f"
printf("id = " ID_FMT ", temp = " TEMP_FMT "\n", id, temp);The demand that a format string be a constant (chapter 76′s format string vulnerability, and the compiler’s format checking) is kept while the fragments are managed under names. It contrasts with passing a variable, as in printf(fmt_string_variable, …), which makes all that checking vanish at once — concatenation finishes at compile time, so the result is still one literal.
The standard library uses the same technique. The format macros of <inttypes.h> met later in chapter 93 and appendix B stand exactly on this rule.
printf("total = %" PRIu64 "\n", total);PRIu64 is defined as "lu" or "llu" (it differs by platform) and joins with the fragments before and after into one format string. The problem that the format for a fixed-width integer differs by platform was solved by concatenation alone.
The three cases come to the same one thing — give literal fragments names and weave them at the place of use. And all three pair with chapter 65′s # operator. If the version is managed as numbers, the numbers can be turned into strings and woven.
#define VER_MAJOR 3
#define VER_MINOR 1
#define STR_RAW(x) #x
#define STR(x) STR_RAW(x) /* double expansion --- see the preprocessor chapter */
#define VERSION "v" STR(VER_MAJOR) "." STR(VER_MINOR) /* "v3.1" */Counter-example. Variables do not join
const char *unit = "°C";
printf("temp = %.1f" unit "\n", t); /* a compile error */Concatenation is a rule between literals. unit is a variable and so cannot join. To insert a variable’s content the answer is one more placeholder — printf("temp = %.1f%s\n", t, unit).
For the same reason, when keeping a fragment to be joined in a #define, that macro must expand to a string literal. Define it as something that is not a literal and it will not join.
43.6 Prefixes — a literal of which encoding#
A prefix (u8, u, U, L) may be attached before a literal to settle which character type’s array it is.
| notation | element type | encoding | sizeof "AB" |
|---|---|---|---|
"AB" | char | the execution character set (mostly UTF-8) | 3 |
u8"AB" | char8_t (C23) / char | UTF-8 | 3 |
u"AB" | char16_t | UTF-16 | 6 |
U"AB" | char32_t | UTF-32 | 12 |
L"AB" | wchar_t | the implementation settles it | 12 on the example’s machine |
Table 43.2 — The kinds of string literal and their sizes
The example really prints this table. That L"AB" is 12 bytes is because this Linux machine’s wchar_t is 4 bytes — on Windows it is 2 bytes and this becomes 6. The standard fixes neither the size nor the encoding of wchar_t; that story is in chapter 85.
Character constants take the same prefixes (L'A', u'A'). And where concatenation meets prefixes there is one more rule — if only one side has a prefix, the prefixed side wins (the example’s L"wide" " and narrow" becomes a wide string), and joining two different prefixes violates a constraint (C23 §6.4.5p2) — the compiler must diagnose it.
In practice. gets — the function expelled from the standard
gets in the early standard library was a function that “reads a line and puts it into an array” — and it did not take the container’s size. If the input was longer than the container it overwrote the neighbouring memory as it went: a function with the boundary violation of chapter 39 built into its design. In 1988 the internet’s first large-scale worm (the Morris worm)2 spread by exploiting defects of this class. After decades of accidents the C11 standard deleted gets — a rare case of a standard formally burying one of its own functions. That is the reason this book taught fgets (the successor that takes the container’s size) from the start in chapter 26 — and the next chapter faces this whole class of accident head on.Q. To handle Hangul properly — to work in units of characters — what must be done?
A. Distinguishing two layers is the starting point. At the byte layer (the world of C strings) you may treat UTF-8 simply as a byte sequence — copying, joining, storing and transmitting are safe without knowing character boundaries (thanks to UTF-8′s self-synchronising design, chapter 8). Work that needs the character layer (counting characters, cutting, case conversion) requires UTF-8 decoding, and since the standard library’s support is thin there, using a library is the practice (Part XIII covers one such library). Keep one rule in mind and most accidents are prevented: do not cut a string by byte index — cut through the waist of a Hangul syllable and you get a broken byte sequence.
We know the string’s identity and cost. The next chapter is this part’s turning point — a dissection of the class of accident called the boundary violation, and five disciplines for handling a failed parse. The “safe input” seed planted in chapter 26 finally bears fruit.
Notes
- ISO/IEC 9899 (C23) 7.1.1, paragraph 1. The fact that the definition is in the library clause (7), not the language proper (6), itself makes this chapter’s point: a string is an agreement of the library. ↩
- Eugene H. Spafford. 1988. The Internet Worm Program: An Analysis. Technical Report CSD-TR-823, Purdue University, West Lafayette, IN.
spaf.cerias.purdue.edu/tech-reps/823.pdf↩