前置知识: C

C23 与 C2y 新标准

00:00
9 min Advanced 2026/6/14

C23标准新特性、C2y草案、GCC常用选项、多文件编译、Makefile、C与汇编混编、标准库速查。

1. C23 标准主要新特性

C23(ISO/IEC 9899:2024)是 C 语言的最新正式标准,于 2024 年正式发布。它引入了大量现代化改进,使 C 语言在保持简洁高效的同时,获得了更强大的表达能力和更高的安全性。

1.1 #embed 资源嵌入

#embed 是 C23 最受期待的特性之一,它允许在编译时将外部二进制资源直接嵌入到源代码中,无需再借助 xxd 等工具将资源转换为 C 数组。

#include <stdio.h>

// 将图片文件直接嵌入为字节数组
const unsigned char icon[] = {
    #embed "icon.png"
};

// 限制嵌入的最大字节数
const unsigned char small_icon[] = {
    #embed "icon.png" limit(256)
};

// 嵌入着色器源代码
const char vertex_shader[] = {
    #embed "shader.vert",
    '\0'  // 确保以空字符结尾
};

int main(void) {
    printf("icon size: %zu bytes\n", sizeof(icon));
    printf("small_icon size: %zu bytes\n", sizeof(small_icon));
    printf("shader: %s\n", vertex_shader);
    return 0;
}

传统方式对比

/* 旧方法:使用 xxd -i 手动生成 C 数组 */
/* 执行: xxd -i icon.png > icon.h */
/* 生成的 icon.h 内容类似:
   unsigned char icon_png[] = {
     0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, ...
   };
   unsigned int icon_png_len = 12345;
*/

/* 新方法:直接使用 #embed */
const unsigned char icon[] = {
    #embed "icon.png"
};

1.2 typeof 与 typeof_unqual

C23 引入了 typeoftypeof_unqual 关键字,允许在类型位置引用表达式的类型,极大简化了泛型编程。

#include <stdio.h>

// typeof: 保留所有限定符(const, volatile 等)
// typeof_unqual: 去除所有限定符,获取底层类型

int main(void) {
    const int x = 10;
    typeof(x) a = 20;          // a 的类型是 const int
    typeof_unqual(x) b = 30;   // b 的类型是 int(去除了 const)

    // a = 40;  // 错误:a 是 const int
    b = 40;     // 正确:b 是 int

    printf("a = %d, b = %d\n", a, b);

    // 在声明中使用 typeof
    double arr[5] = {1.0, 2.0, 3.0, 4.0, 5.0};
    typeof(arr) arr2;           // arr2 是 double[5]
    typeof(*arr) val = arr[0];  // val 是 double

    return 0;
}

在泛型宏中的应用

#include <stdio.h>

// 使用 typeof 实现类型安全的交换宏
#define SWAP(a, b) do {         \
    typeof(a) _tmp = (a);       \
    (a) = (b);                  \
    (b) = _tmp;                 \
} while (0)

int main(void) {
    int x = 10, y = 20;
    double p = 3.14, q = 2.71;

    SWAP(x, y);
    SWAP(p, q);

    printf("x=%d, y=%d\n", x, y);     // x=20, y=10
    printf("p=%.2f, q=%.2f\n", p, q); // p=2.71, q=3.14

    return 0;
}

1.3 #elifdef 与 #elifndef

C23 新增了 #elifdef#elifndef 预处理指令,简化了条件编译的写法。

/* 旧写法 */
#ifdef _WIN32
    #define PLATFORM "Windows"
#elif defined(__linux__)
    #define PLATFORM "Linux"
#elif defined(__APPLE__)
    #define PLATFORM "macOS"
#else
    #define PLATFORM "Unknown"
#endif

/* C23 新写法 */
#ifdef _WIN32
    #define PLATFORM "Windows"
#elifdef __linux__
    #define PLATFORM "Linux"
#elifdef __APPLE__
    #define PLATFORM "macOS"
#else
    #define PLATFORM "Unknown"
#endif

/* #elifndef 示例 */
#ifndef MY_HEADER_H
    #define MY_HEADER_H
