将数据作为副作用提供有效类型吗?

假设我有一大块动态分配的数据:

void* allocate (size_t n) { void* foo = malloc(n); ... return foo; } 

我希望将foo指向的数据用作特殊类型type_t 。 但是我想稍后这样做,而不是在分配期间。 为了给分配的数据一个有效的类型 ,我可以这样做:

 void* allocate (size_t n) { void* foo = malloc(n); (void) *(type_t*)foo; ... return foo } 

根据C11 6.5 / 6,这个左值访问应该是有效类型type_t

对于没有声明类型的对象的所有其他访问,对象的有效类型只是用于访问的左值的类型。

但是,行(void) *(type_t*)foo; 不包含任何副作用,因此编译器应该可以自由地优化它,我不希望它生成任何实际的机器代码。

我的问题是:上面的安全措施是什么? 是否将数据作为副作用提供有效类型? 或者通过优化代码,编译器是否也会优化掉有效类型的选择?

也就是说,使用上面的左值访问技巧,如果我现在调用上面这样的函数:

 int* i = allocate(sizeof(int)); *i = something; 

这是否会导致严格的别名违规UB,或者是现在的有效类型int

您明确引用的标准中的短语仅说明了对对象的访问 。 标准描述的对象的有效类型的唯一更改是之前的两个短语,它们清楚地描述了您必须使用要使其生效的类型存储到对象中。

6.5 / 6

如果通过具有非字符类型的类型的左值将值存储到没有声明类型的对象中,则左值的类型将成为该访问的对象的有效类型以及不修改该值的后续访问的有效类型储值。

标准中没有任何内容表明在操作具有其他副作用的情况下(例如更改存储在该对象中的位模式),只需要将写入对象的操作识别为设置有效类型。 。 另一方面,使用基于类型的积极优化的编译器似乎无法识别对象的有效类型的可能更改作为副作用,即使写入没有其他可观察的副作用,也必须保持这种副作用。

要了解有效类型规则的实际含义,我认为有必要了解它的来源。 据我所知,它似乎来自缺陷报告#028,更具体地说是用于certificate其中给出的结论的理由。 给出的结论是合理的,但给出的理由是荒谬的。

从本质上讲,基本前提涉及的可能性如下:

 void actOnTwoThings(T1 *p1, T2 *p2) { ... code that uses p1 and p2 } ... ...in some other function union {T1 v1; T2 v2; } u; actOnTwoThings(&u.v1, &u.v2); 

因为将联合编写为一种类型并将其作为另一种类型进行读取的行为会产生实现定义的行为,所以标准没有完全定义通过指针写入一个联合成员并读取另一个联合成员的行为,因此应该(通过DR的逻辑) #028)被视为未定义的行为。 虽然在上述许多场景中使用p1和p2访问相同的存储实际上应该被视为UB,但理由完全是错误的。 指定一个动作产生实现定义的行为与说它产生未定义的行为是非常不同的,特别是在标准会对实现定义的行为施加限制的情况下。

从联合的行为中导出指针类型规则的关键结果是,行为被完全且明确地定义,没有实现定义的方面,如果代码使用任何成员以任何顺序写入联合任意次数,然后读取最后一个成员写的。 虽然要求实现允许这将阻止一些其他有用的优化,但很明显,有效类型规则被编写为需要这样的行为。

基于联合行为的基本类型规则引起的更大问题是,如果新的比特模式与旧的比特模式匹配,则使用一种类型读取联合并使用另一种类型编写联合的动作不需要被视为具有任何副作用。 。 由于实现必须将新位模式定义为表示作为新类型写入的值,因此还必须将(相同的)旧位模式定义为表示相同的值。 给定函数(假设’long’和’long long’是相同的类型):

  long test(long *p1, long long *p2, void *p3) { if (*p1) { long long temp; *p2 = 1; temp = *(long long*)p3; *(long*)p3 = temp; } return *p1; } 

gcc和clang都会决定写入*(long*)p3不会产生任何影响,因为它只是存储了通过*(long long*)p3读取的相同位模式,如果如果通过*p2写入存储,则在读取*p1在实现定义的行为中处理,但如果该情况被视为UB则不是。 遗憾的是,由于标准与行为是实现定义还是未定义不一致,因此写入是否需要被视为副作用是不一致的。

从实际的角度来看,当不使用-fno-strict-aliasing ,gcc和clang应被视为处理C的方言,其中有效类型一旦设置就变为永久性。 他们不能可靠地识别所有可能改变有效类型的情况,并且处理这种情况所需的逻辑可以轻松有效地处理许多情况,gcc的作者长期声称这些情况在没有内脏优化的情况下无法处理。