我见过很多以下代码(抽象示例):
char* byteBlockPtr;
long* alignedPtr = NULL;
/* ... */
/* aligning pointer by long boundary */
while (!ALIGNED(byteBlockPtr))
{
byteBlockPtr++;
}
alignedPtr = (long*)byteBlockPtr;
/* ... */
/* do stuff with memory */
alignedPtr++; /* go to next block */
/* ... */
这是可以理解的,因为从char指针转换为更严格的指针类型(在这种情况下指向long的指针)要求对齐是相同的。
这同样适用于void指针吗?
是否有必须遵循的一般规则,以便在例如编写自己的memset时,不要破坏指针的对齐?
如果有关于char和void指针以及其他指针的指针别名和对齐之间的连接是什么?例如,如果按照标准将void指针隐式转换为任何其他指针类型,这是否意味着保证也满足对齐要求?
P.S。提前抱歉超过1个问题,但显然我的知识存在差距,我不知道如何缩小范围。
答案 0 :(得分:5)
如果使用除标准中列出的特定类型之外的任何类型的指针修改任何类型的对象,C标准允许obtuse实现执行他们想要的任何操作,无论编译器是否有理由期望对象正在被修改。指针是否正确对齐并不重要。根据基本原理,规则存在,以便给定代码如:
float f;
void hey(int *p)
{
f=1.0f;
*p=6;
f+=1.0f;
}
编译器不必悲观地假设p
可能保留f
的地址,因此在指针赋值之前写f
并在之后读取它。在这样的情况下,编译器没有理由期望写入p
会影响f
,因此没有理由期望冗余存储和加载可以用于任何目的。
虽然没有证据表明标准的作者认为编译器编写者应该如此迟钝以至于忽略了别名明显的情况,但是一些编译器编写者,包括与gcc有关的编写者,解释了缺乏授权作为一种迹象表明他们应该忽略明显的混淆,这样做会有助于提高效率和效率。代码,不考虑相关代码是否真正有用。
在任何定义检查指针是否针对给定类型进行了适当对齐的方法的平台上,将指针转换为char *,将其递增,除非或直到它被适当对齐,然后将其转换为其他类型将产生指向其他类型的指针。不幸的是,虽然C11定义了一种确保一种类型的对象以满足另一种类型的对齐要求的方式定位的标准方法,但它没有定义一种标准方法,通过该方法,代码可以利用这种对齐而不会遇到别名问题
如果代码只需要在非钝的编译器上运行,我建议从一种类型转换为另一种类型并且作为后一种类型进行访问应该是可靠的,前提是使用新类型的操作是使用已转换的指针完成的< em>从旧类型到使用旧类型的最后一次访问后的新类型,并且使用转换指针的所有操作都是在使用旧类型进行下一次访问之前完成的。大多数代码使用&#34;分块优化&#34;适合这种模式,它是编译器支持的一种简单模式,无需做出过于悲观的假设(如果代码从T1 *类型转换为T2 *然后写入它,假设这样的操作可能会影响T1类型的对象可能是悲观的,但在大多数情况下它也将正确)。
不幸的是,因为标准尚未强制编译器识别别名,即使在显而易见的情况下,并且gcc的作者在没有授权的情况下对此类识别没有兴趣,也没有办法安全地在gcc中使用分块优化,而无需使用非标准的gcc特定扩展或使用-fno-strict-aliasing
标志。在使用该标志时获得良好性能需要学习使用restrict
限定符,但使用分块来加速热循环并使用restrict
来最小化-fno-strict-aliasing
的性能影响似乎更好方法比使用慢速非分块循环。另请注意,gcc通常会处理使用带有或不带有标志的分块优化的代码,但gcc的作者会考虑在没有标记的情况下编译此类代码时的任何正确行为。&#34;意外&#34;并且不厌恶&#34;修复&#34; [即在没有警告的情况下打破这样的代码。
void flip_quad16s(uint16_t *p, int num_quads)
{
uint64_t *pp = (uint64_t*)p;
union {
uint64_t dw;
uint16_t hw[4];
} u;
for (int i=0; i<num_quads; i++)
{
memcpy(u.hw, pp, 8);
u.dw = ~u.dw;
/* Note that if p actually identifies something which has no declared
type but will be used as uint16_t, we must make sure that memcpy
uses that as a source type */
memcpy(pp++, u.hw, 8);
}
}
当然,这将要求编译器假定p可能是别名 任何类型的任何东西,甚至可能阻止完美的优化编译器 从实现结果和非钝的编译器可能有的结果 使用uint16_t的代码实现,将其转换为uint64_t,然后 与之合作,例如
void flip_quad16s(uint16_t *p, int num_quads)
{
uint64_t *pp = (uint64_t*)p;
for (int i=0; i<num_quads; i++)
pp[i] = ~pp[i];
}
理智的编译器应该更容易将后一个函数转换为 最佳代码将反转一堆uint16_t值,而不是任何编译器 同样使用前一个功能,特别是如果它在一个内部被调用 循环使用其他类型,因为使用memcpy会强制使用 编译器确认所有类型的潜在别名,而不仅仅是 uint16_t和uint64_t。