Why a C string ends in a zero
A string in C is an array of characters in memory that ends with a zero byte. That zero is the only mark of where the text stops. A missing zero, or too much text in too small an array, causes a famous family of bugs and security holes.
A form stores a name in eight bytes and works until somebody types nine letters.
By the end of this lesson
Note
I can explain arrays as memory and strings as bytes that end in zero.
Key words
| Word | What it means | Example |
|---|---|---|
| Array | items of one type stored side by side | int row[5] |
| Index | the position of an item, counting from 0 | row[3] |
| String | characters stored as bytes, ending in a zero byte | |
| Terminator | the zero byte that ends a string | |
| Buffer | a block of memory set aside to hold data | |
| Overflow | writing past the end of a buffer |
The cells students fill in are left blank in this preview.
An array is items side by side
An array keeps items of one type next to each other in memory, with no gaps. Item row[3] is found by starting at the array and moving three items along. That is exactly *(row + 3).
C never checks an index. Ask for row[50] of a five-item array and it reads 50 items along, whatever is there.
Worked example: five ints from address 100
| Index | Address | Value |
|---|---|---|
| 0 | 100 | 3 |
| 1 | 104 | 1 |
| 2 | 108 | 4 |
| 3 | 112 | 1 |
| 4 | 116 | 5 |
The cells students fill in are left blank in this preview.
An index and pointer arithmetic agree
#include <stdio.h>
int main(void) {
int row[5] = {3, 1, 4, 1, 5};
printf("%d %d\n", row[3], *(row + 3));
// Bytes in the whole array, then how many items that is.
printf("array bytes %zu, items %zu\n", sizeof(row), sizeof(row) / sizeof(row[0]));
return 0;
}
Write and build the array program
printf '%s\n' '#include <stdio.h>' 'int main(void) {' ' int row[5] = {3, 1, 4, 1, 5};' ' printf("%d %d\n", row[3], *(row + 3));' ' printf("array bytes %zu, items %zu\n", sizeof(row), sizeof(row) / sizeof(row[0]));' ' return 0;' '}' | gcc -Wall -x c - -o arrShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Print one item two ways, and sizes
./arrShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A string ends with a zero byte
C has no string type. A string is an array of char whose last byte is 0, the terminator. So "hello" takes six bytes: five letters and the zero.
strlen counts bytes until it meets a zero. sizeof gives the size of the whole array, zero included.
Worked example: the bytes of "hello"
| Index | Character | Byte value |
|---|---|---|
| 0 | h | 104 |
| 1 | e | 101 |
| 2 | l | 108 |
| 3 | l | 108 |
| 4 | o | 111 |
| 5 | The terminator | 0 |
The cells students fill in are left blank in this preview.
A zero in the middle ends the string
#include <stdio.h>
#include <string.h>
int main(void) {
char word[] = "hello";
printf("sizeof %zu, strlen %zu\n", sizeof(word), strlen(word));
word[2] = 0; // put a zero where the first l was
printf("now strlen %zu, text %s\n", strlen(word), word);
return 0;
}
Write and build the string program
printf '%s\n' '#include <stdio.h>' '#include <string.h>' 'int main(void) {' ' char word[] = "hello";' ' printf("sizeof %zu, strlen %zu\n", sizeof(word), strlen(word));' ' for (int i = 0; i <= 5; i++) printf("%d ", word[i]);' ' printf("\n");' ' word[2] = 0;' ' printf("now strlen %zu, text %s\n", strlen(word), word);' ' return 0;' '}' | gcc -Wall -x c - -o strShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Print sizes, codes and the cut string
./strShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
A common mistake
Warning
Many people think a string stores its own length. It does not. C keeps the letters and one zero, and strlen walks to the zero every time. A text of five letters needs six bytes, so a buffer sized for exactly five is already too small.
Too much text overflows the buffer
A buffer is memory set aside for data, such as eight bytes for a name. If a program copies in nine letters and a zero, the extra bytes land on whatever is next. C does not stop it. The result is a crash, corrupted data or a security hole.
The model below copies with no length check, as strcpy does. It shows a neighbouring score being overwritten.
Watch a neighbour get overwritten
# Memory: eight bytes for a name, then a score of 50 right after it.
memory = [0] * 8 + [50]
text = "Ana Patel" # nine letters, one too many
# A copy with no length check, as strcpy does it.
for i in range(len(text)):
memory[i] = ord(text[i])
print("score is now", memory[8])
# Change text to "Ana Pat" and run again.
Check the length before you copy
The fix is to know the size of the buffer and never copy more. snprintf takes the size and stops: it keeps what fits and always ends the string with a zero.
The next Run passes nine letters to a program with an eight byte buffer. Seven letters survive, and the eighth byte holds the zero.
Write and build a safe copy
printf '%s\n' '#include <stdio.h>' '#include <string.h>' 'int main(int argc, char **argv) {' ' if (argc < 2) return 1;' ' char *text = argv[1];' ' char buf[8];' ' snprintf(buf, sizeof buf, "%s", text);' ' printf("%s has %zu letters, text has %zu\n", buf, strlen(buf), strlen(text));' ' return 0;' '}' | gcc -x c - -o safeShown here, not run. Create a free account to press Run and type it into your own lab’s terminal.
Print the cut name and both lengths
./safe "Ana Patel"Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.