Hello! Welcome to the next lesson in our C interview preparation series.
In our last session, we dissected array decay and the crucial differences in passing 1D and 2D arrays to functions. We established that for a 2D array int mat[R][C], the decayed type is a pointer-to-an-array, int (*)[C], not a double pointer int **. Today, we'll build on these pointer concepts by focusing specifically on C strings.
As an engineer working with communication protocols like CAN and SOME/IP, you're no stranger to manipulating raw byte streams and data buffers. This lesson will sharpen your skills in that domain by focusing on C's native string handling. We'll use pointer arithmetic for efficient string manipulation and, more importantly, dissect the common pitfalls that are frequent topics in technical interviews—mistakes that can lead to bugs ranging from incorrect data processing to serious security vulnerabilities.
Your learning outcome for this lesson is to manipulate C strings using pointer arithmetic and identify common pitfalls like buffer overflows and missing null terminators.
Strings: Pointer-Based by Convention
As we've covered, C has no built-in string type. A "string" is a convention: a contiguous sequence of characters in memory, terminated by a special null character, \0. This null terminator is the only way standard library functions (printf, strlen, strcpy, etc.) know where the string ends.
When you write a string literal like "hello", the compiler stores the character sequence h, e, l, l, o, \0 in a read-only section of memory and the expression "hello" evaluates to a pointer (char *) to the first character, 'h'.
This leads to a classic "gotcha" that is worth remembering:
'a'is a character literal, which has an integer type (its ASCII/UTF-8 value)."a"is a string literal, which represents a pointer to a location in memory containing two characters:'a'and'\0'.
Passing 'a' to a function expecting a char * (like printf("%s", ...) ) is a common mistake that leads to undefined behavior.
Manipulating Strings with Pointers
Since strings are just character arrays, pointer arithmetic is a natural and efficient way to work with them. Incrementing a char* moves it one byte to the next character. Let's look at a classic interview-style question that tests this.
Consider the following code:
#include <stdio.h>
int main(void) {
char s[] = "arista2015";
char *p = s;
printf("%s\n", p + p[4] - p[1]);
return 0;
}
Take a moment to predict the output. The common line of reasoning is:
ppoints tos[0], which is'a'.p[4]iss[4], which is the character't'.p[1]iss[1], which is the character'r'.- The expression becomes
p + 't' - 'r'. - On an ASCII system,
't'is 116 and'r'is 114. So, the expression simplifies top + 116 - 114, which isp + 2. p + 2points tos[2], which is'i'. Therefore,printfshould printista2015.
This output is what you'll likely see on most common platforms. However, an interviewer would expect you to see the critical flaw here.
Pointer arithmetic output asked in an interview
The provided Stack Overflow discussion is an excellent analysis of this exact problem. It reveals why the 'obvious' answer isn't the whole story.
Read the first two answers. The first one explains the common logic and then corrects itself with an edit. The second answer (by M.M.) gets straight to the critical point about operator precedence and undefined behavior. Focus on why (p + p[4]) - p[1] is not the same as p + (p[4] - p[1]) and what makes it undefined behavior.
As you just read, the expression is evaluated according to C's operator precedence, which is left-to-right for addition and subtraction: (p + p[4]) - p[1].
The intermediate step, p + p[4], is p + 't' or p + 116. This attempts to create a pointer that is 116 elements past the beginning of the s array, which has only 11 elements (10 characters + null terminator). According to the C standard, pointer arithmetic is only defined within the bounds of an allocated object (or one past the end). Anything else is undefined behavior (UB).
An ideal interview answer would be: "On a typical ASCII machine, this would likely print ista2015. However, the code invokes undefined behavior because the intermediate calculation p + p[4] produces an invalid pointer that points far outside the bounds of the array s. The compiler is free to do anything, and the behavior is not guaranteed."
Pitfall 1: Buffer Overflows
This is one of the most dangerous and well-known pitfalls in C programming. A buffer overflow occurs when data is written past the boundaries of a fixed-size buffer. This overwrites adjacent memory, which could be other variables, pointers, or even control data on the stack like function return addresses.
In your work with AUTOSAR and ISO 26262, you know that robustness and preventing unintended behavior are paramount. Buffer overflows are a direct threat to this.

