C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
59: Using AddressSanitizer
One of the most frustrating things about C is that it often lets you get away with murder. You can write past the end of an array, access memory you've already freed, or miscalculate a pointer offset, and your program might still run perfectly fine—until it suddenly crashes in production for no apparent reason. I've spent countless hours of my career staring at a segfault that only happens on Tuesdays, only to realize I had a one-off error in a loop three hundred lines of code away from the crash site.
That's why we use AddressSanitizer (ASan). It's essentially a "super-debugger" built into the compiler that instruments your memory accesses. Instead of hoping the OS catches your mistake with a segmentation fault, ASan catches it the exact millisecond it happens and tells you exactly which line caused it.
The invisible heap overflow
Take a look at this snippet. I'm trying to write a simple function that duplicates a string but converts it to uppercase. On the surface, it looks reasonable.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
char* uppercase_copy(const char* src) {
int len = strlen(src);
// We allocate space for the characters
char* dest = malloc(len);
for (int i = 0; i < len; i++) {
dest[i] = toupper((unsigned char)src[i]);
}
dest[len] = '\0'; // Null terminate the string
return dest;
}
int main() {
char* result = uppercase_copy("hello");
printf("Result: %s\n", result);
free(result);
return 0;
}
If you compile and run this with a standard gcc main.c, it will probably print "Result: HELLO" and exit with a return code of 0. It "works." But it's actually broken. I've allocated len bytes, but I'm writing len + 1 bytes because of that null terminator. I'm corrupting a tiny slice of memory right after my allocation.
Catching the ghost with -fsanitize=address
To find this, you don't need a debugger or a bunch of print statements. You just need to add a specific flag during compilation and linking. Try this:
gcc -fsanitize=address -g main.c -o bug_hunt
The -fsanitize=address flag tells the compiler to add "shadow memory" tracking to every single memory access. I added -g so that ASan can give us actual line numbers instead of cryptic hex addresses. When you run this version, the program won't just exit quietly. It will scream at you with a massive red error report:
==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000015 at pc 0x000000000104...
WRITE of size 1 at 0x602000000015 thread T0
#0 0x104567 in uppercase_copy main.c:14
#1 0x1045a1 in main main.c:18
Look at the top of that report. heap-buffer-overflow. Then look at the stack trace: main.c:14. That points exactly to dest[len] = '\0';. ASan just saved me twenty minutes of guessing.
Accounting for the null terminator
The fix here is a classic C mistake: forgetting that strlen returns the length of the string excluding the null terminator. Since every C string needs that \0 to mark the end, we always need to allocate one extra byte.
char* uppercase_copy(const char* src) {
int len = strlen(src);
// Fix: Allocate len + 1 to make room for the null terminator
char* dest = malloc(len + 1);
if (dest == NULL) return NULL; // Always check your mallocs!
for (int i = 0; i < len; i++) {
dest[i] = toupper((unsigned char)src[i]);
}
dest[len] = '\0';
return dest;
}
If you recompile with ASan and run it again, the error disappears. Now the memory we're writing to is actually memory we own. While ASan does add some overhead to the execution speed and memory usage, it is an absolute requirement for my development workflow. I never ship C code that hasn't been "ASan-cleaned" first.
📋 Practical Task
Fixing the Use-After-Free in a Dynamic Array
You are given a program that manages a dynamic list of integers. However, the program has a critical memory bug: it attempts to access an element of the array after the array has been resized (reallocated), but it uses a stale pointer.
Your Task:
- Compile the provided code using
-fsanitize=address -g. - Run the program and analyze the ASan output to identify the exact line where the
use-after-freeoccurs. - Fix the code so that the program correctly handles the pointer after
reallocis called. - Verify the fix by running the program through ASan again to ensure no memory errors remain.
#include <stdio.h>
#include <stdlib.h>
int main() {
int* numbers = malloc(3 * sizeof(int));
numbers[0] = 10; numbers[1] = 20; numbers[2] = 30;
int* old_ptr = numbers; // Keeping a reference to the original start
// Expand the array to hold 6 integers
int* temp = realloc(numbers, 6 * sizeof(int));
if (temp == NULL) {
free(numbers);
return 1;
}
numbers = temp;
numbers[3] = 40;
numbers[4] = 50;
numbers[5] = 60;
// BUG: Using the old pointer after realloc might have moved the memory block
printf("The first element via old pointer is: %d\n", old_ptr[0]);
free(numbers);
return 0;
}
There are no comments for now.