C17 and C23 Features

Introduction

The C17 standard (ISO/IEC 9899:2018) was primarily a technical corrigendum that fixed defects in the C11 standard without introducing significant new features. However, the upcoming C23 standard (ISO/IEC 9899:2024) represents a major evolution of the C language, introducing numerous modern features that make C more expressive, safer, and aligned with contemporary programming practices.

C23 features include typeof operators, binary literals, enhanced preprocessor directives, attribute syntax, and many other improvements that address long-standing limitations in the language. Understanding these features is crucial for writing modern C code.

C17: Technical Corrigendum

C17 was focused on fixing defects and inconsistencies in C11 rather than adding new features. Some notable changes include:

Defect Fixes

  • Clarification of atomic operations semantics
  • Fixes to alignment requirements
  • Corrections to floating-point behavior
  • Improvements to multi-threading support

Example of C17 Improvements

#include <stdio.h>
#include <stdalign.h>
#include <stdatomic.h>

// C17 clarified alignment requirements
_Alignas(16) struct aligned_data {
    int values[4];
};

// C17 improved atomic operations
_Atomic int atomic_counter = 0;

int main() {
    printf("Alignment of aligned_data: %zu\n", alignof(struct aligned_data));
    
    // Atomic operations with clearer semantics
    atomic_fetch_add(&atomic_counter, 1);
    printf("Atomic counter: %d\n", atomic_load(&atomic_counter));
    
    return 0;
}

C23: typeof Operators

One of the most significant additions in C23 is the typeof operator, which allows you to declare variables with the same type as an expression. This feature brings C closer to C++’s decltype and makes generic programming much easier.

Basic typeof Usage

#include <stdio.h>

#define max(a, b) ({ \
    typeof(a) _a = (a); \
    typeof(b) _b = (b); \
    _a > _b ? _a : _b; \
})

int main() {
    int x = 10, y = 20;
    double a = 3.14, b = 2.71;
    
    // typeof allows the macro to work with different types
    int max_int = max(x, y);
    double max_double = max(a, b);
    
    printf("Max of %d and %d: %d\n", x, y, max_int);
    printf("Max of %.2f and %.2f: %.2f\n", a, b, max_double);
    
    return 0;
}

typeof with Complex Expressions

#include <stdio.h>
#include <stdlib.h>

