【C语法硬核20讲】12 C99新特性:VLA与字面量

目标:把 变长数组(VLA, variable length array) 与 复合字面量(compound literal) 用法、限制与替代方案讲清;顺带梳理 C99 的几项“改变写法”的特性:指定初始化器、// 注释、for 内声明、restrict、stdbool.h。


1)VLA:运行期决定维度的栈数组(可选特性)

void foo(size_t n){
    int a[n];      // n 运行期决定
    /* ... */
}

要点

  • 生存期:自动存储期(出栈即销毁)。
  • C11/C17 把 VLA 变为可选特性;MSVC 不支持,部分嵌入式禁用。
  • 大小过大可能栈溢出;不适合不受控的外部输入。

函数形参中的 VLA(携带维度)

void add_mm(size_t r, size_t c, int A[r][c], const int B[r][c]){
    for(size_t i=0;i<r;++i) for(size_t j=0;j<c;++j) A[i][j]+=B[i][j];
}

优点:形参天然知道“行宽”;缺点:可移植性差、栈限制。

跨平台替代

  • 连续堆块:int (*A)[c] = malloc(sizeof *A * r);(见第 02/09 讲)
  • 或固定上界:#define MAXC 128,形参 int (*A)[MAXC]。

2)复合字面量(compound literal):一行造临时对象

#include <stdio.h>

void print_pair(const int p[2]){
    printf("%d,%d\n", p[0], p[1]);
}

print_pair((int[2]){3, 4});        // 栈上临时对象
struct S { int x,y; };
struct S s = (struct S){ .x=1, .y=2 };  // 直接初始化结构体

用途

  • 传数组/结构体临时值
  • 便捷构造返回值或参数
  • 配合指定初始化器写表驱动更清晰

生存期

  • 块作用域:临时对象活到完整表达式结束。
  • 文件作用域:是静态存储期对象。

3)指定初始化器(designated initializer)

int a[10] = { [0]=1, [3]=7 };          // 其他元素自动为 0
struct S t = { .y=2, .x=1 };           // 顺序可调,更不易出错

优势:读写清晰、避免错位;表驱动配置时极其好用。


4)for 内声明与 // 注释

for (int i=0; i<10; ++i) { /* ... */ } // 作用域更小
// 单行注释(C99 起)

5)restrict:指针别名承诺(可大幅优化)

void axpy(size_t n, float a, float * restrict y, const float * restrict x){
    for (size_t i=0;i<n;++i) y[i] += a*x[i];
}

语义:被 restrict 修饰的指针在其有效生存期不与其他指针别名同一对象
作用:让编译器放心做矢量化等优化。
前提:你必须保证承诺为真;否则是未定义行为


6)stdbool.h 与布尔类型

#include <stdbool.h>
bool ok = true; if (!ok) return false;

可读性与接口语义更清晰。


7)组合示例:表驱动 + 复合字面量 + 指定初始化器

struct Op { const char *name; int (*fn)(int,int); };
int add(int a,int b){ return a+b; }
int mul(int a,int b){ return a*b; }

static struct Op ops[] = {
    [0] = { .name="add", .fn=add },
    [1] = (struct Op){ .name="mul", .fn=mul },
};

int apply(int which,int a,int b){
    if ((unsigned)which >= sizeof ops/sizeof ops[0]) return 0;
    return ops[which].fn(a,b);
}

8)自检清单

  • VLA 仅在受控尺寸可移植性可接受时使用
  • 更通用:连续堆块替代二维 VLA
  • 复合字面量用来构造临时结构/数组
  • 指定初始化器让表定义更稳
  • restrict 只在你能保证无别名时使用
  • stdbool.h 提升接口可读性

9)迷你练习

1)把矩阵加法的 VLA 版本改成“连续堆块”版本。
2)用复合字面量一行构造 struct sockaddr_in(提示:用指定初始化器)。
3)为一个 SAX 风格解析器写一张表:用指定初始化器填 state × event → handler。

原文链接:,转发请注明来源!