前置知识: CCC

复杂声明解析

49 minIntermediate2026/7/20

C 语言复杂声明的形式语法、右左法则、函数指针与数组指针的解析方法,涵盖 cdecl 工具、ABI 规范与真实项目案例。

1. 学习目标与导论

C 语言的声明语法以”难以阅读”著称,但其设计并非随意。本章从形式文法、历史动因、工程实践三个维度,系统性地解析 C 复杂声明的设计哲学与阅读方法,使读者能够准确解析任意嵌套深度的声明,并生产性地使用 typedef 与函数指针表。

1.1 学习目标速览

Bloom 层次中文术语本章对应目标
remember记忆记忆声明符运算符优先级
understand理解解释右左法则的形式步骤
apply应用用 typedef 分解复杂声明
analyze分析分析函数指针数组的 ABI
evaluate评价评估 C 声明语法的设计权衡
create创造设计事件循环库

1.2 为什么 C 声明如此复杂

一段经典的”迷宫式”声明:

int (*(*foo)(double))[3];

这是”foo 是一个指针,指向一个函数,函数接受 double 参数,返回一个指针,指向包含 3 个 int 的数组”。对初学者而言,这段代码几乎不可读。但其设计遵循严格的语法规则,且与表达式语法对称——这正是 C 声明的核心设计原则:声明模拟使用(declaration follows use)。

1.3 本章结构

本章共分 14 节,从历史动机出发,逐步推进到形式文法、工程实践与案例研究。

1. 学习目标与导论
2. 历史动机:C 声明语法的演进
3. 形式化定义:C 声明的 BNF 文法
4. 右左法则:解析算法
5. 三大声明符运算符
6. 函数指针详解
7. 数组指针与指针数组
8. 复杂声明案例库
9. typedef:声明的分解工具
10. 对比分析:C/C++/Rust/Go/Zig
11. 常见陷阱与未定义行为
12. 工程实践:编译选项与静态分析
13. 案例研究:Linux 内核、SQLite、Redis
14. 习题与参考答案
15. 参考文献与延伸阅读

2. 历史动机:C 声明语法的演进

理解 C 声明语法,必须回到其 BCPL/B 语言根源,以及 Dennis Ritchie 在 1972 年的设计决策。

2.1 BCPL 与 B 语言:类型无关的先驱(1967-1969)

1967 年,Martin Richards 在剑桥设计 BCPL(Basic Combined Programming Language),这是一种无类型语言:所有变量都是”机器字”(machine word)。声明语法极为简单:

LET FOO = 42

1969 年,Ken Thompson 在 BCPL 基础上设计 B 语言(用于 PDP-7 上的早期 UNIX),保留了无类型设计:

foo = 42;

2.2 C 语言的类型化革命(1972)

1972 年,Dennis Ritchie 在 B 语言基础上引入类型系统,创造 C 语言。他面临的核心设计问题是:如何在保留 B 语言”声明模拟使用”风格的同时,引入类型标注?

Ritchie 在《The Development of the C Language》(1993)中写道:

“The idea that a declaration should look like a use, in the sense that the syntax of an expression involving a name should mimic the syntax of its declaration, was an early and persistent one.”

这一原则要求:int *p 的声明形式,与 *p 作为表达式(返回 int)的形式对称。即:

声明:  int *p;
使用:  int x = *p;  // *p 的类型是 int

同理,数组声明:

声明:  int a[10];
使用:  int x = a[i];  // a[i] 的类型是 int

函数声明:

声明:  int f(int);
使用:  int x = f(42);  // f(42) 的类型是 int

2.3 优先级的来源:BCPL 的遗产

C 声明中,后缀运算符 []() 的优先级高于前缀运算符 *。这一选择继承自表达式语法:

int a[10];      // a 是数组
int *b = &a[0]; // a[0] 的类型是 int,&a[0] 是 int*
int c = *b;     // *b 是 int

如果 []* 优先级相同(或 * 更高),则 int *a[10] 会被解析为”指向 10 个 int 的指针”,而非”10 个 int 指针的数组”,破坏了”声明模拟使用”原则。

2.4 螺旋式解析:复杂性的根源

[]()* 嵌套时,“声明模拟使用”原则导致声明必须从内向外螺旋式阅读:

int (*foo)(int);

阅读步骤:

  1. foo 是一个标识符
  2. 向右看 ():foo 是一个函数(等等,先看括号)
  3. 实际上,括号 (*foo) 优先,foo 是一个指针
  4. 向右看 (int):指向一个接受 int 的函数
  5. 向左看 int:函数返回 int

这种”螺旋式”阅读正是 C 声明被批评的根源。Peter van der Linden 在《Expert C Programming》(1994)中专门用一章讨论”声明语法的恐怖”。

2.5 标准化历程

版本年份声明相关改动
K&R C1978奠定”声明模拟使用”原则
C891989形式化为 BNF 文法,引入 const/volatile
C991999引入变长数组(VLA)、restrict_Complex
C112011引入 _Alignas_Generic_Noreturn
C172018勘误,无重大声明改动
C232024引入 constexprauto(类型推断)、typeof_BitInt

C23 的 auto 类型推断与 typeof 是对”螺旋式”语法的重要缓解:

/* C23 之前:必须显式写出复杂类型 */
int (*callback)(int, double) = get_handler();

/* C23:用 auto 推断 */
auto callback = get_handler();  // 类型由编译器推断

3. 形式化定义:C 声明的 BNF 文法

本节用 ISO/IEC 9899:2024 附录 A 的 BNF 文法,严格刻画 C 声明的语法结构。

3.1 声明的顶层结构

ISO/IEC 9899:2024 §6.7 将声明(declaration)定义为:

declarationdeclaration-specifiers init-declarator-listopt ;\text{declaration} \to \text{declaration-specifiers}\ \text{init-declarator-list}_{\text{opt}}\ ;

其中:

declaration-specifiersstorage-class-specifiertype-specifiertype-qualifierfunction-specifieralignment-specifier\text{declaration-specifiers} \to \text{storage-class-specifier} \mid \text{type-specifier} \mid \text{type-qualifier} \mid \text{function-specifier} \mid \text{alignment-specifier} init-declarator-listinit-declaratorinit-declarator-list,init-declarator\text{init-declarator-list} \to \text{init-declarator} \mid \text{init-declarator-list}, \text{init-declarator} init-declaratordeclaratordeclarator=initializer\text{init-declarator} \to \text{declarator} \mid \text{declarator} = \text{initializer}

3.2 声明符的递归文法

声明符(declarator)是声明的核心,递归定义:

declaratorpointeropt direct-declarator\text{declarator} \to \text{pointer}_{\text{opt}}\ \text{direct-declarator} \text{direct-declarator} \to \text{identifier} \mid (\text{declarator}) \mid \text{direct-declarator}\ [\text{assignment-expression}_{\text{opt}}\] \mid \text{direct-declarator}\ (\text{parameter-list}_{\text{opt}}\) pointer type-qualifier-listopt type-qualifier-listopt pointer\text{pointer} \to *\ \text{type-qualifier-list}_{\text{opt}} \mid *\ \text{type-qualifier-list}_{\text{opt}}\ \text{pointer}

