第§6.5.3.2节“地址和间接运算符”¶3表示(仅限相关部分):
一元&运算符返回其操作数的地址。 ... 如果操作数是一元
*
运算符的结果,则不会对该运算符和&
运算符进行求值,结果就好像两者都被省略,除了对运算符的约束仍然适用且结果不是左值。同样,如果操作数是[]
运算符的结果,则&
运算符和*
隐含的一元[]
都不会被计算,结果如下:如果&
运算符已删除且[]
运算符已更改为+
运算符。 ...
这意味着:
#define NUM 10
int tmp[NUM];
int *i = tmp;
printf("%ti\n", (ptrdiff_t) (&*i - i) );
printf("%ti\n", (ptrdiff_t) (&i[NUM] - i) );
完全合法,打印0和NUM
(10)。标准似乎非常清楚,这两种情况都需要进行优化。
但是,它似乎不需要优化以下内容:
struct { int a; short b; } tmp, *s = tmp;
printf("%ti\n", (ptrdiff_t) (&s->b - s) );
这似乎非常不一致。我没有理由认为上面的代码不应该打印sizeof(int)
加(不太可能)填充(可能是4)。
简化&->
表达式在概念上(IMHO)与&[]
是一个简单的地址加偏移量。它甚至是一个在编译时可以确定的偏移量,而不是[]
运算符可能的运行时间。
关于为什么这种看似不一致的原因有什么理由吗?
答案 0 :(得分:4)
在您的示例中,&i[10]
实际上不合法:它变为i + 10
,变为NULL + 10
,并且您无法对空指针执行算术运算。 (6.5.6 / 8列出了可以执行指针运算的条件)
无论如何,这个规则是在C99中添加的;它没有出现在C89中。我的理解是,它在很大程度上被添加到如下定义的代码中:
int* begin, * end;
int v[10];
begin = &v[0];
end = &v[10];
最后一行在C89(以及在C ++中)在技术上是无效的,但由于此规则在C99中是允许的。这是一个相对较小的变化,使一个常用的结构定义明确。
因为您无法对空指针执行算术运算,所以您的示例(&s->b
)无论如何都会无效。
至于为何存在这种“不一致”,我只能猜测。可能没有人想过让它保持一致,或者没有人看到一个引人注目的用例。这有可能被考虑并最终被拒绝。关于the Rationale &*
缩减的评论没有。您可以在the WG14 papers中找到一些明确的信息,但不幸的是,它们似乎组织得很差,因此通过它们拖网可能会很乏味。
答案 1 :(得分:2)
我认为该规则尚未添加用于优化目的(它带来了as-if规则不会带来什么?)但是允许&t[sizeof(t)/sizeof(*t)]
和&*(t+sizeof(t)/sizeof(*t))
未定义没有它的行为(直接写这些东西可能看起来很愚蠢,但添加一层或两层宏可能有意义)。我没有看到特殊套管和p-> m会带来这种好处的情况。请注意,正如James指出的那样,带有p空指针的&p[10]
仍然是未定义的行为;带有p空指针的&p->m
同样会保持无效(我必须承认,当p是空指针时,我没有看到任何用途。)
答案 2 :(得分:1)
我相信编译器可以选择以不同的方式打包,可能在结构的成员之间添加填充以提高内存访问速度。这意味着您无法确定b
将总是偏移4。单个值没有相同的问题。
此外,编译器可能在优化阶段不知道内存中结构的布局,从而阻止了有关结构成员访问和后续指针转换的任何类型的优化。
编辑:
我有另一种理论......
很多时候,编译器会在词法分析和解析之后优化抽象语法树。这意味着它将找到诸如取消的运算符和计算为常量的表达式之类的东西,并将树的这些部分减少到一个节点。这也意味着有关结构的信息不可用。一些代码生成之后发生的优化过程可能会考虑到这一点,因为它们有额外的信息,但是对于修剪AST这样的信息,那些信息还没有出现。