#elifndef MY_HEADER_ALT
    #define MY_HEADER_ALT
#endif

1.4 改进的属性语法

C23 引入了 C++ 风格的属性语法 [[attribute]],同时标准化了一些常用属性。

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

// [[deprecated]]: 标记已弃用的函数或变量
[[deprecated("use new_func() instead")]]
int old_func(int x) {
    return x * 2;
}

int new_func(int x) {
    return x * 3;
}

// [[fallthrough]]: 标记 switch 中有意穿透
int process(int code) {
    switch (code) {
        case 1:
            printf("Case 1\n");
            [[fallthrough]];
        case 2:
            printf("Case 2\n");
            break;
        default:
            printf("Default\n");
    }
    return 0;
}

// [[nodiscard]]: 提醒调用者不要忽略返回值
[[nodiscard]]
int allocate_id(void) {
    static int id = 0;
    return ++id;
}

// [[maybe_unused]]: 抑制未使用警告
int compute([[maybe_unused]] int verbose) {
    return 42;
}

// [[noreturn]]: 函数不会返回
[[noreturn]]
void fatal_error(const char *msg) {
    fprintf(stderr, "Fatal: %s\n", msg);
    exit(1);
}

int main(void) {
    // old_func(5);  // 编译器会发出弃用警告
    printf("new_func: %d\n", new_func(5));

    int id = allocate_id();  // 正确:使用了返回值
    // allocate_id();       // 警告:忽略了 nodiscard 返回值

    process(1);
    return 0;
}

1.5 位精确类型

C23 标准化了 <stdbit.h> 头文件,提供对位操作的标准化支持,同时引入了位精确整数类型。

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

int main(void) {
    unsigned int val = 0b10110000;

    // 计数前导零
    printf("Leading zeros: %zu\n", stdc_leading_zeros(val));

    // 计数尾随零
    printf("Trailing zeros: %zu\n", stdc_trailing_zeros(val));

    // 计数置位(1 的个数)
    printf("Set bits: %zu\n", stdc_count_ones(val));

    // 检查是否为 2 的幂
    printf("Is power of 2: %s\n",
           stdc_has_single_bit(val) ? "yes" : "no");

    // 找到最低位的位宽度
    printf("Bit width: %zu\n", stdc_bit_width(val));

    // 位精确类型(编译器支持时可用)
    // _BitInt(N) 类型允许指定精确位宽
    _BitInt(8) small = 127;       // 精确 8 位有符号整数
    unsigned _BitInt(12) medium = 4095;  // 精确 12 位无符号整数

    printf("small = %d\n", (int)small);
    printf("medium = %u\n", (unsigned)medium);

    return 0;
}

1.6 其他 C23 改进

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

// 1. bool/true/false/NULL 成为真正的关键字(不再需要头文件)
// C23 中 bool, true, false 是内建关键字

// 2. 函数声明无需再写 void 表示无参
int get_value() {  // C23 中等价于 int get_value(void)
    return 42;
}

// 3. auto 类型推断(仅限块作用域)
auto compute_sum(int a, int b) -> int {  // 返回类型后置
    return a + b;
}

// 4. constexpr 对象(编译时常量)
constexpr int MAX_SIZE = 100;
constexpr double PI = 3.14159265;

// 5. 零初始化语法 = {}
struct Point {
    double x, y;
};

int main(void) {
    // bool 是内建关键字
    bool flag = true;

    // 零初始化
    struct Point p = {};  // x=0.0, y=0.0
    int arr[5] = {};      // 全部初始化为 0

    printf("flag = %d\n", flag);
    printf("MAX_SIZE = %d\n", MAX_SIZE);
    printf("sum = %d\n", compute_sum(3, 4));
    printf("point: (%.1f, %.1f)\n", p.x, p.y);

    return 0;
}

2. C2y 草案与未来方向

C2y 是 C23 之后的下一个标准修订版,目前仍在草案阶段。以下是一些正在讨论的重要提案。

2.1 改进的预处理能力

/*
 * C2y 草案中的预处理改进提案:
 *
 * 1. #elifdef 增强扩展
 *    - 支持更复杂的条件表达式
 *    - 允许在预处理指令中使用 __has_include 的逻辑组合
 *
 * 2. 静态断言增强
 *    - 改进 _Static_assert 的错误信息格式
 *    - 支持更丰富的编译时诊断
 */

