c - 是否有任何现代目标/编译器的示例在 C 中打破严格的别名会影响程序结果?
问题描述
有时有关堆栈溢出的问题和答案建议将指针强制转换为类型双关语的有效方式。它经常被声称这破坏了严格的别名并因此调用未定义的行为而被拒绝。
现代目标真的关心严格的混叠吗?在这种情况下,是否存在任何会显示意外行为的程序实例?
解决方案
是的,严格别名是一种非常真实的现象,现代编译器经常利用它来执行优化。
考虑以下代码 -
typedef struct
{
char a;
} my_struct;
void foo( int * a, my_struct * b, int count )
{
int i;
for (i=0;i<count;i++)
{
a[i] += b->a;
}
}
当使用clang 3.8.0-2
(这是一个非常现代的编译器)为 X64 目标(这也是一个非常现代的目标)编译时,使用以下命令 -
clang -m64 -S -O2 -std=c11 foo.c -fno-vectorize -fno-unroll-loops
生成以下程序集(简化并采用AT&T语法) -
foo:
testl %edx, %edx
jle .LBB0_3
movsbl (%rsi), %eax # Value loaded only once before start of loop
.align 16, 0x90
.LBB0_2:
addl %eax, (%rdi)
addq $4, %rdi
decl %edx
jne .LBB0_2
.LBB0_3:
retq
可以看到 from 的值b->a
在循环开始之前只加载一次,并添加到a
.
但是如果这个函数被称为 -
my_struct a[100];
// initializaion of values in a;
...
foo((int*)a, a, 2); // Breaking strict aliasing
现在很容易看出结果与预期不符。
在较低的优化级别或-fno-strict-aliasing
编译器在循环体中添加指令movsbl (%rsi), %eax
。
因此,可以公平地得出结论,具有现代编译器的现代架构确实利用了严格的别名,因此不能将指针强制转换用作类型双关语的一种方式。
参考:这个例子的灵感来自这篇博文
推荐阅读
- algorithm - push-relabel 算法中残差图的意义何在?
- javascript - 如何从对象数组创建新对象?
- wolfram-mathematica - 在 Mathematica 中使用 Manipulate 的问题
- performance - 如何确定Jetty中每个用户的线程数
- python - 求解同时的线性和非线性方程和不等式
- google-api - 是否有从 G-Suite Basics 中提取帐单/付款数据的 API?
- sicstus-prolog - 如何在 Windows 上安装 Berkeley DB for SICstus?
- google-sheets - Google表格 - 如何根据同一行中另一列的值计算一列
- python-3.x - 检查 TextChannel 的 id 并执行一些操作
- php - 无法加载动态库“pdo_mysql.so”,因为将 php 版本升级到 7.4