C中是否允许使用负数组索引?


115

我只是在阅读一些代码,发现该人正在使用arr[-2]来访问之前的2nd元素arr,如下所示:

|a|b|c|d|e|f|g|
       ^------------ arr[0]
         ^---------- arr[1]
   ^---------------- arr[-2]

可以吗

我知道那arr[x]*(arr + x)。那么arr[-2]*(arr - 2),这似乎确定。你怎么看?

Answers:


168

那是正确的。从C99§6.5.2.1/ 2:

下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同。

没有魔术。这是1-1的当量。与往常一样,在取消引用指针(*)时,您需要确保它指向的是有效地址。


2
还要注意,您不必取消引用指针即可获取UB。仅计算somearray-2是不确定的,除非结果在从开始somearray到结束为止的1 范围内。
RBerteig

34
在较早的书籍中,将[]它们称为指针算术的语法糖。混淆初学者的最喜欢的方法是写1[arr]-而不是arr[1]-看着他们猜测这意味着什么。
Dummy00001

4
当您的32位整数索引为负时,在64位系统(LP64)上会发生什么?在计算地址之前,索引是否应提升为64位带符号的int?
Paul R

4
@Paul,来自第6.5.6 / 8节(加法运算符),“当将整数类型的表达式添加到指针或从指针中减去时,结果将具有指针操作数的类型。如果指针操作数指向元素如果数组对象的大小为0,并且数组足够大,则结果指向与原始元素偏移的元素,以使结果数组元素和原始数组元素的下标之差等于整数表达式。” 因此,我认为它将得到推广,((E1)+(E2))并将成为具有期望值的(64位)指针。
马修·弗拉申

@Matthew:谢谢你-听起来应该像人们合理预期的那样工作。
Paul R

63

仅当arr指针指向数组中的第二个元素或更高版本的元素时才有效。否则,它是无效的,因为您将访问数组范围之外的内存。因此,例如,这将是错误的:

int arr[10];

int x = arr[-2]; // invalid; out of range

但这没关系:

int arr[10];
int* p = &arr[2];

int x = p[-2]; // valid:  accesses arr[0]

但是,使用负下标是不寻常的。


我不会走那么远,说这是无效的,只是潜在的杂乱
马特·乔伊纳

13
@Matt:第一个示例中的代码产生未定义的行为。
James McNellis

5
无效。按照C标准,它明确具有未定义的行为。在另一方面,如果int arr[10];是与前它的其他元件的结构的一部分,arr[-2]有可能被良好定义的,并且如果它是基于你能确定offsetof,等等
R.,GitHub的停止帮助ICE

4
在末尾的K&R第5.3节中找到了它:If one is sure that the elements exist, it is also possible to index backwards in an array; p[-1], p[-2], and so on are syntactically legal, and refer to the elements that immediately precede p[0]. Of course, it is illegal to refer to objects that are not within the array bounds.尽管如此,您的示例仍然可以帮助我更好地理解它。谢谢!
徐强

4
对线程的坏死很抱歉,但是我只是喜欢K&R对于“非法”的含义是如何模棱两可的。最后一句话听起来像是越界访问引发编译错误。那本书对初学者是毒药。
马丁

12

对我来说听起来不错。但是,您很少有理由合法地需要它。


9
这不是罕见的-它在如图像处理非常有用的邻里运营商。
Paul R 2010年

我只需要使用它,因为我正在创建带有堆栈和堆[结构/设计]的内存池。堆栈向着更高的内存地址增长,堆向着更低的内存地址增长。在中间开会。
JMI MADISON


7

我不确定这是否可靠,但我只是阅读了以下有关64位系统(大概是LP64)上的负数组索引的警告:http : //www.devx.com/tips/Tip/41349

作者似乎在说具有64位寻址的32位int数组索引可能导致错误的地址计算,除非将数组索引显式提升为64位(例如,通过ptrdiff_t cast)。我实际上已经看到了他的gcc 4.1.0 PowerPC版本的错误,但是我不知道这是编译器错误(即应根据C99标准工作)还是正确行为(即索引需要转换为64)正确行为的位)?


3
这听起来像是编译器错误。
tbleher 2013年

2

我知道问题已得到解答,但我无法抗拒分享此解释。

我记得编译器设计原理,假设a是一个int数组,int的大小是2,而a的基址是1000。

怎么a[5]工作->

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (5*size of(data type for array a))
i.e. 1000 + (5*2) = 1010

这也是数组中的负索引在C中起作用的原因。

即如果我访问a[-5]它将给我

Base Address of your Array a + (index of array *size of(data type for array a))
Base Address of your Array a + (-5 * size of(data type for array a))
i.e. 1000 + (-5*2) = 990

它将在位置990返回我对象。通过这种逻辑,我们可以在C语言中访问Array中的负索引。


2

关于为什么有人要使用否定索引,我在两种情况下使用了它们:

  1. 有一张组合数字表,告诉您comb [1] [-1] = 0; 您总是可以在访问表之前检查索引,但是这样,代码看起来更干净并且执行得更快。

  2. 在表格的开头放置一个centinel。例如,您想使用类似

     while (x < a[i]) i--;

但是您还应该检查是否i为阳性。
解决方案:使其a[-1]-DBLE_MAX,因此x&lt;a[-1]始终为假。


0
#include <stdio.h>

int main() // negative index
{ 
    int i = 1, a[5] = {10, 20, 30, 40, 50};
    int* mid = &a[5]; //legal;address,not element there
    for(; i < 6; ++i)
    printf(" mid[ %d ] = %d;", -i, mid[-i]);
}

1
尽管此代码可以回答问题,但提供有关此代码为何和/或如何回答问题的其他上下文,可以提高其长期价值。
β.εηοιτ.βε

Python常规...拥有它们。一个简单的用例是可以在不知道数组大小的情况下访问数组的最后一个元素,这在许多项目情况下都是非常实际的要求。许多DSL也从中受益。
Rathinavelu Muthaliar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.