资讯动态

为什么数组不能整体赋值:左值判定的底层逻辑

发布时间:2026/10/14 22:49:36 来源:尧图企业网站定制
先看四行声明和两行赋值。哪一行编译不过structBuf{intdata[3];};inta[3]{1,2,3};intb[3];structBufs1,s2;ba;// ①s2s1;// ②① 报错gcc 给出的原文是error: assignment to expression with array type② 通过s2得到s1的完整副本。同样是一整块内存同样 12 字节待遇天差地别。这篇把判定规则挖到底层最后顺手给你一个让数组复活整体赋值能力的正统手法。一、三个流行解释都不成立解释一数组太大复制开销高所以语言禁止。不成立int[3]才 12 字节比多数结构体小得多而几百字节的结构体赋值畅通无阻。解释二数组名是指针指针赋值会改指向危险。方向反了真正的指针变量恰恰可以随便赋值反而是最像指针的数组名不行。解释三编译器懒得实现。不成立结构体整体赋值从 C89 起就是标准语义四十多年了。三个都倒掉之后剩下的才是真相这个问题与大小无关、与性能无关它是类型系统的一条明文规则。要读懂这条规则先得认识一个名字。二、可修改左值赋值号左边资格审查标准对赋值的约束只有一句话赋值运算符的左操作数必须是可修改左值C11 §6.5.16。可修改左值的定义写在 C11 §6.3.2.1原文列了排除项第一条就是数组类型。为什么把数组排除在外回到赋值要做的事往一块已经存在的存储里写数据。所以左边必须指得着一处可写的内存。上一篇刚拆过形参现在轮到表达式一个表达式能不能被赋值取决于它代表一块内存还是只代表一个值。用四要素的语言说变量名、*p、a[i]、s.member、结构体变量这些表达式背后都有属于自己的一块存储写它们就是写那块存储而数组名在表达式里代表的是首元素的地址值篇 1 的退化规则地址值是个值和0x1000这个数字没有本质区别你没法把 12 字节塞进一个数值里。有个细节很有意思gcc 的报错原文assignment to expression with array type结尾落在 array type 两个词上。报错里保留了数组类型恰好证明左操作数b此刻没有退化成指针标准在值读取转换的例外清单里明确暂停了赋值号左操作数的转换它必须以数组的本来面目接受资格审查然后在可修改左值这一关被拦下。图b a;的编译器判定过程保持数组身份接受资格审查随后打回图左值判定一览左边一列有自己的一块存储可写右边一列只是值无处可写对照着看一遍判定表表达式可作左值原因int x;的 x可以变量有存储*p可以解引用后指向真实对象a[i]可以等价*(ai)指向具体元素s.member可以成员是对象的一部分struct变量可以表达式代表聚合对象本身语义为逐成员拷贝数组名a不行可修改左值定义明确排除数组类型a不行地址常量无自身存储a b、42不行表达式的结果值右值结构体之所以能整体赋值是因为结构体表达式不代表地址它就代表那个聚合对象本身赋值语义被定义为逐成员拷贝从 C89 沿用至今。数组在语言演化的另一条路上篇 1 讲过的表达式中按地址处理从未获得过这种作为整体对象的表达式身份。三、工程现场让数组搭便车的正统手法规则记住了活还是要干。数组复制有两条常规路径逐元素循环或者memcpy不知道元素类型布局时memcpy(b, a, sizeof b)是唯一安全解。想要更多把数组包进结构体typedefstruct{intdata[3];}Buf;Bufmake(void){Buf b{{1,2,3}};returnb;// 结构体按值返回数组搭上了便车}Buf xmake();// x.data 就是那个被返回的数组结构体能整体赋值、按值传参、按值返回数组包进去之后这三种能力全部继承。C 语言没有返回数组的语法这是公认的正统替代方案。Linux 内核里就有现成例子uuid_be的定义是typedef struct { __u8 b[16]; } uuid_be;16 字节的数组裹上结构体外壳整个 UUID 就能按值传递和赋值了。写代码前的自查口诀也随之出炉落笔之前问一句左边是一个能往里放东西的对象还是一个值前者放行后者编译器会替你拦下。这句话反过来还能解释一堆报错a x地址常量、hello p字面量、getchar() c函数返回值全是同一款审查。写在最后把这篇压成五条赋值要求左边是可修改左值C11 §6.3.2.1 的定义把数组类型明确排除在外。b a报错不是性能取舍是类型系统的明文规则。数组名在表达式里代表地址值退化规则篇 1地址值没有存储写不进 12 字节。门牌号不是房子门牌号上也贴不了春联。结构体能整体赋值因为它的表达式就代表聚合对象本身语义为逐成员拷贝C89 起标准化。数组复制的正解逐元素循环、memcpy想要赋值、传参、返回全套能力用结构体包装内核的uuid_be是现成范例。自查口诀左边必须是能往里放东西的对象。变量名、*p、a[i]、s.member、结构体变量都行数组名、a、常量、表达式结果都不行。下一篇换把趁手的武器qsort为什么只靠一个函数指针就能排任何类型把把行为当数据的回调机制从qsort拆到状态机表驱动函数指针不是炫技是 C 语言的解耦神器。同系列文章会陆续发布关注后即可收到更新。回复「交流」我很乐意分享我的学习经验和工作经验直接来聊。感谢阅读。转载请注明出处与作者。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