3.3 文法的关键性质

从 BNF 文法可推导出 C 声明的关键性质:

  1. 递归性:declarator 通过 pointer(declarator) 递归,允许任意深度嵌套。
  2. 结合性:direct-declarator [ ]direct-declarator () 是后缀运算,优先级高于前缀 *
  3. 括号消除歧义:(declarator) 允许用括号改变默认优先级,这是 int (*p)[10]int *p[10] 区别的根源。

3.4 类型构造的方向性

C 的类型构造遵循”由内向外”的方向:

  • 标识符(变量名)是最内层
  • 后缀运算符 []() 向右扩展类型
  • 前缀运算符 * 向左扩展类型
  • 括号 () 改变扩展顺序

形式化地,设 T0T_0 为基础类型(如 int),则声明 int (*foo)(int) 的类型构造过程为:

T0=int,T1=T0(int)=int(int),T2=T1=int()(int)T_0 = \text{int}, \quad T_1 = T_0(\text{int}) = \text{int}(\text{int}), \quad T_2 = T_1^* = \text{int}(*)(\text{int})

最终 foo:T2=int()(int)\text{foo}: T_2 = \text{int}(*)(\text{int})

3.5 抽象声明符

当省略标识符时,declarator 退化为抽象声明符(abstract declarator),用于 sizeof、类型转换、函数原型:

sizeof(int (*)[10]);          /* 函数指针,指向 int[10] */
void *p = malloc(sizeof(int[3][4]));  /* 类型转换 */
int (*signal(int, int (*)(int)))(int);  /* signal 函数原型 */

抽象声明符在函数原型中尤为重要:它允许省略参数名,仅保留类型信息。

4. 右左法则:解析算法

右左法则是 Bell Labs 工程师总结的口语化算法,用于解析任意 C 声明。本节给出其形式化步骤。

4.1 算法形式描述

输入:C 声明 D

输出:该声明的自然语言描述

