Null-Terminated Strings in C Explained

In the C programming language, strings are not a distinct primitive data type, but rather contiguous sequences of characters stored in memory and terminated by a special character called the null terminator ('\0'). This article examines the internal representation of null-terminated strings in C, explaining how memory is allocated for them, how standard library functions interact with the null byte, and the operational trade-offs associated with this design choice.

Internal Memory Layout

A C string is represented as a contiguous one-dimensional array of char values. The end of the string is explicitly marked by a null character—a byte with an ASCII/binary value of zero (0x00), denoted in code as '\0'.

When you declare a string literal such as:

char greeting[] = "Hello";

The compiler reserves six consecutive bytes in memory:

Index 0 1 2 3 4 5
Character 'H' 'e' 'l' 'l' 'o' '\0'
Hex Value 0x48 0x65 0x6C 0x6C 0x6F 0x00

Even though the human-readable text contains five characters, the internal representation requires N + 1 bytes, where N is the length of the string.

Purpose of the Sentinel Byte

The null byte acts as an in-band sentinel value. Because the array does not store explicit metadata regarding its length, functions must scan the memory sequentially, element by element, until they encounter the '\0' byte.

Standard library functions in <string.h> rely entirely on this contract:

  • strlen(s): Increments a counter while iterating through memory addresses starting at s until the current byte equals 0.
  • strcpy(dest, src): Copies bytes sequentially from the source address to the destination address, halting immediately after copying the null byte.
  • printf("%s", s): Outputs characters sequentially until reaching the null character.

Performance and Safety Implications

Using a null-terminated representation introduces specific performance characteristics and safety considerations:

  1. Linear Time Complexity for Length: Determining string length requires an \(O(n)\) traversal. Languages that use length-prefixed strings (like Pascal or modern languages such as Rust and Go) store the size separately, allowing \(O(1)\) length lookups.
  2. Buffer Overruns: If a string array is not properly terminated—either due to insufficient buffer allocation or improper manual manipulation—functions like strlen and strcpy will read past the allocated buffer bounds until an arbitrary zero byte is found in memory. This leads to undefined behavior, data corruption, or segmentation faults.
  3. Binary Data Restrictions: Because the byte 0x00 is reserved to signify the end of the string, null-terminated strings cannot contain raw binary data or embedded null characters. Handling binary data in C requires a separate byte array (unsigned char*) paired with an explicit length variable.