11

第 §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)。

简化&->表达式在概念上(恕我直言)与&[]简单的地址加偏移量相同。它甚至是一个可以在编译时确定的偏移量,而不是潜在的[]操作员运行时。

为什么这看起来如此不一致,有什么理由吗?

4

3 回答 3

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) 无论如何都是无效的。

至于为什么会出现这种“不一致”,我只能猜测。很可能没有人想过让它保持一致,或者没有人看到一个令人信服的用例。这有可能被考虑并最终被拒绝。没有关于理由&*减少的评论。您也许可以在WG14 论文中找到一些确定的信息,但不幸的是,它们似乎组织得很差,因此翻阅它们可能很乏味。

于 2011-02-05T07:11:52.617 回答
2

我认为该规则不是为了优化目的而添加的(它带来了什么,而 as-if 规则没有带来什么?)但是允许&t[sizeof(t)/sizeof(*t)]并且&*(t+sizeof(t)/sizeof(*t))没有它将是未定义的行为(直接写这样的东西可能看起来很傻,但是添加一层或两层宏,这很有意义)。我没有看到特殊套管 &p->m 会带来这种好处的情况。请注意,正如詹姆斯指出的那样,&p[10]使用 pa 空指针仍然是未定义的行为;&p->m使用 pa 空指针同样会保持无效(我必须承认,当 p 是空指针时我看不到任何用处)。

于 2011-02-05T07:51:26.900 回答
1

我相信编译器可以选择以不同的方式打包,可能会在结构成员之间添加填充以提高内存访问速度。这意味着您不能肯定地说那b始终是 4 的偏移量。单一值没有同样的问题。

此外,编译器在优化阶段可能不知道结构在内存中的布局,从而阻止了与结构成员访问和后续指针转换有关的任何类型的优化。


编辑:

我还有一个理论...

很多时候编译器会在词法分析和解析之后优化抽象语法树。这意味着它将找到诸如抵消的运算符和评估为常数并将树的这些部分减少为一个节点的表达式之类的东西。这也意味着有关结构的信息不可用。在某些代码生成之后发生的后续优化过程可能能够考虑到这一点,因为它们有额外的信息,但对于修剪 AST 之类的事情,这些信息还不存在。

于 2011-02-05T06:54:24.170 回答