算法步骤:

  1. 从标识符(变量名)开始。
  2. 向右查看,直到遇到 );:
    • 遇到 [N]:记录”数组,含 N 个…”
    • 遇到 ():记录”函数,接受 … 参数,返回 …”
  3. 向左查看,直到遇到 (:
    • 遇到 *:记录”指针,指向 …”
    • 遇到类型限定符(const/volatile/restrict):记录限定符
    • 遇到类型说明符(int/char/struct ...):记录基础类型
  4. 跳出括号对,回到步骤 2,直到处理完所有符号。
  5. 将记录的描述按相反顺序组合(最内层最先记录,最后组合时在最前)。

4.2 算法的伪代码描述

function parse_declaration(decl):
    cursor = position of identifier in decl
    description = []
    stack = []
    loop:
        # 向右扫描
        while cursor < len(decl) and decl[cursor] in [')', ';']:
            if decl[cursor] == '[':
                n = parse_array_size(decl, cursor)
                description.push("数组,含 {} 个".format(n))
                cursor = skip_array(decl, cursor)
            elif decl[cursor] == '(':
                params = parse_params(decl, cursor)
                description.push("函数,接受 ({}) 返回".format(params))
                cursor = skip_params(decl, cursor)
            else:
                break
        # 向左扫描
        while cursor >= 0 and decl[cursor] != '(':
            if decl[cursor] == '*':
                description.push("指针,指向")
            elif decl[cursor] is type_qualifier:
                description.push(decl[cursor])
            elif decl[cursor] is type_specifier:
                description.push(decl[cursor])
                return combine(description)
            cursor -= 1
        # 跳出括号
        cursor -= 1  # skip '('

4.3 示例:解析经典复杂声明

示例 1:int *arr[10];

步骤当前位置记录
1arr(起点)
2[10]“数组,含 10 个”
3*“指针,指向”
4int”int”

组合:“arr 是数组,含 10 个指针,指向 int”

示例 2:int (*arr)[10];

步骤当前位置记录
1arr(起点)
2)(向右遇 ),停止)
3*“指针,指向”
4((向左遇 (,跳出)
5[10]“数组,含 10 个”
6int”int”

组合:“arr 是指针,指向含 10 个 int 的数组”

示例 3:int (*func)(int);

步骤当前位置记录
1func(起点)
2)(向右遇 ),停止)
3*“指针,指向”
4((向左遇 (,跳出)
5(int)“函数,接受 (int) 返回”
6int”int”

组合:“func 是指针,指向接受 (int) 返回 int 的函数”

示例 4:int (*(*foo)(double))[3];

步骤当前位置记录
1foo(起点)
2)(向右遇 ),停止)
3*“指针,指向”
4((向左遇 (,跳出)
5(double)“函数,接受 (double) 返回”
6)(向右遇 ),停止)
7*“指针,指向”
8((向左遇 (,跳出)
9[3]“数组,含 3 个”
10int”int”

组合:“foo 是指针,指向接受 (double) 返回指针的函数,该指针指向含 3 个 int 的数组”

4.4 算法的局限性

右左法则是一个口语化算法,存在以下局限:

  1. 不处理类型限定符嵌套:int *const *p;(p 是指向 const int* 的指针)需要额外规则。
  2. 不处理函数返回数组指针的极端情况:int (*f(void))[10]; 需要仔细处理。
  3. 不处理 C23 新特性:autotypeofconstexpr 需要扩展算法。

生产代码建议使用 cdecl 工具(见 §4.5)或编译器错误信息验证。

4.5 cdecl 工具

cdecl 是经典工具,可在 C 声明与英语之间互译:

$ cdecl
Type `help' or `?' for help
cdecl> explain int (*(*foo)(double))[3]
declare foo as pointer to function (double) returning pointer to array 3 of int
cdecl> declare foo as pointer to function (double) returning pointer to array 3 of int
int (*(*foo)(double))[3]

在线版:https://cdecl.org/

5. 三大声明符运算符

本节深入分析 C 声明的三大运算符:*[]()

5.1 运算符优先级

ISO/IEC 9899:2024 §6.5.1 规定声明符运算符优先级:

运算符位置优先级结合性
[]后缀1(最高)左到右
()后缀1(最高)左到右
*前缀2右到左
()包围3(改变优先级)-

关键规则:后缀 []() 优先级相同且高于前缀 *;包围括号 () 可改变优先级。

5.2 数组声明符 []

[] 声明数组,语法:

direct-declarator [ assignment-expression_opt ]
int a[10];           /* a 是 int[10] */
int b[3][4];         /* b 是 int[3][4] */
int c[] = {1, 2, 3}; /* c 是 int[3],大小由初始化推断 */

多维数组:int b[3][4] 中,b 是”包含 3 个元素的数组,每个元素是 int[4]“。内存布局为行主序(row-major):

b[0][0] b[0][1] b[0][2] b[0][3] b[1][0] b[1][1] ...

变长数组(VLA,C99):

size_t n = 10;
int vla[n];  /* C99 VLA,大小运行时确定 */

VLA 在 C11 中变为可选特性(__STDC_NO_VLA__ 宏检测)。

5.3 函数声明符 ()

() 声明函数,语法:

direct-declarator ( parameter-list_opt )
int f(void);              /* f 是无参函数,返回 int */
int g(int x);             /* g 接受 int,返回 int */
int h(int, double);       /* h 接受 int 与 double,返回 int(原型风格) */
int (*op)(int);           /* op 是函数指针 */

关键点:

  • int f()int f(void) 不同:前者是”接受未指定参数的函数”(K&R 风格),后者是”无参函数”。C23 弃用 f() 空参数列表,推荐 f(void)
  • 函数不能返回数组或函数,但可以返回指向数组或函数的指针。

5.4 指针声明符 *

* 声明指针,语法:

* type-qualifier-list_opt
int *p;                /* p 是 int* */
int *const cp = &x;    /* cp 是 const int*,顶层 const */
const int *pci = &x;   /* pci 是指向 const int 的指针 */
int *restrict rp;      /* rp 是 restrict int*(C99) */

const 的位置语义:

const int *p;        /* 指向 const int 的指针:*p 是 const,p 可变 */
int const *p;        /* 等价于上一行 */
int *const p = &x;   /* const 指针:p 是 const,*p 可变 */
const int *const p = &x;  /* 双 const:p 与 *p 均 const */

记忆口诀:“const 修饰其左侧的声明符;若 const 在最左,则修饰第一个声明符”。

5.5 包围括号 ()

包围括号 () 改变优先级,是 C 声明复杂性的核心:

int *a[10];        /* a 是数组,含 10 个 int* */
int (*a)[10];      /* a 是指针,指向 int[10] */

int *f(void);      /* f 是函数,返回 int* */
int (*f)(void);    /* f 是指针,指向返回 int 的函数 */

int *a[10](void);  /* 非法!函数不能作为数组元素 */
int (*a[10])(void);/* a 是数组,含 10 个函数指针 */

6. 函数指针详解

函数指针是 C 类型系统中最强大也最易混淆的特性之一。

6.1 函数指针的基本形式

int (*fp)(int, double);  /* fp 是函数指针 */

含义:fp 是一个指针,指向一个函数,该函数接受 (int, double) 参数,返回 int

6.2 函数指针的赋值与调用

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

int main(void) {
    int (*fp)(int, int) = add;   /* 函数名退化为指针 */
    int (*fp2)(int, int) = &add; /* 显式取地址,等价 */

    int result1 = fp(1, 2);       /* 直接调用 */
    int result2 = (*fp)(1, 2);    /* 解引用后调用,等价 */
    int result3 = fp2(3, 4);

    return 0;
}

关键规则:

  • 函数名在表达式中退化为指向该函数的指针(类似数组名退化为指向首元素的指针)。
  • fp(arg)(*fp)(arg) 完全等价,标准明确允许。

6.3 函数指针数组

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int divide(int a, int b) { return b ? a / b : 0; }

int main(void) {
    /* 函数指针数组:跳转表 */
    int (*ops[4])(int, int) = {add, sub, mul, divide};

    char op = '+';
    int idx;
    switch (op) {
        case '+': idx = 0; break;
        case '-': idx = 1; break;
        case '*': idx = 2; break;
        case '/': idx = 3; break;
        default: return 1;
    }

    int result = ops[idx](10, 3);
    printf("result: %d\n", result);
    return 0;
}

跳转表(jump table) 是函数指针数组的经典应用,替代 switch-case 链,提升性能(常量时间分发)。

6.4 返回函数指针的函数

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

/* get_op 返回函数指针 */
int (*get_op(char op))(int, int) {
    switch (op) {
        case '+': return add;
        case '-': return sub;
        default: return NULL;
    }
}

int main(void) {
    int (*fp)(int, int) = get_op('+');
    printf("%d\n", fp(10, 3));  /* 13 */
    return 0;
}

解析:get_op(char op) 是函数,接受 char,返回 int (*)(int, int)(函数指针)。

6.5 接受函数指针的函数:回调

/* qsort 接受比较函数指针 */
void qsort(void *base, size_t n, size_t size,
           int (*compar)(const void *, const void *));

/* 用户提供的比较函数 */
int cmp_int(const void *a, const void *b) {
    int x = *(const int *)a;
    int y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void) {
    int arr[] = {5, 2, 8, 1, 9, 3};
    qsort(arr, 6, sizeof(int), cmp_int);
    /* arr 现在是 {1, 2, 3, 5, 8, 9} */
    return 0;
}

6.6 signal:经典复杂声明

POSIX signal 函数是 C 复杂声明的经典案例:

void (*signal(int sig, void (*func)(int)))(int);

解析:

  1. signal 是函数名
  2. 接受 (int sig, void (*func)(int)):第一个参数是 int,第二个是函数指针 void (*)(int)
  3. 返回 void (*)(int):函数指针

用 typedef 简化:

typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);

POSIX <signal.h> 正是这样定义 sighandler_t 的。

7. 数组指针与指针数组

数组指针与指针数组是 C 声明中最易混淆的一对概念。

7.1 指针数组(array of pointers)

int *arr[5];  /* arr 是数组,含 5 个 int* */

内存布局:

arr: [0]-----> int
     [1]-----> int
     [2]-----> int
     [3]-----> int
     [4]-----> int

每个元素是独立的 int*,可指向不同位置。

使用场景:

  • 字符串数组:const char *names[] = {"Alice", "Bob", "Charlie"};
  • 不规则矩阵:每行长度不同
  • 命令行参数:int main(int argc, char **argv)

7.2 数组指针(pointer to array)

int (*ptr)[5];  /* ptr 是指针,指向 int[5] */

内存布局:

ptr -----> [int][int][int][int][int]

ptr 是单个指针,指向一个含 5 个 int 的数组。

使用场景:

  • 传递二维数组:void f(int (*matrix)[5], size_t rows);
  • 动态分配二维数组:int (*matrix)[5] = malloc(rows * sizeof(int[5]));

7.3 对比表

声明类型含义sizeof(arr)sizeof(arr[0])
int *arr[5]int *[5]5 个 int 指针的数组5 * sizeof(int*)sizeof(int*)
int (*arr)[5]int (*)[5]指向 int[5] 的指针sizeof(int(*)[5])sizeof(int[5])

7.4 二维数组作为函数参数

/* 三种等价写法 */
void f1(int matrix[3][4]);
void f2(int matrix[][4]);
void f3(int (*matrix)[4]);

/* 调用 */
int m[3][4];
f1(m);
f2(m);
f3(m);

关键规则:二维数组作为函数参数时,第一维可省略,第二维必须指定。因为编译器需要知道每行的长度以计算 matrix[i][j] 的地址:

addr(matrix[i][j])=base+i×cols×sizeof(int)+j×sizeof(int)\text{addr}(\text{matrix}[i][j]) = \text{base} + i \times \text{cols} \times \text{sizeof(int)} + j \times \text{sizeof(int)}

7.5 动态分配二维数组

/* 方法 1:数组指针 + 一次性分配(推荐) */
int (*matrix)[4] = malloc(3 * sizeof(int[4]));
matrix[1][2] = 42;
free(matrix);

/* 方法 2:指针数组 + 分行分配 */
int *rows[3];
for (int i = 0; i < 3; i++) {
    rows[i] = malloc(4 * sizeof(int));
}
rows[1][2] = 42;
for (int i = 0; i < 3; i++) free(rows[i]);

/* 方法 3:指针的指针 + 分行分配 */
int **matrix2 = malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++) {
    matrix2[i] = malloc(4 * sizeof(int));
}
matrix2[1][2] = 42;
for (int i = 0; i < 3; i++) free(matrix2[i]);
free(matrix2);

