C严格别名是否使无类型静态内存池变得不可能?
WG14 成员 Jens Gustedt 在一篇关于严格别名规则的博文中说:
字符数组不能被重新解释为其他类型的对象。
事实上,这是真的吗?(我猜标准中的相应语言是说如果一个对象具有声明的类型,那么该类型就是它的有效类型。)如果是这样,这是否意味着分配器从静态声明的内存区域中分配内存是不可实现的标准C?
我知道 TeX 忽略了 Pascal 的大部分类型系统,并且由于类似的问题将所有内容都视为单词数组,但我希望如果我在 ( malloc-less) C 中发现自己处于类似的情况,我可以声明一个最大对齐char数组并继续以通常的方式使用结构。_Alignas除了作为表达非标准要求的标准化设备(类似于volatile)之外,我也看不出在这样的世界中可能有什么意义。
回答
C 标准的第 6.5p7 节中指定了别名规则:
对象的存储值只能由具有以下类型之一的左值表达式访问:88)
- 与对象的有效类型兼容的类型,
- 与对象的有效类型兼容的类型的限定版本,
- 一种类型,它是与对象的有效类型相对应的有符号或无符号类型,
- 一种类型,它是与对象有效类型的限定版本相对应的有符号或无符号类型,
- 在其成员中包含上述类型之一的聚合或联合类型(递归地包括子聚合或包含联合的成员),或
- 一种字符类型。
- 此列表的目的是指定对象可以或不可以别名的情况
请注意,此列表允许通过 a 访问任何对象char *,但反过来则不允许,即声明为一个或多个字符的数组的对象不能作为其他类型的左值访问。
这也意味着malloc不能以符合标准的方式实现,因为没有它就无法创建没有有效类型的内存。然而malloc被认为是实现的一部分,因此可以利用它的实现内部知识来返回一个指向兼容程序可以使用的内存块的指针。
- @mevets,“现在”是什么意思?撇开它对系统编程的后果的问题不谈,严格的别名规则一直在 C 中使用,自 1989 年最初的 ANSI 标准化以来,它的形式几乎相同。
- 事实上,严格别名规则的残留形式可以追溯到 K&R (1978) 的第一版,它观察到“您还应该注意声明中的暗示,**a 指针被约束为指向一个特定类型的对象**”(第 5.1 节;添加了重点)。将“约束为”与“解释为”进行对比。
- 这对标准机构来说是一个相当大的成就。现在可用的系统语言少了一种。
- @mevets 我不会把它称为恋物癖 - 别名是具有不受限制的指针的语言的优化器的祸害,这可能就是为什么老派 Fortran(没有它们)在多年来的 Benchmarks Game(在 Game 抛弃所有实现之前,除了每种语言一个,这是我停止跟踪它的时候)。别名也是使 C(不是 C++)`const` 在没有 `restrict` 的情况下无用的原因。只是 C 的弱类型系统对此无能为力,优化器有十年的悬而未决的果实要先完成。
- 我认为这是对它的一种非常无端的解释。它更有可能表明`struct stat *s = ..; s->st_size = s->tm_year;` 不再像早期版本那样被允许。您正确地指出标准机构对别名的迷恋很早就开始了。
回答
“字符数组不能被重新解释为其他类型的对象”这句话是不准确的。正确的说法是,如果将字符数组重新解释为另一种类型的对象(C 2018 6.5 7 允许的除外),则 C 标准不会定义该行为。
与往常一样,如果我们想要完成一项任务,而 C 标准没有定义我们想要的行为,我们可以通过其他方式来定义我们想要的行为。
如果是这样,这是否意味着从静态声明的内存区域中分配内存的分配器在标准 C 中无法实现?
这种分配器在严格符合C 的情况下是无法实现的,C 是不依赖于未指定、未定义或实现定义的行为(并且不超过任何最小实现限制)的 C 代码。完全有可能用符合C 语言编写这样的分配器,也就是带有扩展的 C。很简单,可以将内存分配例程放在一个源文件中,并使用支持将内存别名为不同类型的开关编译它们。(这是一个扩展,比如 GCC 的-fno-strict-aliasingswitch.) 那么,在用普通编译器编译其他源文件时,编译器对内存分配源文件中内存的有效类型是视而不见的,因此不会受到内存分配例程使用字符数组的影响。(这是另一个扩展,尽管这种行为隐含地源于我们对编译器和链接器的设计方式的理解。)