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 suffixAttribute 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:
- C17: Technical corrigendum fixing defects in C11
- C23: Major feature release with modern language enhancements
Key C23 features include:
- typeof Operators: Type introspection and generic programming
- Binary Literals: More intuitive bit manipulation
- Enhanced Preprocessor: More powerful conditional compilation
- Attribute Syntax: Standardized compiler hints
- Extended Integer Types: Support for arbitrary precision integers
- Enhanced Static Assertions: More flexible compile-time checking
- 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.