考虑这个C代码:
extern void CheckIfPtrInHeap( void* p );
void TakePtr( void** p, size_t n )
{
for( size_t i = 0 ; i < n ; ++i )
CheckIfPtrInHeap( p[ i ] );
}
typedef size_t val_t[ 6 ];
extern void* stack_top;
void Func()
{
val_t val;
TakePtr( (void**) &val, ( (size_t) stack_top - (size_t) &val ) / sizof( size_t ) );
}
用gcc 4.8,mingw 4.7,cl 18.00编译好。这可能是有道理的,因为仅从它的外观来看,我说这里没有违规:是的,有2个指针指向同一个变量,但是第二个的值立即被转换为size_t需要一个临时的,所以指针本身不能在函数TakePtr中使用。
Q1:我的假设是否正确?如果是,这是否意味着确实没有违规,或者说编译器在技术上(即遵循标准的字母)没有发现任何违规(存在违规(仍然有两个指向相同变量的指针被取消引用)?
Q2:我在Linux上使用mingw交叉编译器版本4.2.1时偶然发现了这一点,它为TakePtr引用了warning: dereferencing type-punned pointer will break strict-aliasing rules
行。所以第二个问题实际上就像第一个问题,但换句话说:这个编译器是正确的,还是一个bug,或者更倾向于称它为编译器过于严格?
问题3:想要对所有上述编译器进行编译而不发出警告,代码中最正确的修复方法是什么(可能不仅仅适用于这种情况,而是针对更普遍的修正违反规则和无效指针的情况)用的)?似乎有多种方法可以使编译器静音:
union { val_t* r; void** ptr; } cast_ = { &val };
void** ptr = cast_.ptr;
或
void* p1 = (void*) &val;
void** ptr = &p1;
甚至
void** ptr = (void**) (void*) &val;
或者也许(告诉编译者嘿,我知道我在做什么&#39;,至少如果我正确地理解restrict
)
void* restrict p1 = (void*) &val;
void** ptr = &p1;
或
val_t* p1 = &val;
void** p2 = NULL;
memcpy( p2, p1, sizeof( p1 ) );
答案 0 :(得分:2)
严格的别名规则只是通过另一种类型的左值访问一种类型的对象是非法的(void*
和char*
之类的例外,以及其他显而易见的事情) 。不多也不少。
同时拥有指向同一对象的两个,三个或10000个指针,无论它们是否都是相同的类型,都与严格的别名规则无关。您需要取消引用其中一个才能发生违规行为。
这与restrict
关键字完全无关,关键字根本不涉及类型,只是简单地通知编译器指针是唯一的。
TakePtr
都是违规的,只是因为它访问了一个size_t
数组,好像它是一个void*
的数组。
工会不会得到帮助,因为分配到工会的一个字段然后访问另一个类型的字段也不合法。 完全在解除引用错误指针时发生的同样糟糕的事情,在这种情况下也会发生。
通过不同类型的左值访问值的唯一半正确方法是memcpy
。并非结果是自动定义良好。表示类型A的有效值的位模式可能不代表类型B的有效值。但如果不是这种情况,那么您可以记住它并保证提供正确的值。