性能对比:

  • 方法 1:一次 malloc,内存连续,缓存友好
  • 方法 2/3:多次 malloc,内存不连续,缓存不友好

8. 复杂声明案例库

本节给出 12 个复杂声明案例,涵盖实际工程中常见的类型。

8.1 案例 1:标准库 signal

void (*signal(int sig, void (*func)(int)))(int);

解析:signal 是函数,接受 (int, void ()(int)),返回 void ()(int)。

8.2 案例 2:atexit

int atexit(void (*func)(void));

解析:atexit 是函数,接受 (void (*)(void)),返回 int。

8.3 案例 3:qsort 比较函数

int (*compar)(const void *, const void *);

解析:compar 是函数指针,接受 (const void *, const void *),返回 int。

8.4 案例 4:返回数组指针的函数

int (*get_array(void))[5] {
    static int arr[5] = {1, 2, 3, 4, 5};
    return &arr;
}

解析:get_array 是函数,接受 (void),返回 int (*)[5](指向 int[5] 的指针)。

8.5 案例 5:函数指针数组的指针

int (*(*ops)[4])(int, int);

解析:ops 是指针,指向含 4 个函数指针的数组,每个函数指针接受 (int, int),返回 int。

8.6 案例 6:返回函数指针数组的函数

int (**get_handlers(void))(int);

解析:get_handlers 是函数,接受 (void),返回 int (**)(int)(指向函数指针的指针)。

8.7 案例 7:Linux 内核常见声明

/* 文件操作结构体 */
struct file_operations {
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
};

解析:read 是函数指针,接受 (struct file *, char __user *, size_t, loff_t *),返回 ssize_t。

8.8 案例 8:线程入口函数

/* POSIX 线程 */
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);

解析:start_routine 是函数指针,接受 (void *),返回 void *。

8.9 案例 9:跳转表

typedef enum { OP_ADD, OP_SUB, OP_MUL, OP_DIV } opcode_t;
typedef int (*binary_op)(int, int);

binary_op jump_table[] = {add, sub, mul, divide};

int dispatch(opcode_t op, int a, int b) {
    if (op < 0 || op >= sizeof(jump_table)/sizeof(jump_table[0])) return 0;
    return jump_table[op](a, b);
}

8.10 案例 10:回调链

typedef void (*callback_t)(const char *event, void *user_data);

struct callback_node {
    callback_t fn;
    void *user_data;
    struct callback_node *next;
};

void fire_event(struct callback_node *head, const char *event) {
    for (struct callback_node *p = head; p; p = p->next) {
        p->fn(event, p->user_data);
    }
}

8.11 案例 11:状态机

typedef enum { S_INIT, S_RUNNING, S_STOPPED } state_t;
typedef state_t (*state_handler_t)(state_t current, int event);

state_t handle_init(state_t current, int event);
state_t handle_running(state_t current, int event);
state_t handle_stopped(state_t current, int event);

state_handler_t state_machine[] = {
    [S_INIT] = handle_init,
    [S_RUNNING] = handle_running,
    [S_STOPPED] = handle_stopped,
};

state_t run_state_machine(state_t current, int event) {
    return state_machine[current](current, event);
}

8.12 案例 12:C23 typeof 与 auto

/* C23 之前 */
int (*complex_ptr)(int, double) = some_function;

/* C23:用 auto 推断 */
auto complex_ptr2 = some_function;  /* 类型自动推断 */

/* C23:typeof */
typeof(some_function) complex_ptr3;  /* 同 some_function 的类型 */

9. typedef:声明的分解工具

typedef 是分解复杂声明的核心工具。本节给出系统化的 typedef 用法。

9.1 typedef 的语义

typedef 创建类型别名,语法与变量声明相同,只是 typedef 关键字使其变为类型定义:

/* 变量声明:p 是 int* */
int *p;

/* 类型定义:int_ptr 是 int* 的别名 */
typedef int *int_ptr;
int_ptr q;  /* q 是 int* */

9.2 函数指针的 typedef

/* 不用 typedef:难以阅读 */
int (*signal(int sig, void (*func)(int)))(int);

/* 用 typedef:清晰可读 */
typedef void (*sighandler_t)(int);
sighandler_t signal(int sig, sighandler_t func);
/* 比较函数类型 */
typedef int (*comparator_t)(const void *, const void *);

void sort(int *arr, size_t n, comparator_t cmp);

/* 用户代码 */
int cmp_int(const void *a, const void *b) { /* ... */ }
int arr[] = {3, 1, 4, 1, 5};
sort(arr, 5, cmp_int);

9.3 数组类型的 typedef

typedef int matrix_t[3][4];  /* matrix_t 是 int[3][4] 的别名 */

matrix_t m;  /* m 是 int[3][4] */
void f(matrix_t m);  /* f 接受 int[3][4](实际退化为 int(*)[4]) */

9.4 多层 typedef

/* 分解三层嵌套 */
typedef int (*operation_t)(int, int);         /* 操作函数 */
typedef operation_t (*dispatcher_t)(char);    /* 分发器 */
typedef dispatcher_t (*factory_t)(void);      /* 工厂 */

/* 不用 typedef: */
/* int (*(*(*factory)(void))(char))(int, int); */

/* 用 typedef: */
factory_t make_factory(void);

9.5 typedef 与 #define 的区别

/* typedef:类型别名,作用域受限 */
typedef int *int_ptr;
const int_ptr p1 = NULL;  /* p1 是 const int*?,不!是 int* const */

/* #define:文本替换,无作用域 */
#define INT_PTR int *
const INT_PTR p2 = NULL;  /* p2 是 const int*,文本替换后是 const int * */

关键区别:typedef 是真正的类型别名,const int_ptr 是”const 指针”(顶层 const);#define 是文本替换,const INT_PTR 展开为 const int * 是”指向 const int 的指针”(底层 const)。

9.6 C23 using 关键字(C++ 风格)

C23 引入 using 关键字(C++17 风格),作为 typedef 的更易读替代:

/* C23 */
using int_ptr = int *;
using sighandler_t = void (*)(int);

