Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/c/57.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
在C中从末尾访问数组?_C_Arrays_Pointers_Language Lawyer - Fatal编程技术网

在C中从末尾访问数组?

在C中从末尾访问数组?,c,arrays,pointers,language-lawyer,C,Arrays,Pointers,Language Lawyer,我最近注意到,在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个字符,因此(&ar

我最近注意到,在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])
是指向位于数组后面的字符的指针。因此,通过查看
[-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]);
}
void swap(char*a,char*b){*a^=*b;*b^=*a;*a^=*b;}
int main(){
字符u[]={1,2,3,4,5,6,7,8,9,10};
对于(int i=0;i

这种在末尾访问数组的方法有缺陷吗?

C标准没有定义
(&array)[1]
的行为

考虑
&数组+1
。这由C标准定义,原因有二:

  • 执行指针算术时,将为从数组的第一个元素(索引为0)到最后一个元素之后的结果定义结果
  • 在执行指针算术时,指向单个对象的指针的行为类似于指向具有一个元素的数组的指针。在本例中,
    &array
    是一个指向单个对象的指针(该对象本身就是一个数组,但指针算法用于指向数组的指针,而不是指向元素的指针)
因此
&array+1
是定义的指针算法,它指向数组的末尾

但是,根据下标运算符的定义,
(&array)[1]
*(&array+1)
。虽然定义了
&array+1
,但未对其应用
*
。关于指针运算的结果,C 2018 6.5.6 8明确告诉我们,“如果结果指向数组对象的最后一个元素,则不应将其用作所计算的一元
*
运算符的操作数。”


由于大多数编译器的设计方式,问题中的代码可能会根据需要移动数据。然而,这不是你应该依赖的行为。使用
char*End=array+sizeof-array/sizeof*array,可以获得一个指向数组最后一个元素后面的指针。然后您可以使用
End[-1]
引用最后一个元素,
End[-2]
引用倒数第二个元素,依此类推。

尽管标准规定ArrayValue[i]表示
(*((ArrayValue)+(i))
,这将通过获取
ArrayValue
的第一个元素的地址来处理,当应用于数组类型值或左值时,gcc有时将
[]
视为一个运算符,它表现为
.member
语法的索引版本,生成编译器将视为数组类型一部分的值或左值。我不知道当数组类型的操作数不是结构或联合的成员时,是否可以观察到这一点,但在这种情况下,效果是显而易见的,而且我不知道有什么可以保证类似的逻辑不会应用于嵌套数组

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。
对于(inti=vet.length;i>0;i--)

FWIW,过于“聪明”的代码,例如
voidswap(char*a,char*b){*a^=*b;*b^=*a;*a^=*b;}
几乎总是比仅仅用临时值做显而易见的事情效率低。“从
a
加载寄存器1,从
b
加载寄存器2,处理按位操作(希望不是直接到内存!),然后存储值”的效率比“从
a
加载寄存器1,从
b
加载寄存器2,将r2存储在
a
中,将r1存储在
b
中”低得多。如果编译器没有对“聪明”代码进行优化,那么它总共可以进行9次加载/存储。在最坏的情况下,使用临时值代替6。即使中间值是OOB,它也会计算一个有效的地址,所以它应该是正常的。虽然显示的代码没有这样做,但请注意swap()在a==b.sizeof char u[]正常工作时的作用,但是如果要使用alloc族分配数组,那么,找到大小并因此使用此技巧并非易事。在这一特殊情况下,如果您在堆栈上分配它,那么您可以使用这个construct.IMHO,缺陷在于它不可读,因此您的程序员同伴不太容易维护。理解正在发生的事情的认知努力依赖于对C型系统的深入了解和假设。我更喜欢
sizeof
表达式。6.5.3.2p3(
&
运算符):“类似地,如果操作数是
[]
运算符的结果,则
&
运算符和一元