为什么转换不同的指针类型会导致 TBAA(基于类型的别名分析)违规?
Why does casting different pointer types cause TBAA (Type-Based Alias Analysis) violation?
我正在读这个 blog,然后我发现了一些代码
我真的不明白。为什么这是错误的代码?
float *P;
void zero_array() {
int i;
for (i = 0; i < 10000; ++i)
P[i] = 0.0f;
}
int main() {
P = (float*)&P; // cast causes TBAA violation in zero_array.
zero_array();
}
希望有人能解释一下。
如博客中所述,这:
for (i = 0; i < 10000; ++i)
P[i] = 0.0f;
}
可以优化成这样:
memset(P, 0, 40000);
这是因为 P
应该指向一个 float
的数组,其中一个浮点数是 4 个字节(在这个例子中)。
但是如果你这样做:
P = (float*)&P;
那么P
实际上指向了一个指针数组指向float
。如果 float *
的大小为 8 个字节,优化将失败。
这是一个更具体的例子:
int main() {
int i;
P = malloc(10000 * sizeof(float));
zero_array(); // this properly sets an array of 10000 floats to 0.
free(P);
float **PP = malloc(10000 * sizeof(float *));
P = (float *)PP;
zero_array(); // if sizeof(float *) == 8, the first 5000 pointers will be NULL,
// and the next 5000 will contain garbage.
free(PP);
}
以下代码:
float *P;
P = (float*)&P;
P[0] = 0.0f;
违反了严格的别名规则。
对象 P
具有 有效类型 float *
,因为这是其声明的类型。 C11 6.5/6:
The effective type of an object for an access to its stored value is the declared type of the object, if any.
第二行执行后,表达式P[0]
表示与P
相同的内存位置。 (注意:为了解释的目的,假设 sizeof(float) == sizeof(float *)
。显然,如果这些尺寸不同,那么情况会更糟!)
执行 P[0] = 0.0f
使用类型 float
的左值来访问类型 float *
的对象。这违反了 6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types
在这些引用中,"access" 表示读或写。 "following types" 的列表不包括任何涵盖使用 float
表达式读取 float *
的异常
这个例子有点难以理解,因为它是自我参照的。然而,它与这个更简单的例子的原理完全相同:
float *Q;
float *P = &Q;
*P = 0.0f;
在这种情况下 Q
具有类型 float *
,但它是通过类型 float
.
的左值写入的
此示例演示了该规则的基本原理。假设不存在严格的别名规则,并且允许所有别名。然后评估 P[0] = 0.0f
更改 P
。在一个常见的实现中,这会导致 P
现在成为一个空指针,但我们可以很容易地想象一些其他的值被赋值,这使得 P
成为指向其他变量的有效指针。
在那种情况下,P[1] = 0.0f
行必须 成功写入另一个变量。因此,编译器将无法用 memset
替换循环;这样的 memset 会更新 P[1]
的原始位置,但不会在 P[0] = ....;
执行后更新 P[1]
的新位置。不允许优化改变程序的可观察行为。
严格别名规则的存在意味着编译器可以执行优化,例如将此循环更改为 memset,而不必担心循环的内容可能会即时更改指针。
注意。 P = (float *)&P
行也可能是对齐冲突。对齐规则和严格别名规则是完全独立的规则,不应相互混淆。 (一些写得不好的页面试图 'explain' 严格别名作为某种对齐要求)。
我正在读这个 blog,然后我发现了一些代码 我真的不明白。为什么这是错误的代码?
float *P;
void zero_array() {
int i;
for (i = 0; i < 10000; ++i)
P[i] = 0.0f;
}
int main() {
P = (float*)&P; // cast causes TBAA violation in zero_array.
zero_array();
}
希望有人能解释一下。
如博客中所述,这:
for (i = 0; i < 10000; ++i)
P[i] = 0.0f;
}
可以优化成这样:
memset(P, 0, 40000);
这是因为 P
应该指向一个 float
的数组,其中一个浮点数是 4 个字节(在这个例子中)。
但是如果你这样做:
P = (float*)&P;
那么P
实际上指向了一个指针数组指向float
。如果 float *
的大小为 8 个字节,优化将失败。
这是一个更具体的例子:
int main() {
int i;
P = malloc(10000 * sizeof(float));
zero_array(); // this properly sets an array of 10000 floats to 0.
free(P);
float **PP = malloc(10000 * sizeof(float *));
P = (float *)PP;
zero_array(); // if sizeof(float *) == 8, the first 5000 pointers will be NULL,
// and the next 5000 will contain garbage.
free(PP);
}
以下代码:
float *P;
P = (float*)&P;
P[0] = 0.0f;
违反了严格的别名规则。
对象 P
具有 有效类型 float *
,因为这是其声明的类型。 C11 6.5/6:
The effective type of an object for an access to its stored value is the declared type of the object, if any.
第二行执行后,表达式P[0]
表示与P
相同的内存位置。 (注意:为了解释的目的,假设 sizeof(float) == sizeof(float *)
。显然,如果这些尺寸不同,那么情况会更糟!)
执行 P[0] = 0.0f
使用类型 float
的左值来访问类型 float *
的对象。这违反了 6.5/7:
An object shall have its stored value accessed only by an lvalue expression that has one of the following types
在这些引用中,"access" 表示读或写。 "following types" 的列表不包括任何涵盖使用 float
表达式读取 float *
这个例子有点难以理解,因为它是自我参照的。然而,它与这个更简单的例子的原理完全相同:
float *Q;
float *P = &Q;
*P = 0.0f;
在这种情况下 Q
具有类型 float *
,但它是通过类型 float
.
此示例演示了该规则的基本原理。假设不存在严格的别名规则,并且允许所有别名。然后评估 P[0] = 0.0f
更改 P
。在一个常见的实现中,这会导致 P
现在成为一个空指针,但我们可以很容易地想象一些其他的值被赋值,这使得 P
成为指向其他变量的有效指针。
在那种情况下,P[1] = 0.0f
行必须 成功写入另一个变量。因此,编译器将无法用 memset
替换循环;这样的 memset 会更新 P[1]
的原始位置,但不会在 P[0] = ....;
执行后更新 P[1]
的新位置。不允许优化改变程序的可观察行为。
严格别名规则的存在意味着编译器可以执行优化,例如将此循环更改为 memset,而不必担心循环的内容可能会即时更改指针。
注意。 P = (float *)&P
行也可能是对齐冲突。对齐规则和严格别名规则是完全独立的规则,不应相互混淆。 (一些写得不好的页面试图 'explain' 严格别名作为某种对齐要求)。