int main() {
    int array[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    // Declare variable with same type as array element
    typeof(array[0]) element = 42;
    
    // Declare variable with same type as array (pointer decay)
    typeof(array) *ptr = &array;
    
    // Declare variable with same type as complex expression
    typeof((int)3.14 + (long)42) result = 0;
    
    printf("Element: %d\n", element);
    printf("First array element via pointer: %d\n", (*ptr)[0]);
    printf("Result type size: %zu bytes\n", sizeof(result));
    
    return 0;
}

typeof_unqual for Unqualified Types

#include <stdio.h>

int main() {
    const volatile int value = 42;
    
    // typeof preserves qualifiers
    typeof(value) qualified_var = 100;  // Still const volatile
    
    // typeof_unqual removes all qualifiers
    typeof_unqual(value) unqualified_var = 200;  // Just int
    
    printf("Qualified var type size: %zu\n", sizeof(qualified_var));
    printf("Unqualified var type size: %zu\n", sizeof(unqualified_var));
    
    return 0;
}

Binary Literals

C23 introduces binary literals, allowing you to write numbers in base-2 format using the 0b or 0B prefix.

Basic Binary Literals

#include <stdio.h>

int main() {
    // Binary literals
    int binary1 = 0b1010;        // 10 in decimal
    int binary2 = 0B11110000;    // 240 in decimal
    int binary3 = 0b1111'1111;   // 255 in decimal (with digit separator)
    
    printf("0b1010 = %d\n", binary1);
    printf("0B11110000 = %d\n", binary2);
    printf("0b1111'1111 = %d\n", binary3);
    
    // Useful for bit manipulation
    unsigned char flags = 0b1010'0101;
    printf("Flags: 0x%02X (binary: ", flags);
    for (int i = 7; i >= 0; i--) {
        printf("%d", (flags >> i) & 1);
    }
    printf(")\n");
    
    return 0;
}

Bit Manipulation with Binary Literals

#include <stdio.h>

// Bit manipulation macros using binary literals
#define BIT(n) (1U << (n))
#define SET_BIT(value, n) ((value) |= BIT(n))
#define CLEAR_BIT(value, n) ((value) &= ~BIT(n))
#define TOGGLE_BIT(value, n) ((value) ^= BIT(n))
#define CHECK_BIT(value, n) (((value) & BIT(n)) != 0)

int main() {
    unsigned char status = 0b0000'0000;
    
    printf("Initial status: 0b");
    for (int i = 7; i >= 0; i--) {
        printf("%d", (status >> i) & 1);
    }
    printf("\n");
    
    // Set some bits
    SET_BIT(status, 0);  // Set bit 0
    SET_BIT(status, 3);  // Set bit 3
    SET_BIT(status, 7);  // Set bit 7
    
    printf("After setting bits 0, 3, 7: 0b");
    for (int i = 7; i >= 0; i--) {
        printf("%d", (status >> i) & 1);
    }
    printf("\n");
    
    // Toggle a bit
    TOGGLE_BIT(status, 3);  // Toggle bit 3
    
    printf("After toggling bit 3: 0b");
    for (int i = 7; i >= 0; i--) {
        printf("%d", (status >> i) & 1);
    }
    printf("\n");
    
    // Check specific bits
    printf("Bit 0 is %s\n", CHECK_BIT(status, 0) ? "set" : "clear");
    printf("Bit 3 is %s\n", CHECK_BIT(status, 3) ? "set" : "clear");
    printf("Bit 5 is %s\n", CHECK_BIT(status, 5) ? "set" : "clear");
    
    return 0;
}

Enhanced Preprocessor Directives

C23 introduces several new preprocessor directives that make conditional compilation more powerful and flexible.

#elifdef and #elifndef

#include <stdio.h>

#define DEBUG_MODE
// #define PRODUCTION_MODE

int main() {
#if defined(DEBUG_MODE)
    printf("Debug mode enabled\n");
#elifdef PRODUCTION_MODE
    printf("Production mode enabled\n");
#elifndef TESTING_MODE
    printf("Testing mode not defined, using default mode\n");
#else
    printf("Unknown mode\n");
#endif
    
    return 0;
}

#warning Directive

#include <stdio.h>

// Compiler version check
#if __STDC_VERSION__ < 202311L
    #warning "This code is designed for C23 or later"
#endif

// Platform-specific warnings
#ifdef __APPLE__
    #warning "This code may need adjustments for macOS"
#endif

int main() {
    printf("Program running\n");
    
#ifdef __GNUC__
    #warning "Consider using Clang for better C23 support"
#endif
    
    return 0;
}

#embed Directive (Conceptual)

Note: The #embed directive is still being standardized and may not be available in all compilers yet.

// Example of proposed #embed directive
// #embed "shader.glsl" limit(1024) prefix(0xFF, 0xD8) suffix(0x00)

// This would embed the contents of shader.glsl as a byte array
// with a JPEG header prefix and null terminator suffix

Attribute Syntax

C23 introduces a standardized attribute syntax using double square brackets [[]], making it easier to specify compiler-specific features in a portable way.

Standard Attributes

#include <stdio.h>

// [[nodiscard]] - warn if return value is ignored
[[nodiscard]] int calculate_value(int x) {
    return x * x;
}

// [[maybe_unused]] - suppress unused variable warnings
void example_function([[maybe_unused]] int debug_param) {
    // debug_param might only be used in debug builds
#ifndef NDEBUG
    printf("Debug param: %d\n", debug_param);
#endif
}

// [[deprecated]] - mark functions as deprecated
[[deprecated("Use calculate_value instead")]]
int old_function(int x) {
    return x + x;
}

// [[fallthrough]] - indicate intentional fallthrough in switch
int process_state(int state) {
    switch (state) {
        case 1:
        case 2:
            printf("Low state\n");
            [[fallthrough]];
        case 3:
        case 4:
            printf("Medium state\n");
            [[fallthrough]];
        case 5:
            printf("High state\n");
            break;
        default:
            printf("Unknown state\n");
    }
    return 0;
}

int main() {
    // [[nodiscard]] example - compiler should warn if ignored
    calculate_value(5);  // Warning: ignoring return value
    
    example_function(42);
    
    // [[deprecated]] example
    old_function(10);  // Warning: deprecated function
    
    process_state(3);
    
    return 0;
}

Extended Integer Types

C23 introduces _BitInt(N) types for extended integer precision, allowing for integers with more than 64 bits.

_BitInt Example (Conceptual)

// Note: _BitInt is still being implemented in compilers
/*
#include <stdio.h>

int main() {
    // 128-bit integer
    _BitInt(128) big_number = 0;
    
    // 256-bit integer for cryptographic operations
    _BitInt(256) crypto_key = 0;
    
    printf("Size of 128-bit integer: %zu bytes\n", sizeof(big_number));
    printf("Size of 256-bit integer: %zu bytes\n", sizeof(crypto_key));
    
    return 0;
}
*/

Enhanced _Static_assert

C23 improves the _Static_assert macro by making the message parameter optional.

Enhanced Static Assertions

#include <stdio.h>
#include <stdint.h>

// Traditional static assertion with message
_Static_assert(sizeof(int) >= 4, "int must be at least 32 bits");

// C23 allows static assertion without message
_Static_assert(sizeof(long long) > sizeof(long));

// Complex static assertions
typedef struct {
    uint32_t header;
    char data[256];
    uint32_t checksum;
} Packet;

_Static_assert(sizeof(Packet) == 264, "Packet structure size must be exactly 264 bytes");
_Static_assert(_Alignof(Packet) >= 4, "Packet must be aligned to at least 4 bytes");

int main() {
    printf("All static assertions passed\n");
    printf("Size of Packet: %zu bytes\n", sizeof(Packet));
    printf("Alignment of Packet: %zu bytes\n", _Alignof(Packet));
    
    return 0;
}

New Standard Library Functions

C23 adds several new standard library functions, including improved string handling and memory allocation functions.

strdup and strndup

#define _GNU_SOURCE  // For strdup in some implementations
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Simple implementation of strdup if not available
char *my_strdup(const char *s) {
    size_t len = strlen(s) + 1;
    char *copy = malloc(len);
    if (copy) {
        memcpy(copy, s, len);
    }
    return copy;
}

int main() {
    const char *original = "Hello, World!";
    
    // Using strdup (if available in C23 implementation)
#ifdef _GNU_SOURCE
    char *copy1 = strdup(original);
    if (copy1) {
        printf("strdup copy: %s\n", copy1);
        free(copy1);
    }
#endif
    
    // Using custom implementation
    char *copy2 = my_strdup(original);
    if (copy2) {
        printf("my_strdup copy: %s\n", copy2);
        free(copy2);
    }
    
    // strndup example (copy at most n characters)
    char *copy3 = strndup(original, 5);
    if (copy3) {
        printf("strndup copy (5 chars): %s\n", copy3);
        free(copy3);
    }
    
    return 0;
}

Enhanced aligned_alloc

#include <stdio.h>
#include <stdlib.h>

int main() {
    // Allocate 100 bytes aligned to 16-byte boundary
    void *ptr = aligned_alloc(16, 100);
    if (ptr) {
        printf("Allocated address: %p\n", ptr);
        printf("Alignment check: %s\n", 
               ((uintptr_t)ptr % 16 == 0) ? "OK" : "FAIL");
        
        // Use the memory...
        
        free(ptr);
    } else {
        printf("Allocation failed\n");
    }
    
    return 0;
}

Practical Examples

Modern Configuration System

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

// Using binary literals for configuration flags
typedef enum {
    CFG_DEBUG      = 0b0000'0001,
    CFG_LOGGING    = 0b0000'0010,
    CFG_NETWORK    = 0b0000'0100,
    CFG_DATABASE   = 0b0000'1000,
    CFG_SECURITY   = 0b0001'0000,
    CFG_COMPRESSION= 0b0010'0000
} ConfigFlags;

// Using typeof for generic configuration handling
#define CONFIG_GET(config, key) \
    typeof(((config)->key)) get_config_value(const Config *c, const char *k) { \
        if (strcmp(k, #key) == 0) return c->key; \
        /* Handle other keys */ \
        return (typeof(((config)->key)))0; \
    }

typedef struct {
    int max_connections;
    double timeout;
    bool enable_cache;
    ConfigFlags flags;
} Config;

// Using attributes for better code quality
[[nodiscard]] bool validate_config(const Config *config) {
    [[maybe_unused]] int debug_value = 42;
    
    if (config->max_connections <= 0) {
        return false;
    }
    
    if (config->timeout < 0.0) {
        return false;
    }
    
    return true;
}

[[deprecated("Use validate_config instead")]]
bool old_validate_config(Config *config) {
    return validate_config(config);
}

void print_config(const Config *config) {
    printf("Configuration:\n");
    printf("  Max Connections: %d\n", config->max_connections);
    printf("  Timeout: %.2f\n", config->timeout);
    printf("  Enable Cache: %s\n", config->enable_cache ? "Yes" : "No");
    printf("  Flags: 0b");
    for (int i = 7; i >= 0; i--) {
        printf("%d", (config->flags >> i) & 1);
    }
    printf("\n");
}

int main() {
    // Initialize configuration with binary literals and designated initializers
    Config config = {
        .max_connections = 100,
        .timeout = 30.0,
        .enable_cache = true,
        .flags = CFG_DEBUG | CFG_LOGGING | CFG_NETWORK
    };
    
    // Static assertion to ensure configuration structure size
    _Static_assert(sizeof(Config) > 0, "Config structure must not be empty");
    
    if (validate_config(&config)) {
        print_config(&config);
    } else {
        printf("Invalid configuration\n");
    }
    
    // Deprecated function usage (should generate warning)
    old_validate_config(&config);
    
    return 0;
}

Summary

C17 and C23 represent important milestones in the evolution of the C programming language:

  1. C17: Technical corrigendum fixing defects in C11
  2. C23: Major feature release with modern language enhancements

Key C23 features include:

  1. typeof Operators: Type introspection and generic programming
  2. Binary Literals: More intuitive bit manipulation
  3. Enhanced Preprocessor: More powerful conditional compilation
  4. Attribute Syntax: Standardized compiler hints
  5. Extended Integer Types: Support for arbitrary precision integers
  6. Enhanced Static Assertions: More flexible compile-time checking
  7. New Standard Library Functions: Improved string and memory operations

These features make modern C more expressive, safer, and better aligned with contemporary programming practices while maintaining the language’s core principles of efficiency and low-level control.