为什么负数组索引有意义?


14

我在C编程方面遇到了奇怪的经历。考虑以下代码:

int main(){
  int array1[6] = {0, 1, 2, 3, 4, 5};
  int array2[6] = {6, 7, 8, 9, 10, 11};

  printf("%d\n", array1[-1]);
  return 0;
}

编译并运行此程序时,没有任何错误或警告。正如我的讲师所说,数组索引-1访问另一个变量。我仍然很困惑,为什么编程语言具有这种能力?我的意思是,为什么要允许使用负数组索引?


2
尽管这个问题是由C作为具体的编程语言所激发的,但我认为它可以理解为一个概念性的问题(如果勉强可以)。
拉斐尔

7
@Raphael我不同意并认为它应该属于SO,无论哪种方式,这都是教科书未定义的行为(引用数组外部的内存),并且适当的编译器标志也应对此发出警告
棘手怪胎

我同意@ratchetfreak。因为有效的索引范围是[0,5],所以这似乎是编译器的缺陷。外部的任何内容都必须是编译/运行时错误。通常,向量是函数特殊情况,其第一元素索引由用户决定。由于C约定是元素从索引0开始,因此访问负元素是错误的。
2013年

2
@Raphael C在典型的语言上有两个特殊的特性,数组在这里很重要。一种是C具有子-1数组,并且引用子数组的元素是在较大数组中引用该数组之前的元素的一种完全有效的方法。另一个是如果索引无效,则该程序无效,但是在大多数实现中,您将获得无声的不良行为,而不是超出范围的错误。
吉尔斯(Gilles)“所以,别再邪恶了”

4
@Gilles如果这是问题的关键,那么确实应该在Stack Overflow上
拉斐尔

Answers:


27

数组索引操作a[i]从C的以下功能中获得其含义

  1. 语法a[i]等效于*(a + i)。因此,说到5[a]的第5个元素是正确的a

  2. 指针算术表示,给定一个指针p和一个整数ip + i 指针pi * sizeof(*p)字节前进

  3. 数组的名称a很快就变成了指向第0个元素的指针a

实际上,数组索引是指针索引的一种特殊情况。由于指针可以指向数组内的任何位置,因此看起来任何任意表达式p[-1]不会出错,因此编译器不会(不能)将所有此类表达式都视为错误。

您的示例a[-1]a实际上是数组名称的地方实际上是无效的。IIRC,如果有一个有意义的指针值作为表达式的结果是不确定的a - 1,其中a是知道是一个指针数组的第0个元素。因此,聪明的编译器可以检测到该错误并将其标记为错误。其他编译器仍然可以兼容,同时可以通过为您提供指向随机堆栈插槽的指针来使自己陷入困境。

计算机科学的答案是:

  • 在C语言中,[]运算符是在指针而不是数组上定义的。特别是,它是根据指针算术和指针取消引用定义的。

  • 在C中,指针抽象为(start, length, offset)条件为的元组0 <= offset <= length。指针算术本质上是针对偏移量的提升算法,但需要注意的是,如果运算结果违反了指针条件,则它是未定义的值。取消引用指针会添加一个附加约束offset < length

  • C的概念undefined behaviour允许编译器将元组具体表示为单个数字,而不必检测任何违反指针条件的情况。满足抽象语义的任何程序在具体(有损)语义上都是安全的。违反抽象语义的任何内容都可以在不加任何注释的情况下被编译器接受,并且它可以执行它想要做的任何事情。


请尝试给出一个普遍的答案,而不是一个取决于任何特定编程语言的特质的答案。
拉斐尔

6
@Raphael,问题是明确约C.我想我谈到了为什么一个C编译器允许编译看似无意义的表达的具体问题下的定义
哈日

特别是关于C的问题不在这里;注意我对这个问题的评论。
拉斐尔

5
我认为问题的比较语言学方面仍然有用。我相信我给出了相当“计算机科学”风格的描述,说明了为什么特定的实现表现出特定的具体语义。
哈里

15

数组只是简单地布置为连续的内存块。诸如a [i]的数组访问将转换为对内存位置addressOf(a)+ i的访问。这段代码a[-1]是完全可以理解的,它只是指向数组开始之前的地址。

这似乎很疯狂,但是有很多原因允许这样做:

  • 检查a [-]的索引i是否在数组的范围内是很昂贵的。
  • 一些编程技术实际上利用了a[-1]有效的事实 。例如,如果我知道这a实际上不是数组的开始,而是一个指向数组中间的指针,则a[-1]只需获取指针左侧的数组元素即可。

