c - 仅写入动态分配的存储 block 的一部分是否设置了整个 block 的有效类型?

标签 c strict-aliasing

编辑:C11 Standard - §6.5 Expressions (p6,7)中提到了我正在谈论的“有效类型”。 (感谢David C. Rankin在您的评论中提供此链接。)

阅读后,我不完全了解C中有关有效类型和严格别名的规则。我在以下代码中评论了我认为有效类型正在发生的事情。对于此示例,请假定int和float大小相同。

void *memory = malloc(sizeof(int) + sizeof(float));

int *x = memory;    // x points to an "object" with no effective type.
*x = 1;             // x points to an object with effective type int.
float *y = memory;  // y points to an object with effective type int.
++y;                // y points to an "object" with effective type ???

最后,y指向尚未写入的内存。因此,如果y指向没有有效类型的“对象”,这对我来说很有意义。

另一方面,已经在动态分配的“对象”中写入了一个int,因此该“对象”可能会解释为一个int数组。从这个角度来看,如果y指向有效类型为int的对象,这对我来说是有意义的。

考虑另一个示例:
void *memory = malloc(sizeof(short) + sizeof(float));

short *x = memory;  // x points to an "object" with no effective type.
*x = 1;             // x points to an object with effective type short.
++x;                // x points to an "object" with effective type ???

在这里,由于内存对齐,将x指向什么表示为浮点数似乎是不合理的。由于这样的对齐问题,我可以理解为什么写入部分内存块可能会设置整个内存块的有效类型。

如果这始终是正确的,那么据我了解,从技术上讲,分配一个巨大的内存块是不明确的行为,稍后在其任一端访问不同的数据类型。

这确实是导致我研究有效类型的核心问题。我一直在使用自己的内存竞技场,但是我无法弄清楚分配大块内存并将它们解释为连续打包的不同结构在技术上是还是是错误的。它在实践中一直有效。否则,什么是在动态分配的内存块中实现多于一种类型的存储的有效方法(除了将它们全部放入结构或联合中)?

最佳答案

想要了解C标准的任何人都应该阅读已发布的Rationale文档(例如,http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf,它是google的“C99 Rationale”搜索词)。

支持使C独特有用的各种有趣构造的能力一直是标准管辖范围之外的实现质量问题。相反,QoI问题打算留给市场。由于其客户需要某些构造的编译器编写者大概会寻求满足客户的需求,而无需考虑标准是否需要它,因此无需为某些程序需要但其他程序不需要的构造提供标准授权支持,也无需担心编写会明确解决所有极端情况的规则。

您要询问的案例是标准作者似乎没有考虑的众多案例之一。因此,最合理的解释是,尽管该标准不禁止实现不合理地处理此类构造的实施,但它并非旨在特别邀请此类行为,而高质量的实施则更关心构造是否有用而不是其是否有用。授权应该支持它。

有效类型规则基于对缺陷报告028的不良书面答复,该答复旨在解决编译器是否给出以下内容的问题:

float test(float *p1, unsigned *p2)
{
  *p1 = 1.0f;
  *p2 = 0;
  return *p1;
}

应该要求允许可能由类似的函数调用的可能性:
float test2(void)
{
  union { float f, unsigned u} uf;
  return test2(&uf.f, &uf.u);
}

该响应正确地表明,不需要编译器允许这种可能性,而是引用了荒谬的推理:因为将unsinged写入并集对象并读取int的行为是实现定义的行为,因此访问此类行为通过指针的对象是 undefined 行为。没有理由说使用指针不应产生与直接使用对象相同的实现定义的行为。这里的含义是,对于联合没有完全定义的行为的动作将调用UB。

实际上,对DR#028的正确响应应该说,没有使用成员类型的指针来访问联合(甚至结构)成员的一般权限,而是通过指针或左值进行的访问(可以识别为源自于)出于类型访问规则的目的,应将可能用于访问对象的其他类型之一视为通过原始类型的访问。编译器通常采用了最常见的模式,在这些模式下代码将派生并使用指针,但是这种支持的实际机制各不相同。因此,什么时候编译器应该容纳派生的左值这个问题留在了实施质量上。

“有效类型”规则尝试通过整理对DR#028的响应来“澄清”规则,而没有注意到在没有引用任何依据的情况下将实现定义的行为视为 undefined 行为,它完全无法考虑许多重要的极端情况。结果,虽然规则被写为“澄清”事物,但实际上却具有相反的效果。

从实际的角度来看,应将clang和gcc视为处理C的方言,它不允许曾经通过任何特定非字符类型访问的任何存储区域,也可以像其他任何类型一样可靠地访问,该标准将允许这种访问。相反,诸如icc之类的其他编译器会认识到,在可以看到一种指针或一种类型的左值用于形成另一种指针的情况下,对该指针的操作可能会影响原始对象,而无需考虑标准是否要求它们注意这些事情。如果在该块的生存期内没有通过一种以上的类型访问过malloc块中的特定存储部分,则即使clang和gcc也可能允许使用不同的类型来访问该块的不相交部分。但是,无论是clang还是gcc,都无法可靠地处理有时使用一种类型并有时使用另一种类型访问存储区域的情况,即使将曾经用于形成对象地址的唯一指针从旧类型转换为新型。

关于c - 仅写入动态分配的存储 block 的一部分是否设置了整个 block 的有效类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57320537/

相关文章:

c - 在c数组中通过引用传递

c - 我的缓冲区溢出漏洞仅打开常规用户 shell,而不打开 root shell

c - 了解限制关键字

c - glibc 的严格别名规则和 strlen 实现

c++ - 严格别名是否会阻止您通过不同类型写入 char 数组?

c - Head First C 中的代码后出现奇怪的结果

c - wdk 8.1 kmdf驱动蓝屏

c++ - 将 int 放入 char 数组中是否需要放置 new 合法?

rust - 如何使用(不安全的)别名?

c - 内存访问冲突 readdir