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
202: Sequence Points and Undefined Behavior
Imagine you're at a busy deli. You tell the counter person, "I want a turkey sandwich and a bag of chips." In a normal world, the employee hears the whole order, processes the payment, and then hands you the food. The "payment" is the clear boundary—once that's done, the order is finalized. Now, imagine a chaotic deli where the employee starts bagging the chips while simultaneously trying to charge you for the sandwich, but they're using the same single notepad to track your total. They scribble a number, then erase it, then add to it, all at the same time. If they don't have a clear "stop and check" moment, your final bill could be anything from $0 to $100, depending on who moved their pen faster.
In C, a sequence point is that "stop and check" moment. It's a point in the execution of the program where all previous "side effects" (like changing the value of a variable) are guaranteed to be complete before the program moves on. When you write code that tries to change the same variable twice between two sequence points, you're essentially inviting that chaotic deli employee into your CPU. You've entered the realm of Undefined Behavior (UB).
The Danger of the Unspecified Order
I've seen a lot of beginners try to be "clever" by condensing their code. They think they're saving space, but they're actually writing a lottery ticket. Take a look at this:
int i = 5;
int result = i++ + ++i;
You might think the answer is 12. Maybe you think it's 13. Here is the reality: the C standard doesn't actually say what happens here. Because there is no sequence point between the first i++ and the ++i, the compiler is free to evaluate them in any order it wants. Some compilers will give you 12, some 13, and some might do something completely bizarre because they optimized the register usage in a way you didn't expect. I always tell my juniors: if you can't tell exactly what the value is without knowing which version of GCC or Clang you're using, the code is broken.
Where the Compiler Stops Caring
Undefined Behavior is the scariest part of C because the compiler doesn't have to warn you. It doesn't throw a runtime error; it just... happens. When you trigger UB, you're telling the compiler, "I don't care what you do with this." The compiler takes that literally. It might assume that since UB is "impossible" in a correct program, it can optimize away entire chunks of your if statements or rearrange your logic in ways that seem nonsensical.
The most common sequence points you'll encounter are the semicolon (;) at the end of a statement and the logical operators && and ||. For example, in if (a == 1 && b == 2), C guarantees that a == 1 is fully evaluated before it even looks at b. That's why we can use the left side of an && to protect the right side from crashing (like checking if a pointer is NULL before dereferencing it).
The Classic Increment Trap
The most frequent "gotcha" happens inside function calls, specifically printf. You might be tempted to do something like this:
int count = 0;
printf("Current: %d, Next: %d\n", count++, ++count);
You'd expect it to print "0, 2" or "1, 1". But the C standard explicitly states that the order in which function arguments are evaluated is unspecified. The compiler can evaluate the arguments right-to-left, left-to-right, or in any order it finds convenient for the stack. I've seen this exact line of code produce different results on an ARM processor versus an x86 processor. It's a nightmare to debug because the bug disappears the moment you turn on certain compiler optimizations.
My rule of thumb? Never modify a variable and use it again in the same expression unless there is a guaranteed sequence point (like a && or a semicolon) between them. Be explicit. Write three lines of boring code instead of one line of "magic" code. Your future self—and your teammates—will thank you.
📋 Practical Task
Stabilizing the Buffer Index Counter
You have been handed a snippet of legacy code designed to track a circular buffer. The previous developer tried to be concise, but the code is exhibiting erratic behavior across different compilers because of sequence point violations.
The Broken Code:
int buffer_index = 0; int max_size = 10; // This line is causing Undefined Behavior buffer_index = (buffer_index + 1) % max_size; // The dev actually wrote it like this in the original source: // buffer_index = ++buffer_index % max_size;Actually, look at this specific problematic function:
void update_indices(int *idx, int limit) { // BUG: Modifying and using idx in the same expression without a sequence point *idx = (*idx++ % limit); }Your Task:
- Rewrite the
update_indicesfunction to achieve the same goal (incrementing the index and wrapping it around the limit) without triggering Undefined Behavior. - Ensure that the increment and the modulo operation happen in a clearly defined order.
- Write a small
mainfunction to test that the index correctly wraps fromlimit-1back to0.
There are no comments for now.