6
换句话说,可能不应该使用它。期。什么,你叫Donald Knuth,并且您尝试保存另外17条指令?一定要继续。
拉斐尔

感谢您的答复,但我不知道。顺便说一句,我会一遍又一遍地读下去,直到我理解.. :)
Mohammed Fawzan

2
@Raphael:cola对象模型的实现使用-1位置来存储vtable:piumarta.com/software/cola/objmodel2.pdf。因此,字段存储在对象的正部分,而vtable存储在负部分。我不记得这些细节,但是我认为这与一致性有关。
戴夫·克拉克2013年

@DeZéroToxin:数组实际上只是内存中的一个位置,逻辑上是数组的一部分,它旁边的某些位置。但实际上,数组只是一个指针。
戴夫·克拉克

1
@Raphael a[-1]某些情况下非常有意义a,在这种情况下,它是完全非法的(但未被编译器捕获)
vonbrand

4

正如其他答案所解释的那样,这是C中未定义的行为。请考虑将C定义为(并且最常使用)“高级汇编程序”。C的用户对它的不妥协的速度非常看重它,并且出于纯粹的性能考虑,在运行时检查(大多数情况下)是不可能的。像这样的语言,有些C构造对于从其他语言来的人们似乎毫无意义a[-1]。是的,这并不总是有意义的(


1
我喜欢这个答案。给出了为什么可以这样做的真实原因。
darxsys

3

可以使用这种功能来编写直接访问内存的内存分配方法。一种这样的用法是使用负数组索引检查前一个存储块,以确定两个块是否可以合并。开发非易失性内存管理器时,我已经使用了此功能。


2

C不是强类型。标准的C编译器不会检查数组范围。另一件事是C中的数组不过是一个连续的内存块,而索引从0开始,因此索引-1是之前任何位模式的位置a[0]

其他语言则很好地利用了负数索引。在Python中,a[-1]将返回最后一个元素,a[-2]将返回倒数第二个元素,依此类推。


2
强类型和数组索引之间有何关系?是否存在类型为自然数的语言,其中数组索引必须为自然数?
拉斐尔

@Raphael据我所知,强类型化意味着捕获类型错误。数组是类型,IndexOutOfBounds是错误,因此在强类型语言中将被报告,而在C中则不会。我正是这个意思。
saadtaame

在我所知道的语言中,数组索引的类型为int,因此a[-5],更一般地,int i; ... a[i] = ...;它的类型正确。仅在运行时检测到索引错误。当然,聪明的编译器可能会检测到某些违规情况。
拉斐尔

@Raphael我说的是整个数组数据类型,而不是索引类型。这就解释了为什么C确实允许用户编写a [-5]。是的,-5是正确的索引类型,但超出范围,这是一个错误。我的答案中没有提到编译或运行时类型检查。
saadtaame

1

简单来说:

C语言中的所有变量(包括数组)都存储在内存中。假设您有14个字节的“内存”,并初始化了以下内容:

int a=0;
int array1[6] = {0, 1, 2, 3, 4, 5};

另外,将int的大小考虑为2个字节。然后,假设在内存的前2个字节中将保存整数a。在接下来的2个字节中,将保存数组的第一个位置的整数(即array [0])。

然后,当您说array [-1]就像引用保存在array [0]之前的内存中的整数时,我们假设它是整数a。实际上,这并不是变量存储在内存中的确切方式。


0
//:Example of negative index:
//:A memory pool with a heap and a stack:

unsigned char memory_pool[64] = {0};

unsigned char* stack = &( memory_pool[ 64 - 1] );
unsigned char* heap  = &( memory_pool[ 0     ] );

int stack_index =    0;
int  heap_index =    0;

//:reserve 4 bytes on stack:
stack_index += 4;

//:reserve 8 bytes on heap:
heap_index  += 8;

//:Read back all reserved memory from stack:
for( int i = 0; i < stack_index; i++ ){
    unsigned char c = stack[ 0 - i ];
    //:do something with c
};;
//:Read back all reserved memory from heap:
for( int i = 0; i < heap_index; i++ ){
    unsigned char c = heap[ 0 + i ];
    //:do something with c
};;

欢迎来到CS.SE!我们正在寻找带有解释或阅读说明的答案。我们不是编码站点,并且我们不希望答案只是一段代码。您可能会考虑是否可以编辑答案以提供此类信息。谢谢!
DW
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.