2024-03-29.

A brief reference for the differences between C standards. Source

K&R C (1972-1989)

Anything before standardisation was called K&R C as per the first edition of the book that was the de facto implementation at the time. The book came out at 1978, so sometimes this implementation is also referred to as C78.

The following language features were introduced by the book:

C functions that returned an int could be declared without the int keyword upfront as it was implicit that they returned int.

/* K&R */
long function_that_returns_long();
function_that_returns_int();

/* newer standards */
long function_that_returns_long();
int function_that_returns_int();

Function declarations also had an implicit int return when the return type was not specified:

/* K&R */
function_that_returns_int() {
  return 10;
}

/* newer standards */
int function_that_returns_int() {
  return 10;
}

Function declarations also didn't include information about function arguments. This means that function parameter type checks were not performed. Some compilers would still raise a warning if the function was called with the wrong number of arguments or if different calls to the same function had different numbers of arguments.

After the book publication and before C89, several compilers added other features to the language:

C89 (ANSI / ISO C)

The goal of this standards was to create a superset of K&R out of the many unofficial features, such as:

/* before (no argument info in declarations) */
long function_that_returns_long();

/* C89 */
long function_that_returns_long(int a, int b);

C99

The following functionalities were introduced in this new version of the standard:

inline void swap(int *m, int* n) {
  int tmp = *m;
  *m = *n;
  *n = tmp;
}
struct point p = { .x = 1, .y = 2 };
my_sweet_function_call((struct x) {1, 2});
float read_and_process(int n)
{
    float vals[n];

    for (int i = 0; i < n; ++i)
        vals[i] = read_val();

    return process(n, vals);
}
void function(int rows, int cols, int array[static rows][cols]) {
    // Function implementation
}
struct vectord {
    short len;    // there must be at least one other data member
    double arr[]; // the flexible array member must be last.
    // The compiler may reserve extra padding space here, like it can between struct members
};
void update(int *restrict a, int *restrict b, int n) {
    // Using 'restrict' keyword to indicate no aliasing
    for (int i = 0; i < n; i++) {
        a[i] += b[i];
    }
}

C11

// Align the array to a 16-byte boundary.
_Alignas(16) char aligned_array[32];

// Get the alignment requirement of a data type.
printf("Alignment of int: %zu\n", _Alignof(int));

// Allocate 32 bytes of memory aligned to a 16-byte boundary
void *ptr = aligned_alloc(16, 32);

// <stdalign.h> header provides similar functionality:
alignas(16) char aligned_array[32];
printf("Alignment of aligned_array: %zu\n", alignof(aligned_array));
#define print_value(x) _Generic((x), \
    int:    printf("%d\n", x), \
    float:  printf("%f\n", x), \
    double: printf("%lf\n", x), \
    default: printf("Unsupported type\n") \
)
struct T { int tag; union { float x; int n; }; };

C17

C17 fixes numerous minor defects in C11 without introducing new language features.

[Extra] Embedded C

Embedded C is a set of language extensions for the C programming language by the C Standards Committee to address commonality issues that exist between C extensions for different embedded systems.

These include:

Many embedded processors lack an FPU, because integer arithmetic units require substantially fewer logic gates and consume much smaller chip area than an FPU