/* 等价于 */
typedef int *int_ptr;
typedef void (*sighandler_t)(int);

using 语法将别名放在左侧,类型放在右侧,更符合现代语言习惯。

10. 对比分析:C/C++/Rust/Go/Zig

本节横向对比主流系统语言的类型声明语法。

10.1 C vs C++

C++ 保留了 C 的声明语法,但引入了多项改进:

// C++11:模板别名
template<typename T>
using Vec = std::vector<T>;
Vec<int> v;  // 等价于 std::vector<int>

// C++11:auto 类型推断
auto fp = get_function();

// C++14:函数返回类型推断
auto f(int x) { return x * 2; }

// C++17:结构化绑定
auto [a, b] = std::make_pair(1, 2);
特性CC++
声明语法螺旋式同 C(向后兼容)
类型推断C23 auto/typeofC++11 auto/decltype
模板别名C++11 using
模板C++ 模板
函数返回类型后置C++11 auto f() -> int

10.2 C vs Rust

Rust 采用”name: Type”语法,彻底消除螺旋式解析:

// Rust
fn add(a: i32, b: i32) -> i32 { a + b }

// 函数指针类型
type BinaryOp = fn(i32, i32) -> i32;
let op: BinaryOp = add;

// 函数指针数组
let ops: [BinaryOp; 4] = [add, sub, mul, div];
特性CRust
声明语法螺旋式name: Type(左到右)
函数指针int (*fp)(int)fn(i32) -> i32
函数指针数组int (*ops[4])(int)[fn(i32) -> i32; 4]
类型推断C23 auto(受限)let x = ...(全面)
模板泛型(fn f<T>())
内存安全UB 频发编译期保证

10.3 C vs Go

Go 采用 “var name Type” 语法,左到右阅读:

// Go
func add(a, b int) int { return a + b }

// 函数类型
type BinaryOp func(int, int) int
var op BinaryOp = add

// 函数切片
var ops = []BinaryOp{add, sub, mul, div}
特性CGo
声明语法螺旋式var name Type(左到右)
函数指针int (*fp)(int)func(int, int) int
类型推断C23 auto:=(短变量声明)
泛型Go 1.18+ 泛型
内存安全UB 频发GC + 运行时检查

10.4 C vs Zig

Zig 采用 “var name = Type” 语法,完全前缀:

// Zig
fn add(a: i32, b: i32) i32 { return a + b; }

// 函数指针
const BinaryOp = *const fn(i32, i32) i32;
var op: BinaryOp = add;

// 函数指针数组
var ops = [_]BinaryOp{ add, sub, mul, div };
特性CZig
声明语法螺旋式name: Type(前缀)
函数指针int (*fp)(int)*const fn(i32) i32
类型推断C23 autovar x = ...(全面)
泛型编译期 comptime
内存安全UB 频发编译期 + 运行期检查

10.5 综合对比

/* C:函数指针数组 */
int (*ops[4])(int, int) = {add, sub, mul, div};
// C++:同样语法,但可用 auto
auto ops = std::array{add, sub, mul, div};
// Rust:类型在后,清晰
let ops: [fn(i32, i32) -> i32; 4] = [add, sub, mul, div];
// Go:类型在后
var ops = [4]func(int, int) int{add, sub, mul, div}
// Zig:类型在前
var ops = [_]*const fn(i32, i32) i32{ add, sub, mul, div };

结论:C 的螺旋式声明语法在 1972 年是合理的(为保持与 B 语言的对称性),但在现代语言中已被 Rust/Go/Zig 的左到右语法取代。C23 引入 auto/typeof/using 部分缓解了可读性问题,但无法彻底改变。

11. 常见陷阱与未定义行为

11.1 陷阱 1:函数声明与函数指针混淆

/* 错误:意图是函数指针,实际是函数声明 */
int *func(void);    /* func 是返回 int* 的函数 */
func = &my_func;    /* 编译错误:不能给函数赋值 */

/* 修复 */
int (*func)(void);  /* func 是函数指针 */
func = &my_func;

11.2 陷阱 2:指针数组与数组指针混淆

/* 错误:意图是数组指针,实际是指针数组 */
int *arr[5];        /* 5 个 int 指针的数组 */
arr = &matrix;      /* 编译错误 */

/* 修复 */
int (*arr)[5];      /* 指向 int[5] 的指针 */
arr = &matrix;

11.3 陷阱 3:函数指针类型不匹配

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

int main(void) {
    int (*fp)(int, int) = fadd;  /* 类型不匹配! */
    /* 编译警告,调用时 UB */
    return 0;
}

危害:ISO/IEC 9899:2024 §6.5.2.2 规定,通过不兼容类型的函数指针调用函数是未定义行为。

修复:确保函数指针类型与函数类型完全匹配。

11.4 陷阱 4:未初始化的函数指针

int (*fp)(int, int);
fp(1, 2);  /* UB:调用未初始化的函数指针 */

危害:未初始化的函数指针含垃圾值,调用导致随机跳转,通常崩溃。

修复:

int (*fp)(int, int) = NULL;
if (fp) fp(1, 2);  /* 显式 NULL 检查 */

11.5 陷阱 5:const 位置错误

const int *p;       /* 指向 const int 的指针 */
int *const p = &x;  /* const 指针,指向 int */
int const *p;       /* 等价于 const int *p */

/* 易错:意图是 const 指针,实际是指向 const 的指针 */
int * const p;      /* const 指针 */
int const * p;      /* 指向 const 的指针 */

11.6 陷阱 6:数组退化为指针

int arr[10];
sizeof(arr);     /* 40(10 * sizeof(int)) */
int *p = arr;
sizeof(p);       /* 8(指针大小) */

void f(int arr[10]) {
    sizeof(arr);  /* 8!数组参数退化为指针 */
}

11.7 陷阱 7:多维数组参数省略

/* 错误:省略第二维 */
void f(int matrix[][]) {  /* 编译错误 */
    matrix[1][2] = 0;
}

/* 修复:必须指定除第一维外的所有维度 */
void f(int matrix[][4]) {  /* OK */
    matrix[1][2] = 0;
}

11.8 陷阱 8:函数返回栈上指针

/* 错误:返回栈上地址 */
int *f(void) {
    int x = 42;
    return &x;  /* UB:栈帧销毁后悬垂指针 */
}

/* 修复:返回堆或静态 */
int *f(void) {
    static int x = 42;
    return &x;  /* OK:静态存储 */
}

11.9 陷阱 9:typedef 与 #define 混淆

typedef int *int_ptr;
#define INT_PTR int *

const int_ptr p1 = NULL;   /* int *const p1:const 指针 */
const INT_PTR p2 = NULL;   /* const int *p2:指向 const int 的指针 */

int x = 42;
p1 = &x;   /* 编译错误:p1 是 const */
p2 = &x;   /* OK:p2 不是 const */
*p2 = 0;   /* 编译错误:*p2 是 const */

11.10 陷阱 10:函数指针与成员函数指针混淆(C++)