// __has_include 检查头文件是否存在(部分编译器已支持)
#if __has_include(<stdbit.h>)
    #include <stdbit.h>
    #define HAS_STDBIT 1
#else
    #define HAS_STDBIT 0
#endif

// __has_c_attribute 检查属性支持
#if __has_c_attribute(deprecated)
    #define DEPRECATED(msg) [[deprecated(msg)]]
#else
    #define DEPRECATED(msg)
#endif

2.2 错误处理改进

/*
 * C2y 正在讨论的错误处理改进:
 *
 * 1. 结构化错误传播
 *    - 类似 Rust 的 Result<T, E> 模式
 *    - 允许函数同时返回值和错误状态
 *
 * 2. defer 语法(提案中)
 *    - 类似 Go 语言的 defer
 *    - 确保资源在作用域结束时释放
 */

// 模拟 defer 模式(当前可用 __attribute__((cleanup)))
static void cleanup_file(FILE **fp) {
    if (*fp) {
        fclose(*fp);
        *fp = NULL;
    }
}

#define DEFER_FILE(name, path, mode)        \
    FILE *name __attribute__((cleanup(cleanup_file))) = fopen(path, mode)

int read_config(void) {
    DEFER_FILE(fp, "config.txt", "r");
    if (!fp) return -1;

    char line[256];
    while (fgets(line, sizeof(line), fp)) {
        printf("%s", line);
    }
    // fp 在此处自动关闭,无需手动 fclose
    return 0;
}

3. GCC 常用编译选项

3.1 优化选项

# 无优化(默认),便于调试
gcc -O0 -o program program.c

# 基础优化,推荐日常开发使用
gcc -O2 -o program program.c

# 激进优化,可能改变浮点行为和调试体验
gcc -O3 -o program program.c

# 优化生成的代码体积
gcc -Os -o program program.c

# 针对特定架构优化
gcc -O2 -march=native -o program program.c

3.2 调试选项

# 生成调试信息(DWARF 格式)
gcc -g -o program program.c

# 同时启用优化和调试(推荐用于性能分析)
gcc -O2 -g -o program program.c

# 生成调试信息 + 源码级别调试
gcc -g3 -o program program.c

# 地址 sanitizer(检测内存错误)
gcc -fsanitize=address -g -o program program.c

# 未定义行为 sanitizer
gcc -fsanitize=undefined -g -o program program.c

# 线程 sanitizer(检测数据竞争)
gcc -fsanitize=thread -g -o program program.c

# 内存 sanitizer(检测未初始化读取)
gcc -fsanitize=memory -g -o program program.c

3.3 警告选项

# 启用常用警告
gcc -Wall -o program program.c

# 启用更多额外警告
gcc -Wall -Wextra -o program program.c

# 将警告视为错误(CI/CD 推荐使用)
gcc -Wall -Wextra -Werror -o program program.c

# 特定警告选项
gcc -Wall -Wextra -Wpedantic -Wconversion -Wshadow -o program program.c

3.4 标准选择

# 指定 C 标准
gcc -std=c23 -o program program.c      # C23 标准
gcc -std=c17 -o program program.c      # C17 标准
gcc -std=c11 -o program program.c      # C11 标准
gcc -std=c99 -o program program.c      # C99 标准
gcc -std=gnu23 -o program program.c    # C23 + GNU 扩展

# 查看预定义宏
gcc -dM -E - < /dev/null | grep STDC

4. 多文件编译与链接

4.1 分离声明与实现

头文件 math_utils.h

#ifndef MATH_UTILS_H
#define MATH_UTILS_H

// 函数声明
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
double divide(int a, int b);

// 外部变量声明
extern int call_count;

#endif // MATH_UTILS_H

实现文件 math_utils.c

#include "math_utils.h"

// 全局变量定义
int call_count = 0;

int add(int a, int b) {
    call_count++;
    return a + b;
}

int subtract(int a, int b) {
    call_count++;
    return a - b;
}

