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
111: Essential Types with stddef.h: size_t, ptrdiff_t, NULL
I want to show you a snippet of code that looks perfectly fine at first glance. It's a common pattern: we have a buffer of data, and we want to iterate through it backward to perform some operation. Take a look at this:
#include <stdio.h>
#include <stddef.h>
void print_backward(const char *str, size_t len) {
for (size_t i = len - 1; i >= 0; i--) {
printf("%c", str[i]);
}
printf("\n");
}
int main() {
const char *msg = "Hello C";
print_backward(msg, 7);
return 0;
}
If you run this, you aren't going to see "C olleH". Instead, you're going to get a crash or an infinite loop that prints garbage until the program dies. Why? Because size_t is an unsigned type. When i is 0 and the loop executes i--, it doesn't become -1. It wraps around to the largest possible value a size_t can hold. Since that huge number is definitely greater than or equal to 0, the loop never ends.
The Danger of Unsigned Wrap-around
This is the "gotcha" of size_t. We use it for sizes and indices because a memory address or a buffer length can't be negative, and we want to be able to address as much memory as the CPU architecture allows. If you used a signed int, you'd limit yourself to 2GB of addressable space on many 64-bit systems, which is a waste of a modern processor.
The fix isn't to go back to using int. The fix is to change how you think about the loop or use the correct type for the distance. If you must go backward, you can use a while loop or a slightly more clever for loop:
for (size_t i = len; i > 0; i--) {
printf("%c", str[i - 1]);
}
By checking i > 0 and accessing i - 1, we avoid the underflow entirely. This is the "professional" way to handle unsigned indices.
Measuring Memory Gaps with ptrdiff_t
Sometimes, you aren't just counting indices; you're calculating the distance between two pointers. This is where ptrdiff_t comes in. While size_t is for sizes, ptrdiff_t is for differences.
If you subtract one pointer from another, the result could be negative (if the first pointer comes before the second). ptrdiff_t is a signed integer type specifically designed to hold this result. I've seen developers try to cast pointer subtraction to size_t, and that's a recipe for disaster the moment your pointers are out of order.
char buffer[100];
char *start = &buffer[0];
char *end = &buffer[20];
ptrdiff_t diff = end - start; // Result: 20
ptrdiff_t reverse_diff = start - end; // Result: -20
Use size_t when you're talking about "How big is this?" or "Which index am I at?" Use ptrdiff_t when you're asking "How far apart are these two memory locations?"
The Standard Way to say "Nothing"
Finally, let's talk about NULL. You've likely used it, but it's important to understand that it's defined in stddef.h (among other places). In C, NULL is a macro that expands to a null pointer constant.
I often see beginners use 0 or '\0' interchangeably with NULL. While the compiler often lets you get away with it, using NULL explicitly tells anyone reading your code: "I am dealing with a pointer here, not an integer or a character." It's about intent. When you check if (ptr == NULL), you are making a statement about the state of a memory address, not a numeric value.
- size_t: Unsigned. Use for array indexing,
sizeofresults, and loop counters that only go up. - ptrdiff_t: Signed. Use for the result of pointer subtraction.
- NULL: A constant representing a pointer that points to nothing.
📋 Practical Task
Measuring Buffer Offsets with ptrdiff_t
Your task is to write a function that calculates the distance between a "cursor" pointer and the start of a buffer. However, the cursor might be moved behind the start of the buffer (simulating an underflow or a relative offset).
Create a program that does the following:
- Defines a
chararray of 50 elements. - Creates a pointer
cursorthat points to the 10th element. - Calculates the distance from the start of the array to the
cursorusingptrdiff_tand prints it. - Moves the
cursorto point to the 5th element. - Calculates the distance from the 10th element to the new
cursorposition usingptrdiff_tand prints it (this should be a negative number). - Includes a check to see if the
cursorisNULLbefore performing the calculations to ensure the program is robust.
There are no comments for now.