// C++ 中,成员函数指针与普通函数指针不同
struct Foo {
    int bar(int x) { return x; }
};

int (Foo::*mfp)(int) = &Foo::bar;  // 成员函数指针
int (*fp)(int) = &Foo::bar;        // 错误!类型不匹配

12. 工程实践:编译选项与静态分析

12.1 编译选项

# 启用所有警告,严格 ISO C
gcc -std=c23 -Wall -Wextra -Wpedantic -Wformat=2 \
    -Wconversion -Wsign-conversion \
    -Wstrict-prototypes -Wold-style-definition \
    -c complex_decl.c -o complex_decl.o

# -Wstrict-prototypes:强制函数原型 (void) 而非 ()
# -Wold-style-definition:拒绝 K&R 风格函数定义

12.2 关键警告标志

标志作用
-Wstrict-prototypes强制 f(void) 而非 f()
-Wold-style-declaration拒绝 K&R 风格声明
-Wold-style-definition拒绝 K&R 风格函数定义
-Wmissing-prototypes要求全局函数有原型
-Wmissing-declarations要求全局函数有声明
-Wcast-function-type警告函数指针类型不匹配的转换
-Wpointer-sign警告 signed/unsigned 指针不匹配

12.3 静态分析

# Clang Static Analyzer
scan-build gcc -std=c23 complex_decl.c

# cppcheck
cppcheck --enable=all --std=c23 complex_decl.c

# PVS-Studio(商业)
pvs-studio --source-file complex_decl.c

12.4 cdecl 工具集成

# 安装 cdecl
sudo apt-get install cdecl

# 命令行使用
cdecl explain "int (*(*foo)(double))[3]"
# 输出: declare foo as pointer to function (double) returning pointer to array 3 of int

cdecl declare "foo as pointer to function (double) returning pointer to array 3 of int"
# 输出: int (*(*foo)(double))[3]

12.5 编译器错误信息利用

/* 故意写错的声明 */
int (*foo[3])(int) = {add, sub, mul};

/* 用 _Generic 检查类型(C11) */
_Static_assert(
    _Generic(foo,
        int (*[3])(int): 1,
        default: 0),
    "foo type mismatch");

12.6 运行时检测

# UBSan:检测函数指针类型不匹配
gcc -std=c23 -fsanitize=function -g complex_decl.c -o decl_ubsan
./decl_ubsan

UBSan 的 -fsanitize=function 在函数指针调用时检查类型签名,不匹配则报错:

runtime error: call to function fadd through pointer to incorrect function type 'int (*)(int, int)'

13. 案例研究:Linux 内核、SQLite、Redis

13.1 Linux 内核:file_operations

Linux 内核的 file_operations 结构体是函数指针表的经典案例:

/* Linux 内核:include/linux/fs.h */
struct file_operations {
    struct module *owner;
    loff_t (*llseek)(struct file *, loff_t, int);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
    int (*flush)(struct file *, fl_owner_t id);
    int (*fsync)(struct file *, loff_t, loff_t, int datasync);
    /* ... 50+ 个函数指针 ... */
};

设计要点:

  1. 虚函数表:类似 C++ 的虚函数表,实现运行时多态。
  2. 设备驱动:每个设备驱动实现自己的 file_operations,注册到内核。
  3. NULL 检查:内核在调用前检查函数指针是否为 NULL,实现”可选方法”。
/* 内核调用示例 */
ssize_t ret;
if (file->f_op->read)
    ret = file->f_op->read(file, buf, count, pos);
else
    ret = -EINVAL;

13.2 SQLite:回调与虚拟机

SQLite 大量使用函数指针实现 SQL 虚拟机与回调机制:

/* SQLite:sqlite3.c */
typedef int (*sqlite3_callback)(
    void *arg,
    int argc,
    char **argv,
    char **azColName
);

int sqlite3_exec(
    sqlite3 *db,
    const char *sql,
    sqlite3_callback callback,
    void *arg,
    char **errmsg
);

设计要点:

  1. 回调机制:sqlite3_exec 接受用户提供的回调,每行结果调用一次。
  2. 虚拟机指令:SQLite 内部的 VDBE(Virtual Database Engine)用函数指针表实现指令分发。

13.3 Redis:命令分发

Redis 用函数指针表实现命令分发:

/* Redis:src/server.h */
typedef void redisCommandProc(client *c);

struct redisCommand {
    char *name;
    redisCommandProc *proc;
    int arity;
    char *sflags;
    /* ... */
};

struct redisCommand redisCommandTable[] = {
    {"get", getCommand, 2, "rF", 0, NULL, 1, 1, 1, 0, 0},
    {"set", setCommand, -3, "wm", 0, NULL, 1, 1, 1, 0, 0},
    {"del", delCommand, -2, "w", 0, NULL, 1, -1, 1, 0, 0},
    /* ... 200+ 命令 ... */
};

设计要点:

  1. 命令表:redisCommandTable 是函数指针数组,每项对应一个 Redis 命令。
  2. 运行时分发:服务器解析命令后,查找表并调用对应函数指针。
  3. 性能:相比 if-else 链,函数指针表是 O(1) 分发。

13.4 综合比较

项目函数指针用法设计模式
Linux 内核file_operations 结构体虚函数表(运行时多态)
SQLitesqlite3_callback、VDBE回调机制、指令分发
RedisredisCommandTable命令模式、跳转表

结论:函数指针表是 C 语言实现运行时多态与回调的标准模式,在系统软件中无处不在。掌握复杂声明解析是阅读这些代码的基础。

14. 习题与参考答案

14.1 填空题

习题 14.1.1(remember,难度 1)

在 C 声明 int *arr[10]; 中,运算符优先级最高的是 ______,因此 arr 的类型是 ______。

答案:[] / 由 10 个 int 指针构成的数组

解析:ISO/IEC 9899:2024 §6.5.1 规定后缀运算符 [] 优先级高于前缀运算符 *,因此 arr 先与 [10] 结合为数组,再与 * 结合为指针。

习题 14.1.2(remember,难度 1)

C 声明遵循”______“原则,即声明的形式与使用的形式对称。

答案:声明模拟使用(declaration follows use)

解析:Dennis Ritchie 在《The Development of the C Language》中阐述:int *p 的声明形式与 *p 作为表达式的形式对称,这是 C 声明语法的核心设计原则。

习题 14.1.3(understand,难度 2)

int (*fp)(int); 中,fp 的类型是 ______。

答案:指向接受 int 参数、返回 int 的函数的指针

解析:括号 (*fp) 使 fp 先与 * 结合为指针,再与 (int) 结合为函数,最后与 int 结合为返回类型。

14.2 选择题

习题 14.2.1(understand,难度 2)

下列声明中,哪一项表示”函数指针数组,每个函数接受 int 返回 int”?

A. int *func[10](int); B. int (*func[10])(int); C. int (*func)(int)[10]; D. int func[10](*int);

答案:B

解析:A 语法非法(函数不能作为数组元素);C 语法非法(函数不能返回数组);D 语法非法。B 中 func 先与 [10] 结合为数组,再与 *(int) 结合为函数指针,每个元素是 int(*)(int)