int multiply(int a, int b) {
    call_count++;
    return a * b;
}

double divide(int a, int b) {
    call_count++;
    return (b != 0) ? (double)a / b : 0.0;
}

主程序 main.c

#include <stdio.h>
#include "math_utils.h"

int main(void) {
    printf("3 + 5 = %d\n", add(3, 5));
    printf("10 - 4 = %d\n", subtract(10, 4));
    printf("6 * 7 = %d\n", multiply(6, 7));
    printf("22 / 7 = %.4f\n", divide(22, 7));
    printf("Total calls: %d\n", call_count);
    return 0;
}

编译与链接

# 分步编译
gcc -std=c23 -Wall -c math_utils.c -o math_utils.o
gcc -std=c23 -Wall -c main.c -o main.o
gcc math_utils.o main.o -o program

# 一步编译
gcc -std=c23 -Wall math_utils.c main.c -o program

5. Makefile 基本结构

5.1 简单 Makefile

# 变量定义
CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -O2 -g
LDFLAGS =
TARGET = program
SRCS = main.c math_utils.c string_utils.c
OBJS = $(SRCS:.c=.o)

# 默认目标
all: $(TARGET)

# 链接目标文件生成可执行文件
$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)

# 编译 .c 为 .o
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 头文件依赖(简化版)
main.o: main.c math_utils.h string_utils.h
math_utils.o: math_utils.c math_utils.h
string_utils.o: string_utils.c string_utils.h

# 清理
clean:
	rm -f $(OBJS) $(TARGET)

# 安装
install: $(TARGET)
	cp $(TARGET) /usr/local/bin/

.PHONY: all clean install

5.2 自动依赖生成

CC = gcc
CFLAGS = -std=c23 -Wall -Wextra -O2 -g -MMD -MP
TARGET = program
SRCS = $(wildcard *.c)
OBJS = $(SRCS:.c=.o)
DEPS = $(OBJS:.o=.d)

all: $(TARGET)

$(TARGET): $(OBJS)
	$(CC) $(LDFLAGS) $(OBJS) -o $(TARGET)

%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

-include $(DEPS)

clean:
	rm -f $(OBJS) $(DEPS) $(TARGET)

.PHONY: all clean

5.3 常用 Make 变量与自动变量

