https://kukuruku.co/post/i-do-not-know-c/
问题#7:
#include <stdio.h>
void f(int *i, long *l)
{
printf("1. v=%ld\n", *l); /* (1) */
*i = 11; /* (2) */
printf("2. v=%ld\n", *l); /* (3) */
}
int main()
{
long a = 10;
f((int *) &a, &a);
printf("3. v=%ld\n", a);
return 0;
}
小端系统上两个不同编译器的输出是:
1. v=10 2. v=11 3. v=11
1. v=10 2. v=10 3. v=11
第二个结果怎么可能?我通过引用严格别名来解释结果的解释并不完全。编译器是否完全忽略第(2)行?
答案 0 :(得分:6)
引用Wikipedia:
在C或C ++中,由严格别名规则指针强制执行 如果函数中的参数指向,则假定它们不是别名 根本不同的类型,除了char *和void *,可能 任何其他类型的别名。有些编译器允许严格的别名规则 要关闭,以便任何指针参数可以别名 指针参数。在这种情况下,编译器必须假设任何 通过这些指针访问可以别名。这可以防止一些 从优化中获得优化。
这是违反规则的地方:
f((int *) &a, &a);
^ aliasing to different type (a is 'long')
^ passing the same variable with different type
问题在于假设严格的别名规则,函数的第一个和第二个参数指向另一个位置,因为它们是不同类型的。这就是为什么作者解释:
因此,我们可以假设任何长期没有改变。
在此处:printf("2. v=%ld\n", *l);
取消引用long
值。
这就是为什么这部分(2)在两个编译器上都是未定义的行为。
答案 1 :(得分:3)
编译器不会忽略或忽略第(2)部分,它会被执行并且调用者范围中的a
的值以某种系统相关的方式被修改,但编译器可以假设内部函数{{ 1}},通过指针f()
修改int
不会修改i
指向的long
,因此它可以重用第一个{{1}读取的值}}作为第二个l
的参数。
第二个编译器似乎生成执行此操作的代码,而第一个编译器生成的代码重新读取printf()
指向的值。实际上,优化设置等编译器选项可以改变这种行为,这与C标准一致,后者将此代码描述为具有未定义的行为。
答案 2 :(得分:0)
第二个结果是因为*l
的值不能在(1)和(3)之间变化。 (严格的别名规则确保了。)
因此,程序只需要计算一次值,编译器就会生成相当于
的代码void f(int *i, long *l)
{
long l2 = *l;
printf("1. v=%ld\n", l2); /* (1) */
*i = 11; /* (2) */
printf("2. v=%ld\n", l2); /* (3) */
}