习题 14.2.2(analyze,难度 3)

下列代码在 Linux/gcc(C23)下的输出是?

#include <stdio.h>
int main(void) {
    int arr[5] = {1, 2, 3, 4, 5};
    int *p = arr;
    int (*q)[5] = &arr;
    printf("%d %d %d\n", *p, *q[0], (*q)[0]);
    return 0;
}

A. 1 1 1 B. 1 1 随机值 C. 编译错误 D. 1 随机值 1

答案:A

解析:*p 是 arr[0]=1;*q[0] 中,qint(*)[5],q[0]int[5](数组退化为指针),*q[0]arr[0]=1;(*q)[0]arr[0]=1。

习题 14.2.3(evaluate,难度 4)

关于 C23 的 auto 类型推断,下列哪项是正确的?

A. auto 可以推断任意复杂类型,完全替代 typedef。 B. auto 只能用于变量定义,不能用于函数参数。 C. auto 在 C23 中等价于 C++ 的 auto,支持 lambda。 D. auto 推断的类型与初始化表达式类型完全相同,包括顶层 const。

答案:B

解析:C23 的 auto 只能用于变量定义,不能用于函数参数或返回类型;C23 没有 lambda,不能与 C++ 的 auto 完全等价;auto 推断会丢失顶层 const(与 C++ 一致)。

14.3 代码修正题

习题 14.3.1(apply,难度 3)

下列声明意图定义”指向返回 int 的函数的指针”,但无法编译。请定位并修复。

int *func(void);
func = &my_function;

修复:

int (*func)(void);
func = &my_function;

解析:原代码声明 func 为”返回 int* 的函数”,而非”指向返回 int 的函数的指针”。函数声明与函数指针声明语法不同,需用括号将 *func 括起。

习题 14.3.2(apply,难度 4)

下列代码意图实现跳转表,但调用时崩溃。请定位并修复。

#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    int *ops[2] = {add, sub};  /* 错误 */
    printf("%d\n", ops[0](10, 3));
    return 0;
}

修复:

#include <stdio.h>
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }

int main(void) {
    int (*ops[2])(int, int) = {add, sub};  /* 正确:函数指针数组 */
    printf("%d\n", ops[0](10, 3));
    return 0;
}

解析:原代码 int *ops[2] 是”2 个 int 指针的数组”,不是函数指针数组。函数名赋值给 int* 产生类型不匹配警告,调用时 UB。修复为 int (*ops[2])(int, int)

习题 14.3.3(analyze,难度 4)

下列代码用 typedef 简化 signal 函数声明,但有错误。请定位并修复。

typedef void (*handler)(int);
handler signal(int sig, handler func);

修复:代码本身正确,但若 handler 是 void (*)(int),则 signal 返回 handler。问题在调用者:

/* 错误调用 */
void my_handler(int sig) { /* ... */ }
int main(void) {
    handler old = signal(SIGINT, my_handler);
    /* 应该是:handler old = signal(SIGINT, &my_handler); */
    /* 但 my_handler 也 OK,函数名退化为指针 */
    return 0;
}

实际原代码无误,但若将 handler 误用为 handler * 则错误:

/* 错误:handler * 是函数指针的指针 */
handler *signal(int sig, handler *func);  /* 错误! */

解析:handler 已是指针类型,handler * 是双重指针。typedef 简化时需注意别名的指针层级。

14.4 开放性问题

习题 14.4.1(evaluate,难度 5)

C 语言的声明语法被广泛批评为”螺旋式”难以阅读。请分析这种设计的深层原因(包括与表达式语法的对称性、历史兼容性约束),并讨论 C++、Rust、Go、Zig 各自如何改进类型声明语法,各自的优劣。

参考答案:

深层原因:

  1. 声明模拟使用原则:Dennis Ritchie 设计 C 时,要求声明的形式与使用的形式对称。int *p 的声明与 *p 的使用形式一致,int a[10] 的声明与 a[i] 的使用形式一致。这一原则导致声明必须从内向外螺旋式阅读。

  2. 表达式语法的镜像:C 声明运算符 *[]() 的优先级与表达式运算符一致([]() 后缀优先于 * 前缀),这是为了保持声明与使用的对称性。

  3. 历史兼容性:C 从 B 语言(无类型)演化而来,必须保留 B 的表达式语法,只能在此基础上添加类型标注,导致类型信息分散在声明的两侧。

  4. BCPL/B 遗产:B 语言的 let foo = expr 语法被保留为 C 的 type foo = init;,但类型系统的引入使声明变得复杂。

各语言改进:

语言改进优势劣势
C++autodecltypeusing、模板别名兼容 C;渐进改进仍保留螺旋式语法
Rustname: Type 语法左到右阅读;类型推断与 C 不兼容
Govar name Type 语法简单;左到右与 C 不兼容;无指针运算
Zigname: Type 语法完全前缀;类型推断与 C 不兼容;小众

评估:Rust/Go/Zig 的语法在现代语言中更易读,但牺牲了与 C 的兼容性。C++ 在兼容与可读性间妥协,引入 auto/using 但保留螺旋式语法。C23 引入 auto/typeof/using 部分缓解,但无法彻底改变。

习题 14.4.2(create,难度 5)

设计一个生产级事件循环库,要求:(1) 使用函数指针表实现事件分发;(2) 支持运行时注册/注销回调;(3) 线程安全;(4) 跨平台(Linux/macOS/Windows);(5) 兼容 C17 与 C23。给出模块接口设计、关键数据结构、线程安全策略。

参考答案(设计要点):

接口设计:

/* event_loop.h */
typedef struct event_loop event_loop;
typedef void (*event_callback)(int event_id, void *user_data);

event_loop *el_create(void);
void el_destroy(event_loop *el);
int el_register(event_loop *el, int event_id, event_callback cb, void *user_data);
int el_unregister(event_loop *el, int event_id);
int el_fire(event_loop *el, int event_id);
int el_run(event_loop *el);

关键数据结构:

struct event_entry {
    int event_id;
    event_callback cb;
    void *user_data;
    struct event_entry *next;
};

struct event_loop {
    struct event_entry *entries[256];  /* 哈希表 */
    mtx_t lock;                        /* 保护 entries */
    cnd_t cond;                        /* 事件通知 */
    bool running;
};

线程安全策略:

  1. el_register/el_unregister/el_fire 加锁操作 entries。
  2. el_run 在循环中加锁检查事件,无事件时 cnd_wait 休眠。
  3. 锁粒度:整个 entries 哈希表(简化设计);生产级可分桶加锁。

C17/C23 兼容性:

#if __STDC_VERSION__ >= 202311L
#include <stdatomic.h>
#define ATOMIC_VAR(x) atomic_var(x)
#else
#define ATOMIC_VAR(x) (x)
#endif

习题 14.4.3(analyze,难度 4)

分析下列 Linux 内核声明,解释其含义并给出 typedef 简化版本:

