Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 17

Character Arrays vs. Char Pointers: Understanding Null Termination

Recommended resources

Introduction

In our last lesson, we delved into array decay, establishing that while an array name and a pointer are closely related, they are not the same. This distinction becomes especially critical when working with C strings. Today, we'll explore this topic in depth, addressing a common point of confusion for C programmers: the difference between initializing a string with a character array versus a character pointer.

This lesson will clarify precisely what happens in memory for these two declarations:
char arr[] = "some string";
char *ptr = "some string";

Mastering this difference is fundamental. It will prevent subtle bugs and crashes in your future programs, particularly when you start handling game data like command inputs, character dialogue, or item names. Our goal is to distinguish between these two forms of string representation and understand the foundational role of the null terminator.


The C String Convention: The Null Terminator

Before we compare arrays and pointers, we must understand what a "string" is in C. Unlike some other languages, C has no built-in string type. Instead, it relies on a simple but powerful convention: a string is a sequence of characters stored contiguously in memory, ending with a special character called the null terminator, written as '\0'. This character has an ASCII value of zero and is not the same as the digit '0'.

Standard library functions that work with strings, like printf or strlen, don't know the string's length beforehand. They simply process characters one by one until they encounter this '\0', which signals the end of the string.

This video provides an excellent explanation of why the null terminator is essential and what happens when it's missing.

Character arrays and pointers - part 1

This video from mycodeschool, "Character arrays and pointers - part 1," clearly explains the concept of null-terminated strings.

First, watch the section that explains why strings need a null terminator. This part clarifies why you always need one extra byte of storage for any string. Next, see the practical demonstration of what happens when a string is not null-terminated, from this timestamp. The undefined behavior you'll see with printf is a powerful illustration of why this convention is so important to follow.

When you write a string literal (any text enclosed in double quotes, like "hello"), the C compiler automatically appends a null terminator for you. This implicit '\0' is the cornerstone of how strings are handled in C.


Method 1: The Character Array — char str1[] = "hello world";

When you declare a character array and initialize it with a string literal, the compiler performs the following steps:

  1. It reads the string literal "hello world" (which is 11 characters long).
  2. It allocates an array on the stack (since str1 is a local variable) that is large enough to hold the entire string plus the null terminator. In this case, it allocates 12 bytes.
  3. It copies the contents of the string literal, including the '\0', into this newly created array on the stack.

The result is a self-contained, mutable copy of the string.

This diagram illustrates the two main approaches. On the right, `message[]` represents a character array. The string's content is copied directly onto the stack, making it a modifiable local variable.

Because str1 is an array containing a writable copy of the string, you are free to modify its contents.

char str1[] = "hello world";
str1[0] = 'H'; // This is perfectly valid.
printf("%s\n", str1); // Output: Hello world

However, as we know from previous lessons, an array name itself is not a modifiable variable. You cannot reassign it to point to something else.

// These lines would cause a compilation error:
str1 = "new string"; 
str1++; 

Method 2: The Pointer to a String Literal — char *str2 = "hello world";

This method looks similar but is fundamentally different in its memory behavior. Here's what the compiler does:

  1. It places the string literal "hello world" (including its null terminator) into a special, read-only segment of your program's memory. This section is often referred to as .rodata.
  2. It allocates a single pointer variable on the stack, named str2.
  3. It initializes this pointer with the memory address of the first character of the string literal in read-only memory.

The result is that str2 does not hold the string itself, but merely the address of an immutable string.

This visualization perfectly captures the concept. The declaration `char *str2 = "hello world";` creates a pointer `str2` that points to the string literal stored in a read-only data segment of memory. Trying to write to this memory (the 'W' in the diagram) is not allowed.

Because the string literal itself resides in protected memory, attempting to modify it through the pointer leads to undefined behavior, which on most modern systems will cause a segmentation fault and crash your program.

The following video demonstrates this exact scenario and its consequences.

String In Char Array VS. Pointer To String Literal | C Programming Tutorial

The video "String In Char Array VS. Pointer To String Literal" from Portfolio Courses highlights the practical differences.

First, watch the demonstration of initializing and modifying a character array. This reinforces what we just discussed. Next, pay close attention to the section on initializing a pointer to a string literal. The key moment is when an attempt to modify the string results in a runtime error. This is the crucial difference you need to remember.

While you cannot modify the data the pointer points to, the pointer variable str2 itself is on the stack and is modifiable. You can change what it points to.

char *str2 = "hello world";

// str2[0] = 'H'; // UNDEFINED BEHAVIOR! Likely a crash.

str2++; // This is valid. str2 now points to "ello world".
str2 = "new string"; // This is also valid. str2 now points to a different literal.

Best Practice: The const Keyword

Since attempting to modify a string literal via a char* is a common source of bugs, modern C programming practice dictates that you should always declare pointers to literals as const.

const char *str2 = "hello world";

The const keyword tells the compiler that the data the pointer points to should not be changed. If you accidentally write code that tries to modify it, the compiler will catch your mistake and issue an error during compilation, saving you from a potential runtime crash. This is a much safer and more expressive way to declare a pointer to a string literal.


Tying It All Together

Let's review a resource that consolidates these concepts with clear explanations and a complete code example.

vs char *str2 = "hello world"; – The Memory Story Every C ...

This article from dev.to provides a comprehensive breakdown of the memory layout and behavior of both string initialization methods.

First, read the section on character arrays. It summarizes how memory is allocated on the stack and why the content is mutable. Next, study the section on character pointers. This part explains why the pointed-to data is in read-only memory and what operations are (and are not) allowed. Read the short section on using const. This reinforces the best practice we just discussed. Finally, carefully examine the full code example at the end of the article, under PART 1 and PART 2. This code neatly demonstrates all the concepts in a single program. Try to predict the output of each printf statement before reading the comments.


Conclusion

You now have a clear picture of the crucial distinction between char arr[] and char *ptr for handling strings in C. This is not just an academic detail; it's a practical necessity for writing safe and correct low-level code, which is central to your goal of game engine development.

Key Takeaways:

  • C-Strings: Are not a type but a convention—a character array terminated by a null character (\0).
  • char arr[] = "text";: Creates a mutable copy of the string on the stack. The array arr owns its data, which can be modified. The array name itself cannot be reassigned.
  • char *ptr = "text";: Creates a pointer to an immutable string literal stored in read-only memory. The data cannot be modified, but the pointer ptr can be changed to point to something else.
  • sizeof Revisited: This difference explains the behavior we saw in the previous lesson. sizeof(arr) gives the size of the array (e.g., 5 bytes for "text"), whereas sizeof(ptr) gives the size of a pointer (e.g., 8 bytes on a 64-bit system).
  • Safety First: Always use const char * when creating a pointer to a string literal to catch accidental modification attempts at compile time.

Now that we've solidified our understanding of how strings are stored, we are ready to manipulate them. In the next lesson, we will learn how to process strings manually by traversing them character-by-character using pointers, a fundamental skill for tasks like parsing game commands.