C Standards: K&R to C23
C has been in use for over fifty years, and the language has changed during that time. The changes are recorded in standards: official documents that define exactly what valid C code means, so that a program written for one compiler works with another. Knowing the versions helps you read old code, follow textbooks and exam questions written for older C, and choose the right compiler options.
In this lesson you will walk through every major version of C, from the original K&R book to C23, see what each one added, and learn how to tell your compiler which standard to use.
Who makes the C standard?
The first C standard was published by ANSI, the American National Standards Institute, in 1989. Since then, C has been standardised internationally by ISO and IEC. The work is done by a committee called WG14 (formally ISO/IEC JTC1/SC22/WG14), made up of compiler writers, library authors and users. The committee's working papers are public on open-std.org.
The timeline
| Version | Year | Official document | __STDC_VERSION__ |
|---|---|---|---|
| K&R C | 1978 | The book The C Programming Language | Not defined |
| C89 / C90 | 1989 / 1990 | ANSI X3.159-1989, then ISO/IEC 9899:1990 | Not defined |
| C95 | 1995 | Amendment 1 to C90 | 199409L |
| C99 | 1999 | ISO/IEC 9899:1999 | 199901L |
| C11 | 2011 | ISO/IEC 9899:2011 | 201112L |
| C17 | 2018 | ISO/IEC 9899:2018 | 201710L |
| C23 | 2024 | ISO/IEC 9899:2024 | 202311L |
The nicknames do not always match the publication years: C17 was published in 2018 (and is sometimes called C18), and C23 was published on 31 October 2024. The version macro uses the year and month in which the text was finalised.
K&R C (1978)
Dennis Ritchie created C at Bell Labs in the early 1970s. In 1978, Brian Kernighan and Dennis Ritchie published The C Programming Language, and for about ten years that book, known as K&R, was the only definition of C. K&R C had no function prototypes: the compiler did not check the number or types of arguments. Functions defined their parameters in a separate list, and a missing type meant int:
/* K&R style (1978): not valid in C23 */
main()
{
int total;
total = add(40, 2);
printf("%d\n", total);
}
int add(a, b)
int a, b;
{
return a + b;
}C89 and C90: the first standard
ANSI standardised C in 1989 (C89, also called ANSI C), and ISO adopted the same text in 1990 (C90), so C89 and C90 are the same language. It added function prototypes (borrowed from C++), the keywords void, const, volatile and signed, and, very importantly, a standard library with headers such as stdio.h, stdlib.h and string.h. The second edition of the K&R book (1988) described this ANSI C. A small amendment in 1995 (C95) added better support for wide characters and international text.
C99: the big modernisation
C99 brought many features that modern C code relies on:
//comments, and declarations anywhere in a block, including insidefor (int i = 0; ...).long long, the_Booltype withstdbool.h, and fixed-width types instdint.handinttypes.h.inlinefunctions,snprintf,restrictpointers and the__func__name.- Designated initialisers (
.roll = 15,[2] = 50) and compound literals. - Variable-length arrays, flexible array members, variadic macros and
complex.h.
C11: concurrency and safety
C11 added _Generic for type-generic code, _Static_assert for compile-time checks, anonymous structures and unions, _Alignas and _Alignof, _Noreturn, Unicode types char16_t and char32_t in uchar.h, and aligned_alloc. For programs that run several threads at once it added threads.h and atomics in stdatomic.h, both optional. It removed gets, made variable-length arrays optional, added the "x" mode for fopen, and introduced the optional bounds-checking "Annex K" functions such as strcpy_s, which few libraries implement.
#include <stdio.h>
#define type_name(x) _Generic((x), int: "int", double: "double", char *: "string", default: "other")
_Static_assert(sizeof(int) >= 4, "int must be at least 32 bits");
int main(void)
{
printf("%s, %s, %s\n", type_name(42), type_name(3.5), type_name("Pune"));
return 0;
}int, double, string
_Generic chooses a result based on the type of its first argument, at compile time. _Static_assert stops compilation with the message if its condition is false; here it passes on every common system. The underscore-and-capital spellings were chosen so that the new keywords could not clash with names in existing programs.
C17: bug fixes
C17 added no new features. It corrected defects and unclear wording found in C11. For practical purposes, C11 and C17 are the same language, and C17 was the default in GCC for several years.
C23: the current standard
C23 is the largest update since C99. Many of its features have appeared throughout this course:
- New keywords:
bool,trueandfalse(Data Types & bool);nullptr(Pointers, NULL & nullptr);constexpr(Constants);autotype inference;typeofandtypeof_unqual; and the plain spellingsstatic_assert,alignas,alignofandthread_local. - Syntax: standard attributes such as
[[nodiscard]],[[maybe_unused]],[[deprecated]]and[[fallthrough]]; binary literals (0b1010); digit separators (1'00'000); empty initialisers= {}; enums with a fixed underlying type; and bit-precise integers_BitInt(N). - Preprocessor:
#embed,#elifdef,#elifndef,#warningand__has_include. - Library: checked arithmetic in
stdckdint.h, bit utilities instdbit.h,strdupandstrndup,memset_explicitfor wiping secrets,unreachable(), and the%bbinary format forprintf. - Removals and clean-ups: K&R-style function definitions are gone,
int f()now means "no parameters", and signed integers must use two's complement.
Here is a small program written in C23 style:
#include <stdio.h>
constexpr int SEATS = 8;
[[nodiscard]] int count_free(const bool booked[], int n)
{
auto count = 0;
for (int i = 0; i < n; i++) {
if (!booked[i]) {
count++;
}
}
return count;
}
int main(void)
{
bool booked[SEATS] = {};
booked[2] = true;
booked[5] = true;
const char *note = nullptr;
int free_seats = count_free(booked, SEATS);
if (free_seats < SEATS) {
note = "some seats are taken";
}
printf("Free seats: %d of %d (%s)\n", free_seats, SEATS, note);
return 0;
}Free seats: 6 of 8 (some seats are taken)
The same program in C17 would need #include <stdbool.h> for bool, #define SEATS 8 instead of constexpr, = {0} instead of = {}, int count instead of auto, NULL (from stddef.h) instead of nullptr, and it would have no [[nodiscard]].
Choosing a standard with -std
GCC and Clang let you pick the standard with -std. The gnu variants (such as -std=gnu17 or -std=gnu23) add the compiler's own extensions; the plain c variants follow the standard more strictly, especially together with -pedantic, which warns when you use an extension.
| Option | Standard |
|---|---|
-std=c89 or -std=c90 | C89/C90 |
-std=c99 | C99 |
-std=c11 | C11 |
-std=c17 | C17 |
-std=c23 | C23 (GCC 14 and newer; GCC 13 uses -std=c2x) |
GCC 15 and newer use gnu23 by default; other compilers and older versions may default to an older standard, so pass -std explicitly. If you must build code for an older compiler, choose the newest standard it supports and avoid C23-only features.
Common mistakes
- Learning from very old material without knowing it.
void main(),gets, K&R definitions and implicitintbelong to the past. - Mixing up C and C++ standards. C23 and C++23 are different documents with different rules; C has no classes, templates or
constexprfunctions. - Relying on the compiler's default standard. It differs between compiler versions; always pass
-std. - Assuming every C23 feature exists everywhere. Library additions such as
stdbit.hdepend on your C library version, not only the compiler.
Key points
- K&R C (1978) was defined by a book; C89/C90 was the first official standard and added prototypes and the standard library.
- C99 added
//comments,long long,stdbool.h,stdint.h, designated initialisers and declarations anywhere. - C11 added
_Generic,_Static_assert, threads and atomics and removedgets; C17 only fixed defects. - C23 (ISO/IEC 9899:2024) added
bool,nullptr,constexpr,auto,typeof, attributes,#embedand checked arithmetic, and removed K&R definitions. __STDC_VERSION__identifies the standard:201710Lfor C17 and202311Lfor C23.- Select the standard with
-std=c23(or another version) and add-pedanticto catch extensions.
Exercise
Rewrite this old-style program in modern C23. Use a proper prototype-style definition, int main(void), the bool type instead of TRUE and FALSE macros, and declare the loop variable inside the for.
#include <stdio.h>
#define TRUE 1
#define FALSE 0
int is_even();
main()
{
int i;
int count;
count = 0;
for (i = 1; i <= 10; i++) {
if (is_even(i) == TRUE) {
count++;
}
}
printf("Even numbers: %d\n", count);
return 0;
}
int is_even(n)
int n;
{
if (n % 2 == 0) return TRUE;
return FALSE;
}Show solution
#include <stdio.h>
bool is_even(int n)
{
return n % 2 == 0;
}
int main(void)
{
int count = 0;
for (int i = 1; i <= 10; i++) {
if (is_even(i)) {
count++;
}
}
printf("Even numbers: %d\n", count);
return 0;
}Even numbers: 5
is_even now has a typed parameter list, so the compiler checks every call. It returns the comparison directly as a bool, and the if tests it without comparing to TRUE. Defining is_even before main removes the need for a separate prototype.
Quick Quiz
1. Which standard first added function prototypes and the standard library?
The ANSI C89 standard, adopted by ISO as C90, introduced prototypes and the standard library.
2. Which version removed the gets function?
C11 removed gets because it could not limit the input size.
3. What is special about C17?
C17 is a bug-fix revision of C11.
4. Which value of __STDC_VERSION__ means C23?
C23 defines __STDC_VERSION__ as 202311L.
Learn C with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
