生长中技术笔记
C 语言的复合字面量
背景
在 C 语言中,结构体和数组属于复合类型。传统写法要求先声明一个变量,再逐个赋值或整体初始化。当对象只在一个表达式中使用、之后不再需要时,单独声明变量就显得多余。C99 引入了复合字面量(Compound Literal),允许在表达式中直接构造一个匿名的复合对象,省去中间变量。
语法
text
(type){initializer}type:结构体、联合体、数组等复合类型名。initializer:初始化列表,支持位置初始化,也支持 C99 的指定初始化器(designated initializer)。
注意外层括号不能省略,它用于区分复合字面量和普通花括号表达式(如复合语句)。
示例一:结构体初始化
text
struct Point {
int x;
int y;
};
struct Point p = (struct Point){10, 20}; // 直接创建匿名Point对象这里 (struct Point){10, 20} 在赋值表达式中直接构造了一个匿名的 struct Point 对象,按成员顺序依次填入 x = 10、y = 20,然后整体拷贝给 p。效果等价于先声明一个临时变量再赋值,但少了一行声明。
示例二:函数传参
text
这段代码展示了两种调用方式:
- 先声明
struct argv a并用指定初始化器(.a = "chenhongyu")赋值,再传参。 - 直接在函数调用处写
(struct argv){"chenhongyu", 24},构造匿名对象后按值传入func。
因为 func 的参数是值传递,函数内部拿到的是副本,复合字面量在所在块结束前一直存在,但调用完成后代码中不再引用它,不存在悬空问题。如果参数是指针传递(struct argv *),就需要格外注意生命周期,见下文。
生命周期与作用域
这是使用复合字面量时最容易踩坑的地方:
- 块作用域内(函数体、
if/for等复合语句中):复合字面量具有自动存储期(automatic storage duration),对象在所在块结束时销毁。如果取它的地址并在块外使用,属于未定义行为。 - 文件作用域(全局位置):复合字面量具有静态存储期(static storage duration),生命周期与程序一致。
原文提到"通常要立即复制给其他变量使用,否则会被销毁",更准确的说法是:不要假设块外的代码还能访问该对象。按值传参或立即赋值给同作用域内的变量是安全的;取地址跨块使用则不安全。
与普通字面量的对比
| 对比项 | 普通字面量 | 复合字面量 |
|---|---|---|
| 类型 | 标量类型(int、float 等)及字符串字面量 | 复合类型(结构体、数组、联合体) |
| 是否匿名 | 是(如 42、3.14) | 是(如 (struct Point){1, 2}) |
| 能否直接构造 | 能 | 能(C99 起) |
| 典型用途 | 常量、简单赋值 | 临时结构体/数组的构造与传参 |
注意事项
- 编译器支持:复合字面量是 C99 特性。GCC、Clang 在 C99 及以上模式下默认支持;MSVC 在 C 模式下对 C99/C11 特性的支持因版本而异,较新版本可通过
/std:c11启用完整 C11 支持,建议查阅所用版本的文档确认。C++ 也支持复合字面量(作为 C 兼容特性),但生命周期规则不同(绑定到完整表达式结束而非所在块),跨语言阅读代码时不要混淆。 - 指定初始化器:示例二中
.a = "chenhongyu"的写法同样是 C99 引入的,与复合字面量常搭配使用,但二者是独立特性。 - 数组复合字面量:
(int[]){1, 2, 3}可以构造匿名数组,常用于sizeof计算或作为函数参数(退化为指针时需注意生命周期)。 - 不要对块作用域内的对象取地址后跨块使用:
上面的代码仅为说明风险,实际项目中应避免这种写法。c
struct Point *pp; { struct Point tmp = (struct Point){1, 2}; pp = &tmp; // 块结束后 tmp 销毁,pp 悬空 } // pp 不可用
小结
复合字面量让 C 语言在"一次性构造复合对象"的场景下少写一行声明,代码意图更直接。它不改变 C 的内存模型,生命周期规则与局部变量一致,使用时只需记住一点:对象活到所在块结束,不要越界引用。对于按值传参、立即赋值等短生命周期场景,它是简洁且安全的写法。
<!-- csdn-article-id: 134290617 -->本文最初于 2023/11/8 发布在 CSDN。
评论
正在读取评论…