&array[i] 总是等同于 (array + i) 吗?
Is &array[i] always equivalent to (array + i)?
最近看到一段C代码是这样的:
#include <stdio.h>
int main(void) {
int array[5] = {1, 2, 3, 4, 5};
for (int* ptr = &array[0]; ptr != &array[5]; ptr++)
printf("%d\n", *ptr);
return 0;
}
由于运算符 []
在 C 中优先于运算符 &
,我认为 &array[5]
等同于 &(*(array + 5))
,这会导致未定义的行为(我们不允许取消引用 array + 5
)。这就是为什么我怀疑上面的代码格式错误。 (顺便说一下,我知道 ptr != array + 5
没问题。)
我使用带有 -O0 -fsanitize=address,undefined
编译器标志的 GCC 11.1.0 和 Clang 12.0.0 测试了这段代码,但两个编译器都将 &array[5]
解释为 array + 5
,并且没有发生意外行为。
&array[i]
是否总是等同于 array + i
(即使 array[i]
无效)?提前谢谢你。
even when array[i] is invalid
从消毒剂的角度回答:
&array[i]
和 array+i
总是给出相同的指针,但只有 &array[i]
会通过地址清理程序(至少在 gcc 中)引发运行时错误。所以,在这方面它们是不等价的。
请注意,如果 i=5
在您的情况下,如果未取消引用指针,地址清理器将不会给出错误,因此上面的代码将起作用(即使打开了清理器)。但是,如果 i
大于 5,sanitizer 会立即给出错误。关于上面的代码,建议使用指针运算(如果你坚持使用指针):
for (int* ptr = array; ptr < array+5; ptr++)
首先是6.5.2.1/2:
The definition of the subscript operator []
is that E1[E2]
is identical to (*((E1)+(E2)))
然后在(6.5.3.2/3)中定义,一元&
运算符:
[...] Similarly, if the operand is the result of a []
operator, neither the &
operator nor
the unary *
that is implied by the []
is evaluated and the result is as if the &
operator
were removed and the []
operator were changed to a +
operator.
这明确表示 &x[y]
的意思就是 (x) + (y)
。
尽管 C 标准根据指针加法和取消引用运算符 +
和 *
、+
和一元 [=] 定义了 []
运算符的行为14=] 运算符,使得 array[i]
意味着 *(array+i)
,clang 和 gcc 实际上都没有那样处理它。在两个运算符都会产生已定义行为的情况下,行为是相同的,但它们与编译器对“严格别名规则”的解释不同。
例如,标准不允许使用成员类型左值访问联合对象。给出如下声明:
union blob { unsigned short hh[4]; unsigned ww[2]; } u;
如果认为访问这些成员的唯一方法是使用字符指针或像 memcpy
这样的函数,那将是荒谬的,特别是因为标准明确允许通过联合对象进行类型双关。另一方面,如果在 gcc 中测试函数:
int test1(int i, int j)
{
u.hh[i] = 1;
u.ww[j] = 2;
return u.hh[i];
}
int test2(int i, int j)
{
*(u.hh+i) = 1;
*(u.ww+j) = 2;
return *(u.hh+i);
}
两个编译器都将为 test1
生成代码以适应类型双关的可能性,但将为 test2
生成无条件 returns 1 的代码。这是允许的,因为标准描述了两种形式都作为未定义的行为,以便允许实现——在实现质量的基础上——支持他们的客户认为有用的任何形式。
虽然我不知道在您列出的特定表达式中行为如何比较,但它们对 []
的解释不同于 +
和 *
的组合这一事实意味着标准根据后者对前者的定义不应被视为表示它们将被相同地处理。
最近看到一段C代码是这样的:
#include <stdio.h>
int main(void) {
int array[5] = {1, 2, 3, 4, 5};
for (int* ptr = &array[0]; ptr != &array[5]; ptr++)
printf("%d\n", *ptr);
return 0;
}
由于运算符 []
在 C 中优先于运算符 &
,我认为 &array[5]
等同于 &(*(array + 5))
,这会导致未定义的行为(我们不允许取消引用 array + 5
)。这就是为什么我怀疑上面的代码格式错误。 (顺便说一下,我知道 ptr != array + 5
没问题。)
我使用带有 -O0 -fsanitize=address,undefined
编译器标志的 GCC 11.1.0 和 Clang 12.0.0 测试了这段代码,但两个编译器都将 &array[5]
解释为 array + 5
,并且没有发生意外行为。
&array[i]
是否总是等同于 array + i
(即使 array[i]
无效)?提前谢谢你。
even when array[i] is invalid
从消毒剂的角度回答:
&array[i]
和 array+i
总是给出相同的指针,但只有 &array[i]
会通过地址清理程序(至少在 gcc 中)引发运行时错误。所以,在这方面它们是不等价的。
请注意,如果 i=5
在您的情况下,如果未取消引用指针,地址清理器将不会给出错误,因此上面的代码将起作用(即使打开了清理器)。但是,如果 i
大于 5,sanitizer 会立即给出错误。关于上面的代码,建议使用指针运算(如果你坚持使用指针):
for (int* ptr = array; ptr < array+5; ptr++)
首先是6.5.2.1/2:
The definition of the subscript operator
[]
is thatE1[E2]
is identical to(*((E1)+(E2)))
然后在(6.5.3.2/3)中定义,一元&
运算符:
[...] Similarly, if the operand is the result of a
[]
operator, neither the&
operator nor the unary*
that is implied by the[]
is evaluated and the result is as if the&
operator were removed and the[]
operator were changed to a+
operator.
这明确表示 &x[y]
的意思就是 (x) + (y)
。
尽管 C 标准根据指针加法和取消引用运算符 +
和 *
、+
和一元 [=] 定义了 []
运算符的行为14=] 运算符,使得 array[i]
意味着 *(array+i)
,clang 和 gcc 实际上都没有那样处理它。在两个运算符都会产生已定义行为的情况下,行为是相同的,但它们与编译器对“严格别名规则”的解释不同。
例如,标准不允许使用成员类型左值访问联合对象。给出如下声明:
union blob { unsigned short hh[4]; unsigned ww[2]; } u;
如果认为访问这些成员的唯一方法是使用字符指针或像 memcpy
这样的函数,那将是荒谬的,特别是因为标准明确允许通过联合对象进行类型双关。另一方面,如果在 gcc 中测试函数:
int test1(int i, int j)
{
u.hh[i] = 1;
u.ww[j] = 2;
return u.hh[i];
}
int test2(int i, int j)
{
*(u.hh+i) = 1;
*(u.ww+j) = 2;
return *(u.hh+i);
}
两个编译器都将为 test1
生成代码以适应类型双关的可能性,但将为 test2
生成无条件 returns 1 的代码。这是允许的,因为标准描述了两种形式都作为未定义的行为,以便允许实现——在实现质量的基础上——支持他们的客户认为有用的任何形式。
虽然我不知道在您列出的特定表达式中行为如何比较,但它们对 []
的解释不同于 +
和 *
的组合这一事实意味着标准根据后者对前者的定义不应被视为表示它们将被相同地处理。