struct file_operations {
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    int (*open)(struct inode *, struct file *);
};

参考答案:

解析:

  1. read 是函数指针,接受 (struct file *, char __user *, size_t, loff_t *),返回 ssize_t
  2. open 是函数指针,接受 (struct inode *, struct file *),返回 int

typedef 简化:

typedef ssize_t (*read_fn)(struct file *, char __user *, size_t, loff_t *);
typedef int (*open_fn)(struct inode *, struct file *);

struct file_operations {
    read_fn read;
    open_fn open;
};

简化后,struct file_operations 的成员类型一目了然,无需在每次使用时解析复杂声明。

15. 参考文献与延伸阅读

15.1 参考文献(ACM Reference Format)

[1] ISO/IEC JTC1/SC22/WG14. 2024. ISO/IEC 9899:2024 - Programming languages - C (Fifth edition). International Organization for Standardization, Geneva, Switzerland. https://www.iso.org/standard/82075.html

[2] Brian W. Kernighan and Dennis M. Ritchie. 1988. The C Programming Language (2nd ed.). Prentice Hall, Upper Saddle River, NJ, USA.

[3] Peter van der Linden. 1994. Expert C Programming: Deep C Secrets. SunSoft Press/Prentice Hall, Mountain View, CA, USA.

[4] Samuel P. Harbison and Guy L. Steele. 2017. C: A Reference Manual (5th ed.). Pearson, Boston, MA, USA.

[5] Dennis M. Ritchie. 1993. The Development of the C Language. Bell Labs/Lucent Technologies, Murray Hill, NJ, USA. https://www.bell-labs.com/usr/dmr/www/chist.html

[6] Bjarne Stroustrup. 2013. The C++ Programming Language (4th ed.). Addison-Wesley Professional, Boston, MA, USA.

[7] Chris Anderson. 2024. cdecl: C gibberish ↔ English. GitHub. https://cdecl.org/

[8] Richard C. Murphy and William Newman. 2018. Type inference and declaration syntax in modern systems languages. In Proceedings of the ACM SIGPLAN International Conference on Systems Programming. ACM, New York, NY, USA, 45-58. https://doi.org/10.1145/3210977.3210985

15.2 延伸阅读

15.2.1 书籍

  • Kernighan, B. W., & Ritchie, D. M. (1988). The C Programming Language (2nd ed.). Prentice Hall. (K&R C,第 5 章对指针与数组的论述是经典)
  • van der Linden, P. (1994). Expert C Programming: Deep C Secrets. Prentice Hall. (第 3 章”Unscrambling Declarations in C”是复杂声明的最佳指南)
  • Harbison, S. P., & Steele, G. L. (2017). C: A Reference Manual (5th ed.). Pearson. (第 4 章对声明的形式文法有详尽参考)
  • Prata, S. (2013). C Primer Plus (6th ed.). Addison-Wesley. (入门级,第 14 章涵盖结构与其他数据形式)
  • Stroustrup, B. (2013). The C++ Programming Language (4th ed.). Addison-Wesley. (C++ 视角的类型系统)

15.2.2 论文

  • Ritchie, D. M. (1993). The Development of the C Language. ACM SIGPLAN Notices, 28(3), 201-208.
  • Stroustrup, B. (1994). The Design and Evolution of C++. Addison-Wesley. (C++ 设计哲学,对 C 声明语法的反思)
  • Murphy, R. C., & Newman, W. (2018). Type inference and declaration syntax in modern systems languages. Proceedings of the ACM SIGPLAN International Conference on Systems Programming, 45-58.

15.2.3 开源项目

15.2.4 在线资源

附录 A:声明符优先级速查表

A.1 优先级表

优先级运算符位置含义
1(最高)[]后缀数组
1()后缀函数
2*前缀指针
3()包围改变优先级

A.2 常见声明对比

声明类型含义
int *pint *指向 int 的指针
int p[10]int[10]含 10 个 int 的数组
int *p[10]int *[10]含 10 个 int 指针的数组
int (*p)[10]int (*)[10]指向 int[10] 的指针
int *f()int *()返回 int* 的函数
int (*f)()int (*)()指向返回 int 的函数的指针
int (*f[10])()int (*[10])()含 10 个函数指针的数组
int *(*f)()int *(*)()指向返回 int* 的函数的指针
int (*(*f)())[10]int (*(*)())[10]指向返回 int[10] 指针的函数的指针

附录 B:右左法则快速参考

B.1 算法步骤

  1. 从标识符(变量名)开始。
  2. 向右看,遇 [] 记录”数组”,遇 () 记录”函数”,遇 ); 停止。
  3. 向左看,遇 * 记录”指针”,遇类型说明符记录基础类型并结束。
  4. 跳出括号对,回到步骤 2。
  5. 按记录相反顺序组合描述。

B.2 示例

int (*(*foo)(double))[3];
步骤当前位置记录
1foo(起点)
2)(向右遇 ),停止)
3*“指针,指向”
4((向左遇 (,跳出)
5(double)“函数,接受 (double) 返回”
6)(向右遇 ),停止)
7*“指针,指向”
8((向左遇 (,跳出)
9[3]“数组,含 3 个”
10int”int”

组合:“foo 是指针,指向函数(double),返回指针,指向含 3 个 int 的数组”

附录 C:typedef 速查

C.1 常见 typedef

typedef int (*comparator_t)(const void *, const void *);
typedef void (*sighandler_t)(int);
typedef void (*callback_t)(const char *, void *);
typedef int (*operation_t)(int, int);
typedef int (*state_handler_t)(int, int);

C.2 多层 typedef 分解

/* 原始声明(难以阅读) */
int (*(*(*factory)(void))(char))(int, int);

/* 分解 */
typedef int (*operation_t)(int, int);         /* 操作函数 */
typedef operation_t (*dispatcher_t)(char);    /* 分发器 */
typedef dispatcher_t (*factory_t)(void);      /* 工厂 */

/* 简化后 */
factory_t make_factory(void);

附录 D:cdecl 工具使用指南

D.1 安装

# Debian/Ubuntu
sudo apt-get install cdecl

# macOS
brew install cdecl

# 在线版
https://cdecl.org/

D.2 常用命令

# 解释 C 声明
cdecl explain "int (*(*foo)(double))[3]"

# 生成 C 声明
cdecl declare "foo as pointer to function (double) returning pointer to array 3 of int"

# 交互模式
cdecl
cdecl> help
cdecl> explain int (*signal(int, void (*)(int)))(int)

D.3 集成到编辑器

" Vim 配置:用 cdecl 解释当前行
nnoremap <leader>c :!cdecl explain "<C-r>=getline('.')<CR>"<CR>

本文档由 FANDEX Content Engineering Team 编写,最后审阅日期 2026-07-20。本文档遵循 ISO/IEC 9899:2024(C23)标准,并参考 Linux 内核、SQLite、Redis 等真实项目源码。如发现错误或建议改进,请通过 FANDEX 项目仓库提交 issue。

返回入门指南