最近我注意到,在C语言中,有一个重要的区别就是 array
和 &array
为下面的声明。
char array[] = {4, 8, 15, 16, 23, 42};
前者是 指针 而后者是 指针指向一个6个字符的数组。. 此外,值得注意的是,写 a[b]
是一个句法糖,用于 *(a + b)
. 事实上,你可以写 2[array]
并且完全可以按照标准进行操作。
所以我们可以利用这些信息来写这个。
char last_element = (&array)[1][-1];
&array
有一个6个字符的大小,所以 (&array)[1])
是一个指针,指向位于数组之后的chars。通过查看 [-1]
因此我访问的是最后一个元素。
例如,我可以用这个方法交换整个数组。
void swap(char *a, char *b) { *a ^= *b; *b ^= *a; *a ^= *b; }
int main() {
char u[] = {1,2,3,4,5,6,7,8,9,10};
for (int i = 0; i < sizeof(u) / 2; i++)
swap(&u[i], &(&u)[1][-i - 1]);
}
这种以最后一个元素访问数组的方法是否有缺陷?
C标准没有定义 (&array)[1]
.
考虑到 &array + 1
. 这是C标准定义的,原因有二。
&array
是一个指向单个对象的指针(它本身是一个数组,但指针运算是针对指向数组的指针,而不是指向一个元素的指针)。所以 &array + 1
是定义的指针算子,它的指向刚好超过了 array
.
然而,根据下标运算符的定义,。(&array)[1]
是 *(&array + 1)
. 虽然 &array + 1
已定义,应用 *
到它不是。C 2018 6.5.6 8明确告诉我们,关于指针运算的结果,"如果结果指向数组对象的最后一个元素过去一个,则不得将其作为单数的操作数。*
操作符,进行评估。"
由于大多数编译器的设计方式,问题中的代码可能会按照你的愿望移动数据。然而,这不是你应该依赖的行为。你可以获得一个很好的指针,指向数组的最后一个元素之外,用 char *End = array + sizeof array / sizeof *array;
. 然后你可以使用 End[-1]
指的是最后一个元素。End[-2]
表示倒数第二个元素,以此类推。
虽然标准规定arrayLvalue[i]指的是 (*((arrayLvalue)+(i)))
的第一个元素的地址来处理。arrayLvalue
,gcc有时会处理 []
当应用于一个数组类型的值或lvalue时,作为一个操作符,它的行为是一个索引版本的 .member
语法,产生一个值或l值,编译器会将其视为数组类型的一部分。 我不知道当数组类型的操作数不是结构或联合体的成员时,这种情况是否可以观察到,但是在有这种情况的情况下,效果是很明显的,而且我不知道有什么东西可以保证类似的逻辑不会被应用到嵌套数组中。
struct foo {unsigned char x[12]};
int test1(struct foo *p1, struct foo *p2)
{
p1->x[0] = 1;
p2->x[1] = 2;
return p1->x[0];
}
int test2(struct foo *p1, struct foo *p2)
{
char *p;
p1->x[0] = 1;
(&p2->x[0])[1] = 2;
return p1->x[0];
}
gcc生成的代码是 test1
总是返回1,而生成的 test2
将返回p1->x[0]中的任何内容。 我不知道在标准或gcc的文档中,有任何东西表明这两个函数应该有不同的行为,也不知道应该如何强迫编译器生成代码,以适应在 p1
和 p2
恰好可以在必要的情况下识别出分配块的重叠部分。 虽然在 test1()
对于所写的函数来说是合理的,但我知道没有任何文件对标准的解释会将这种情况视为 UB,但定义了代码的行为,如果它写成了 p2->x[0]
而不是 p2->x[1]
.
我会做一个for循环,在这个循环中,我设置i=向量的长度-1,每次我不是增加它,而是减少它,直到它大于0。 for(int i = vet.length;i>0;i--)