变量含义
$@当前目标名
$<第一个依赖文件
$^所有依赖文件(去重)
$?比目标新的依赖文件
$(CC)C 编译器,默认 cc
$(CFLAGS)C 编译选项
$(LDFLAGS)链接选项
$(LDLIBS)链接库(如 -lm -lpthread

6. C 与汇编混编

6.1 内联汇编(GCC 扩展)

#include <stdio.h>

// 基本内联汇编
static inline void nop(void) {
    __asm__ volatile ("nop");
}

// 读取时间戳计数器(x86-64)
static inline unsigned long long rdtsc(void) {
    unsigned int lo, hi;
    __asm__ volatile (
        "rdtsc"
        : "=a" (lo), "=d" (hi)
        :
        : "%rbx", "%rcx"
    );
    return ((unsigned long long)hi << 32) | lo;
}

// 原子比较并交换
static inline int cas(int *ptr, int expected, int desired) {
    int result;
    __asm__ volatile (
        "lock cmpxchg %2, %1"
        : "=a" (result), "+m" (*ptr)
        : "r" (desired), "0" (expected)
        : "memory"
    );
    return result;
}

int main(void) {
    unsigned long long t1 = rdtsc();
    // 执行一些操作
    for (volatile int i = 0; i < 1000; i++);
    unsigned long long t2 = rdtsc();
    printf("Cycles elapsed: %llu\n", t2 - t1);

    int value = 10;
    int success = cas(&value, 10, 20);
    printf("CAS result: %d, value: %d\n", success, value);
    return 0;
}

6.2 独立汇编文件调用

汇编文件 add.asm(x86-64 Linux)

; global add function: int add(int a, int b)
section .text
global add

add:
    mov eax, edi    ; 第一个参数 (System V ABI)
    add eax, esi    ; 加上第二个参数
    ret

C 调用代码

#include <stdio.h>

// 声明外部汇编函数
extern int add(int a, int b);

int main(void) {
    int result = add(10, 20);
    printf("10 + 20 = %d\n", result);
    return 0;
}

编译与链接

nasm -f elf64 add.asm -o add.o
gcc -std=c23 -Wall main.c add.o -o program
./program

7. 标准库常用函数速查

7.1 输入输出 <stdio.h>

函数说明示例
printf格式化输出printf("%d\n", x)
scanf格式化输入scanf("%d", &x)
fprintf格式化输出到文件fprintf(fp, "%d", x)
fscanf从文件格式化输入fscanf(fp, "%d", &x)
fopen打开文件fopen("a.txt", "r")
fclose关闭文件fclose(fp)
fgets读取一行fgets(buf, n, fp)
fputs写入字符串fputs(str, fp)
fread二进制读fread(buf, 1, n, fp)
fwrite二进制写fwrite(buf, 1, n, fp)
snprintf安全格式化到缓冲区snprintf(buf, n, "%d", x)

7.2 字符串操作 <string.h>

函数说明示例
strlen字符串strlen("hello") → 5
strcpy复制字符串strcpy(dst, src)
strncpy安全复制strncpy(dst, src, n)
strcat连接字符串strcat(dst, src)
strcmp比较字符串strcmp(s1, s2)
strncmp比较前 n 个字符strncmp(s1, s2, n)
strstr查找子串strstr(str, "sub")
strchr查找字符strchr(str, 'c')
strtok分割字符串strtok(str, ",")
memset填充内存memset(buf, 0, n)
memcpy复制内存memcpy(dst, src, n)
memmove安全复制(可重叠)memmove(dst, src, n)

7.3 数学函数 <math.h>

函数说明示例
sqrt平方sqrt(2.0) → 1.414
pow幂运算pow(2.0, 3.0) → 8.0
abs值(整数abs(-5) → 5
fabs值(浮fabs(-3.14) → 3.14
ceil向上取整ceil(3.2) → 4.0
floor向下取整floor(3.8) → 3.0
round四舍五入round(3.5) → 4.0
sin/cos/tan三角函数sin(3.14159/2)
log/log10log(2.718) → 1.0
fmod取余fmod(5.3, 2.0) → 1.3

7.4 内存分配 <stdlib.h>

函数说明示例
malloc分配内存malloc(n * sizeof(int))
calloc分配并清零calloc(n, sizeof(int))
realloc重新分配realloc(ptr, new_size)
free释放内存free(ptr)
atoi字符串整数atoi("42") → 42
atof字符串转浮atof("3.14") → 3.14
strtol安全字符串转 longstrtol("0xff", NULL, 16)
rand随机rand() % 100
srand设置随机种子srand(time(NULL))
qsort快速排序qsort(arr, n, sz, cmp)
bsearch二分查找bsearch(&key, arr, n, sz, cmp)
exit终止程序exit(EXIT_FAILURE)

7.5 C23 新增标准库 <stdbit.h>

函数说明
stdc_leading_zeros前导零计数
stdc_trailing_zeros尾随零计数
stdc_leading_ones前导一计数
stdc_trailing_ones尾随一计数
stdc_count_ones计数(popcount)
stdc_count_zeros计数
stdc_has_single_bit是否为 2 的幂
stdc_bit_width
stdc_bit_floor最大 2 的幂 ≤
stdc_bit_ceil最小 2 的幂 ≥

8. 编译流程详解

源代码(.c) → [预处理 cpp] → 预处理文件(.i) → [编译 cc1] → 汇编代码(.s)
    → [汇编 as] → 目标文件(.o) → [链接 ld] → 可执行文件(a.out)
# 查看各阶段输出
gcc -E program.c -o program.i      # 仅预处理
gcc -S program.c -o program.s      # 预处理 + 编译为汇编
gcc -c program.c -o program.o      # 预处理 + 编译 + 汇编
gcc program.o -o program           # 仅链接

# 查看符号表
nm program.o

# 查看反汇编
objdump -d program.o

# 查看依赖的动态库
ldd program

# 查看段信息
readelf -S program.o

9. 延伸阅读

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式