While gets() is notoriously unsafe and has been removed from the C standard, the same danger exists with common functions like strcpy, strcat, and sprintf if the destination buffer's size isn't checked beforehand.
Example:
#include <string.h>
#include <stdio.h>
void process_data(const char* data) {
char buffer[8];
// DANGER: If 'data' is 8 characters or more, strcpy will write past
// the end of 'buffer', including the null terminator.
strcpy(buffer, data);
printf("Buffer contains: %s\n", buffer);
}
int main(void) {
// This will cause a buffer overflow.
process_data("CAN_ID_0x1A3_EXT");
return 0;
}
The "safe" alternatives like strncpy and snprintf are better, but require careful handling. For instance, if strncpy fills the entire destination buffer, it does not append a null terminator. You must do it manually.
strncpy(dest, src, sizeof(dest));dest[sizeof(dest) - 1] = '\0';
Pitfall 2: The Null Terminator (\0)
The entire string convention relies on the null terminator. Getting it wrong leads to bugs that are often hard to track.
Off-by-One Errors
This is the most common \0 related bug. When allocating memory for a string, you must always remember to add one extra byte for the null terminator.
The document 'C Traps and Pitfalls' discusses exactly this scenario in the context of concatenating two strings after a dynamic allocation.
Read the section titled '4.5. Pointers are not Arrays'. Pay close attention to the evolution of the code example, specifically why the first call to malloc is incorrect and how it's fixed by adding + 1.
As the text explained, strlen(s) returns the number of characters without counting the \0. To make a copy, you need strlen(s) + 1 bytes. Forgetting this +1 means your string will not be null-terminated, and any function trying to read it will run past the allocated memory until it either crashes or happens to find a zero byte in memory.
The Null Pointer vs. The Null String
This is another point of confusion that interviewers may probe.
- A Null Pointer: A pointer that does not point to any valid memory location. Its value is
NULL(typically(void*)0). - An Empty String: A valid pointer to a memory location that contains a single character: the null terminator
\0.
char *p_null = NULL;
char *p_empty = ""; // Points to a read-only memory location containing {'\0'}
Dereferencing p_null (*p_null) or passing it to functions like strcmp or printf("%s", ...) results in undefined behavior, usually a segmentation fault. In contrast, passing p_empty is perfectly safe: strlen(p_empty) is 0, and printf("%s", p_empty) prints nothing.
Practice: Applying the Concepts
Let's solidify this with a quick exercise. Implement the standard library function strchr using only pointer arithmetic. Its signature is char *strchr(const char *s, int c);. It should search for the first occurrence of the character c in the string s and return a pointer to it. If the character is not found, it should return NULL.
Solution
#include <stddef.h> // For NULL
char* my_strchr(const char *s, int c) {
// Loop until we hit the null terminator
while (*s != '\0') {
if (*s == (char)c) {
// Found it. Return the current pointer.
// We cast away const here, which matches the standard library's
// behavior for historical reasons.
return (char*)s;
}
s++; // Move to the next character
}
// Special case: if c is '\0', we should return a pointer to the string's terminator.
if (*s == (char)c) {
return (char*)s;
}
// If we get here, the character was not found.
return NULL;
}
This simple function beautifully demonstrates pointer-based traversal and checking for the null terminator.
Conclusion
Mastering C strings means mastering pointers and being acutely aware of the conventions and pitfalls. For your interviews, being able to articulate these issues demonstrates a depth of experience beyond just writing functional code.
Key Takeaways:
- Pointer Arithmetic: It's the native way to manipulate strings, but operations must stay within the allocated bounds of the array to avoid undefined behavior.
- Buffer Overflows: A critical pitfall resulting from writing past an array's boundary. Use size-aware functions (
snprintf,strncpy) and always validate input lengths. - The Null Terminator: This is the heart of the C string convention. Always allocate
+1byte for it, and ensure it's present, especially when using functions likestrncpy. - Null Pointer vs. Empty String:
NULLis an invalid address.""is a valid address pointing to a\0. Never dereferenceNULL.
In our next lesson, we will officially dive into double pointers. We will explore what char ** truly represents, how it's used to manage collections of strings (like argv in main), and how to use double pointers to modify other pointers from within a function.