Skip to Content
Course content

6: Understanding the C Standard: C89 vs C99 vs C11 vs C17 vs C23

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I want to show you a piece of code that looks perfectly reasonable. You've probably written something like this a dozen times if you've dabbled in Java, C++, or even modern JavaScript. But if you're working on an older system or using a compiler with strict legacy settings, this will blow up in your face.

#include <stdio.h>

int main() {
    int limit = 10;
    
    for (int i = 0; i < limit; i++) {
        printf("Iteration %d\n", i);
    }
    
    return 0;
}

If you try to compile this with an old-school C89 compiler (or GCC with the -std=c89 flag), you'll get an error like: 'for' loop initial declarations are only allowed in C99 mode. It's frustrating because the logic is flawless. The problem isn't your logic; it's that you're speaking a "dialect" of C that the compiler isn't configured to understand.

The "Variable at the Top" Rule

In the original ANSI C (C89), you couldn't just declare a variable anywhere you wanted. All your variables had to be declared at the very beginning of a scope (the start of a function or the start of a curly-brace block). The int i = 0 inside the for loop is a "mixed declaration," which C89 considers illegal. To fix this for a C89 environment, you have to hoist the declaration:

#include <stdio.h>

int main() {
    int limit = 10;
    int i; // Declare it here, at the top of the block
    
    for (i = 0; i < limit; i++) {
        printf("Iteration %d\n", i);
    }
    
    return 0;
}

Now it compiles. But let's be honest: that's annoying. This is why understanding the C standards matters. Depending on which version of the standard your project uses, the "rules of the road" change.

The Evolution from C89 to C99

C89 was the bedrock, but it was rigid. C99 was the first major "modernization." It gave us the ability to declare variables anywhere, introduced inline functions, and added long long int for when 32 bits just weren't enough. Most importantly, it introduced <stdbool.h>. Before C99, if you wanted a boolean, you just used an int and prayed everyone agreed that 0 was false and 1 was true. C99 finally gave us bool, true, and false.

Polishing the Edge with C11 and C17

By the time we hit C11, the focus shifted toward hardware realities. We got _Generic (which allows a kind of basic function overloading) and static_assert. I use static_assert constantly—it lets you catch errors at compile-time (like checking if an integer is actually 4 bytes on the target system) rather than waiting for the program to crash in production.

C17 didn't actually add new features. Think of it as a "maintenance release." It fixed bugs and clarified ambiguities in C11. If you're choosing between C11 and C17, just pick C17. It's essentially C11 but with the typos fixed.

The New Frontier: C23

C23 is where things get really interesting. It's finally cleaning up the language's legacy baggage. For example, bool, true, and false are becoming actual keywords in the language, meaning you won't even need to include <stdbool.h> anymore. It also introduces nullptr, which is a huge win. Using NULL (which is often just a macro for 0) has caused subtle bugs for decades; nullptr gives us a distinct type for null pointers, similar to what C++ did years ago.

Choosing Your Standard

In the real world, you'll rarely be told "use C89." Instead, you'll see a Makefile with a flag like -std=c11. If you're starting a fresh project today, I recommend targeting C17 for stability or C23 if you're using a cutting-edge compiler and want the best ergonomics. Just remember: the more "modern" the standard you use, the less portable your code might be to ancient embedded systems that are still stuck in the 80s.




📋 Practical Task

Implementing a Compile-Time Architecture Guard

One of the most powerful features introduced in C11 is static_assert. This allows you to ensure that your code is being compiled for the correct hardware assumptions without needing to run the program first.

Your Task: Create a program that uses static_assert to verify that the size_t type is exactly 8 bytes (which is standard for 64-bit systems).

  • Define a program that attempts to print a "Success" message if the assertion passes.
  • Use the <stddef.h> or <stdint.h> header.
  • The Test: First, try to compile your code using a C89 flag (e.g., gcc -std=c89 main.c). Observe the compiler error. Then, compile it using the C11 or C17 flag (e.g., gcc -std=c11 main.c) to see it succeed.

Hint: Your assertion should look something like static_assert(sizeof(size_t) == 8, "Error: 64-bit system required");

Rating
0 0

There are no comments for now.

to be the first to leave a comment.