为什么尽管C有严格的别名规则,但允许引用具有相似第一成员的结构



首先,如果这看起来是重复的,我很抱歉,但我在其他地方找不到确切的问题

我阅读了N1570,特别是§6.5¶7,其中写道:

对象的存储值只能由具有以下类型之一的左值表达式访问:
-与对象的有效类型兼容的类型,
--与对象的无效类型兼容的一个类型的限定版本,
-与对象有效类型对应的有符号或无符号类型,
--一种类型,是与对象有效类型的限定版本相对应的有符号或无符号类型,
--在其成员中包括上述类型之一的聚合或并集类型(递归地包括子聚合或包含并集的成员),或
-字符类型。

这让我想起了我在(类似BSD的)套接字编程中看到的一个常见习惯用法,尤其是在connect()调用中。虽然connect()的第二个参数是struct sockaddr *,但我经常看到传递给它的是struct sockaddr_in *,这似乎很有效,因为它们共享相似的初始元素。我的问题是:

这种情况适用于上述规则中详细说明的哪种偶然性,为什么,或者现在是未定义的行为是以前标准的产物

此行为不是由C标准定义的。

行为是由单一Unix规范和/或与您正在使用的软件相关的其他文档定义的,尽管部分是隐含的。

"对象的存储值只能由…访问"的说法具有误导性。C标准不能强迫你做任何事情;您没有义务遵守其"应"的要求。就C标准而言,不遵守其要求的唯一后果是C标准没有定义行为。这并不禁止其他文档定义该行为。

netinet/in.h文档中,我们看到">sockaddr_In结构用于存储Internet协议系列的地址。此类型的值必须强制转换为structsockaddr,以便与此文档中定义的套接字接口一起使用。"因此,文档"不仅告诉"我们应该,而且必须将sockaddr_in转换为sockaddr。事实上,我们必须这样做意味着软件支持它,它将发挥作用。(请注意,这里的措辞不精确;我们实际上并没有将sockaddr_in强制转换为sockaddr,而是实际转换指针,导致内存中的sockaddr_in对象被视为sockaddr。)

因此,有一个隐含的承诺,即为Unix实现提供的操作系统、库和开发工具都支持这一点。

这是C语言的扩展:如果行为不是由C标准定义的,其他文档可能会提供定义,并允许您编写不能单独使用C标准编写的软件。C标准所说的未定义的行为不是被禁止的行为,而是可以由其他规范填充的空白。

关于常见初始序列的规则可以追溯到1974年。关于";"严格混叠";只能追溯到1989年。后者的意图并不是说它们胜过其他一切,而只是允许编译器执行客户会觉得有用的优化,而不会被认为是不合规的。本标准明确指出,在本标准的一部分和/或实施文件将描述某些行动的行为,但本标准的另一部分将其描述为未定义行为的情况下,实施可以选择优先考虑第一部分,并且《基本原理》明确指出,作者认为";市场";将比委员会更好地决定何时实施

在对N1570 6.5p7约束的足够迂腐的解读下,几乎所有的程序都违反了它们,但在某些方面,除非实现足够迟钝,否则这无关紧要。本标准没有试图列出一种类型的对象可能被另一种类型左值访问的所有情况,而是编译器必须允许一种类型对象被另一个看起来不相关的左值访问。给定代码序列:

int x;
int *p[10];
p[2] = &someStruct.intMember;
...
*p[2] = 23;
x = someStruct.intMember;

在6.5p7中没有规则的情况下,除非编译器跟踪p[2]的来源,否则它没有理由识别someStruct.member的读取可能针对的是刚刚使用*p[2]写入的存储。另一方面,给定代码:

int x;
int *p[10];
...
someStruct.intMember = 12;
p[2] = &someStruct.intMember;
x = *p[2];

在这里,实际上没有规则允许该成员类型的左值访问与结构相关联的存储,但除非编译器故意视而不见,否则它将能够看到在第一次分配给someStruct.intMember之后,该成员的地址被占用,并且应该:

  1. 如果可以的话,说明将使用结果指针执行的所有操作,或者
  2. 不要假设在使用结构类型的上一个和下一个操作之间不会访问结构的存储

我认为,编写后来被重新编号为N1570 6.5p7的规则的人从未想过,他们会被解释为不允许使用公共初始序列规则的公共模式。如前所述,大多数程序都违反了6.5p7的约束,但这样做的方式会被任何不迟钝的编译器预测性地处理;那些使用通用初始序列担保的人就属于这一类。由于本标准的作者认识到";符合";只能有意义地处理一个人为的无用程序的编译器,一个迟钝的编译器可能会滥用";混叠规则";没有被视为缺陷。

最新更新