取消引用void**-casted type**是否会打破严格的别名?

取消引用void**-casted type**是否会打破严格的别名?,c,undefined-behavior,strict-aliasing,C,Undefined Behavior,Strict Aliasing,考虑这个人为的例子: #include <stddef.h> static inline void nullify(void **ptr) { *ptr = NULL; } int main() { int i; int *p = &i; nullify((void **) &p); return 0; } gcc警告严格别名: $ gcc -c -Wstrict-aliasing -fstrict-aliasing a.

考虑这个人为的例子:

#include <stddef.h>

static inline void nullify(void **ptr) {
    *ptr = NULL;
}

int main() {
    int i;
    int *p = &i;
    nullify((void **) &p);
    return 0;
}
gcc
警告严格别名:

$ gcc -c -Wstrict-aliasing -fstrict-aliasing a.c
a.c: In function ‘f’:
a.c:3:7: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
      *((float **) &p) = NULL;
      ~^~~~~~~~~~~~~
但是,对于无效**,它不会发出警告:

#include <stddef.h>
void f(int *p) {
    *((void **) &p) = NULL;
}
#包括
空f(int*p){
*((无效**)和p)=无效;
}
那么,严格的别名规则是否有效


如果不是,如何编写函数使不违反严格别名规则的任何指针(例如)为空?

一般不要求实现对不同指针类型使用相同的表示形式。在将使用不同表示法的平台上,例如
int*
char*
,将无法支持可交替作用于
int*
char*
的单指针类型
void*
。尽管可以互换处理指针的实现有助于在使用兼容表示的平台上进行低级编程,但并非所有平台都支持这种能力。因此,标准的作者没有理由强制要求支持这种特性,而不是将其视为一个实施质量问题

据我所知,像icc这样适合低级编程的高质量编译器,以及所有指针具有相同表示形式的目标平台,在构造上不会有任何困难,例如:

void resizeOrFail(void **p, size_t newsize)
{
  void *newAddr = realloc(*p, newsize);
  if (!newAddr) fatal_error("Failure to resize");
  *p = newAddr;
}

anyType *thing;

... code chunk #1 that uses thing
   resizeOrFail((void**)&thing, someDesiredSize);
... code chunk #2 that uses thing
请注意,在本例中,获取对象地址的行为和所有使用结果指针的行为都明显地发生在使用
对象的两个代码块之间。因此,不存在实际的别名,任何不是故意盲的编译器都会很容易识别将
thing
的地址传递到
reallocorFail
的行为可能会导致
thing
被修改

另一方面,如果用法类似于:

void **myptr;    
anyType *thing;

myptr = &thing;
... code chunk #1 that uses thing
*myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
myptr = &thing;
... code chunk #1 that uses thing
*(void *volatile*)myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
那么,即使是高质量的编译器也可能没有意识到
thing
可能会在使用它的两个代码块之间受到影响,因为在这两个代码块之间没有对
anyType*
类型的任何引用。在此类编译器上,有必要编写如下代码:

void **myptr;    
anyType *thing;

myptr = &thing;
... code chunk #1 that uses thing
*myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
myptr = &thing;
... code chunk #1 that uses thing
*(void *volatile*)myptr = realloc(*myptr, newSize);
... code chunk #2 that uses thing
让编译器知道
*mtptr
上的操作正在做一些“奇怪”的事情。用于低级编程的高质量编译器会将此视为一种迹象,表明他们应该避免在此类操作中缓存
thing
的值,但是,即使是
volatile
限定符也不足以用于优化模式中的gcc和clang等实现,这些优化模式只适用于不涉及低级编程的目的

如果像
reallocOrFail
这样的函数需要使用不适合低级编程的编译器模式,则可以将其编写为:

void resizeOrFail(void **p, size_t newsize)
{
  void *newAddr;
  memcpy(&newAddr, p, sizeof newAddr);
  newAddr = realloc(newAddr, newsize);
  if (!newAddr) fatal_error("Failure to resize");
  memcpy(p, &newAddr, sizeof newAddr);
}
然而,这需要编译器考虑到
resizeOrFail
可能会改变任何类型的任意对象的值,而不仅仅是数据指针,从而不必要地损害应该有用的优化。更糟糕的是,如果所讨论的指针恰好存储在堆上(并且不是类型
void*
),那么不适合低级编程的一致性编译器仍然可以假设第二个
memcpy
不可能影响它


低级编程的一个关键部分是确保选择适合于该目的的实现和模式,并知道何时可能需要
volatile
限定符来帮助他们。一些编译器供应商可能会声称,任何要求编译器适合其用途的代码都是“坏代码”,但试图安抚这些供应商将导致代码的效率低于使用适合其用途的优质编译器所能产生的效率。

此处的一些指针:,gcc如果代码需要超出gcc/clang的作者想要支持的语义,那么确保生成支持这种语义的机器代码的唯一可靠方法就是使用
-fno严格的别名。事实上,以某种方式调整代码将允许它在没有警告的情况下编译,这与生成的代码是否实际工作无关。更糟糕的是,gcc和clang有时会优化掉他们认为无法更改存储区域中位的存储模式的代码,即使它会更改该存储的有效类型。谢谢您非常详细的回答!IIUC,如果我不针对特定的编译器,但希望实现“标准兼容”,则没有解决方案(即使使用memcpy())?@rom1v:要实现“标准兼容”,需要以一种不可能被描述为复制为字符数组的方式执行复制。标准没有明确说明这意味着什么,但如果gcc认识到一段代码的行为方式与memcpy相当,那么同样的语义最终也适用。IIRC,给定
uint16_t*src,*dest
,gcc有时会优化
int temp1=((char*)src)[0]+1;int temp2=((char*)src)1]+1;((char*)dest)[0]=temp1-1;((char*)dest)=temp2-1,进入
*dest=*src
,即使它们不是等价物。@rom1v:只有当且仅当明确它们是低质量实现的让步,并且任何坚持“正确”的实现者以这种方式行事就是承认他们缺乏生成高质量实现的技能。