C 语言中是否允许负数组索引?

我正在读一些代码,发现这个人正在使用 arr[-2]访问 arr之前的第二个元素,像这样:

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

这样可以吗?

我知道 arr[x]*(arr + x)是一样的,所以 arr[-2]*(arr - 2),看起来不错,你觉得怎么样?

116099 次浏览

听起来不错,但是你真正需要它的情况并不多见。

没错 C996.52.1/2:

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

没有魔法。这是1比1的等价关系。当取消对指针(*)的引用时,您需要确保它指向一个有效的地址。

这只有在 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]

然而,使用否定下标是不寻常的。

可能是 arr指向数组的中间,因此使得 arr[-2]指向原始数组中的某个内容而没有超出界限。

我不确定这种方法的可靠性如何,但是我刚刚读到了关于64位系统(大概是 LP64)上的负数组索引的以下警告: http://www.devx.com/tips/Tip/41349

作者似乎在说,除非数组索引被明确提升到64位(例如通过 ptrdiff _ t 强制转换) ,否则32位 int 数组索引和64位寻址可能会导致糟糕的地址计算。实际上,我在 PowerPC 版本的 gcc4.1.0上看到过一个类似的 bug,但我不知道它是编译器 bug (即应该按照 C99标准工作)还是正确的行为(即索引需要强制转换为64位才能正确执行) ?

我知道这个问题已经得到了解答,但我还是忍不住要分享这个解释。

我记得编译器设计原则: 让我们假设 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 语言中数组中的负索引。

关于为什么有人想要使用负指数,我在两种情况下使用了它们:

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

  2. 在表格的开头放一个百分点

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

but then you should also check that i is positive.
Solution: make it so that a[-1] is -DBLE_MAX, so that x&lt;a[-1] will always be false.

#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]);
}

我想分享一个例子:

GNU C + + 库 basic _ string. h

[注意: 有人指出,这是一个“ C + +”的例子,它可能不适合这个主题的“ C”。我编写了一个“ C”代码,其概念与示例相同。至少 GNU gcc 编译器没有抱怨什么。]

它使用[-1]将指针从用户字符串移回管理信息块。因为它分配记忆一次有足够的空间。

萨伊德 ” * 这种方法的巨大优势在于字符串对象 * 只需要一次分配。 所有的丑陋都是有限的 * 在一对内联函数中,每个内联函数都编译为 * a single@a add 指令: _ Rep: : _ M _ data () ,和 * string: : _ M _ rep () ; 以及获取 * 块的原始字节和足够的空间,并构造了一个 _ Rep * Object at the front. ”

源代码: Https://gcc.gnu.org/onlinedocs/gcc-10.3.0/libstdc++/api/a00332_source.html

   struct _Rep_base
{
size_type               _M_length;
size_type               _M_capacity;
_Atomic_word            _M_refcount;
};


struct _Rep : _Rep_base
{
...
}


_Rep*
_M_rep() const _GLIBCXX_NOEXCEPT
{ return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); }

它解释道:

*  A string looks like this:
*
*  @code
*                                        [_Rep]
*                                        _M_length
*   [basic_string<char_type>]            _M_capacity
*   _M_dataplus                          _M_refcount
*   _M_p ---------------->               unnamed array of char_type
*  @endcode
*
*  Where the _M_p points to the first character in the string, and
*  you cast it to a pointer-to-_Rep and subtract 1 to get a
*  pointer to the header.
*
*  This approach has the enormous advantage that a string object
*  requires only one allocation.  All the ugliness is confined
*  within a single %pair of inline functions, which each compile to
*  a single @a add instruction: _Rep::_M_data(), and
*  string::_M_rep(); and the allocation function which gets a
*  block of raw bytes and with room enough and constructs a _Rep
*  object at the front.
*
*  The reason you want _M_data pointing to the character %array and
*  not the _Rep is so that the debugger can see the string
*  contents. (Probably we should add a non-inline member to get
*  the _Rep for the debugger to use, so users can check the actual
*  string length.)
*
*  Note that the _Rep object is a POD so that you can have a
*  static <em>empty string</em> _Rep object already @a constructed before
*  static constructors have run.  The reference-count encoding is
*  chosen so that a 0 indicates one reference, so you never try to
*  destroy the empty-string _Rep object.
*
*  All but the last paragraph is considered pretty conventional
*  for a C++ string implementation.

//使用前面的概念,编写一个示例 C 代码

#include "stdio.h"
#include "stdlib.h"
#include "string.h"


typedef struct HEAD {
int f1;
int f2;
}S_HEAD;


int main(int argc, char* argv[]) {
int sz = sizeof(S_HEAD) + 20;


S_HEAD* ha = (S_HEAD*)malloc(sz);
if (ha == NULL)
return -1;


printf("&ha=0x%x\n", ha);


memset(ha, 0, sz);


ha[0].f1 = 100;
ha[0].f2 = 200;


// move to user data, can be converted to any type
ha++;
printf("&ha=0x%x\n", ha);


*(int*)ha = 399;


printf("head.f1=%i head.f2=%i user data=%i\n", ha[-1].f1, ha[-1].f2, *(int*)ha);


--ha;
printf("&ha=0x%x\n", ha);


free(ha);


return 0;
}






$ gcc c1.c -o c1.o -w
(no warning)
$ ./c1.o
&ha=0x13ec010
&ha=0x13ec018
head.f1=100 head.f2=200 user data=399
&ha=0x13ec010

图书馆的作者使用它。希望它有帮助。