在C中,是否保证数组起始地址小于其他元素的地址?

换句话说

index = &array[x] - &array[0]; 

总是保证(根据C标准)&array [0] <=&array [x],还是依赖于编译器? 与此主题相关的C标准章节是什么?

地址排序是有保证的。 关系运算符的行为在C11 6.5.8p5中定义:

[…]指向具有较大下标值的数组元素的指针比指向具有较低下标值的相同数组的元素的指针要大。 […]

因此&array[x] >= &array[0]如果x是元素的索引,或者大于最大索引的索引,则&array[x] >= &array[0]始终为true。 (如果x指向实际数组之外,则行为未定义。)

但令人惊讶的是, 差异 &array[x] - &array[0] 在以下时定义

  • x是元素的实际索引,或者是大于数组中的最大索引的索引
  • x不大于PTRDIFF_MAX

因为有一个特殊的角落案例: C11 6.5.6p9说

9当减去两个指针时,两个指针都指向同一个数组对象的元素,或者指向数组对象的最后一个元素的元素; 结果是两个数组元素的下标的差异。 结果的大小是实现定义的,其类型(有符号整数类型)是在头中定义的ptrdiff_t 如果结果在该类型的对象中无法表示,则行为未定义。 换句话说,如果表达式P和Q分别指向数组对象的第i和第j个元素,则表达式(P) – (Q)具有值ij,条件是该值适合于对象输入ptrdiff_t […]

如果带符号的ptrdiff_t与unsigned size_t宽度相同,则可能有一个索引x大于PTRDIFF_MAX ; 然后&array[x] >= &array[0]仍然,但&array[x] - &array[0]有完全未定义的行为。


这是一个演示。 我的计算机是运行64位Ubuntu Linux的x86-64,但它也能运行32位程序。 在32位X86 Linux + GCC中, ptrdiff_t是32位有符号整数, size_t是32位无符号整数。 在32位模式下在64位Linux中运行的程序可以使用malloc轻松分配超过2G的内存,因为整个4G地址空间是为用户模式保留的。

 #include  #include  #include  #include  int main(void) { size_t size = (size_t)PTRDIFF_MAX + 2; size_t x = (size_t)PTRDIFF_MAX + 1; char *array = malloc(size); if (! array) { perror("malloc"); exit(1); } array[0] = 42; array[x] = 84; printf("&array[0]: %p\n", (void *)&array[0]); printf("&array[x]: %p\n", (void *)&array[x]); printf("&array[x] >= &array[0]: %d\n", &array[x] >= &array[0]); printf("&array[x] - &array[1]: %td\n", &array[x] - &array[1]); printf("&array[x] - &array[0]: %td\n", &array[x] - &array[0]); printf("(&array[x] - &array[0]) < 0: %d\n", (&array[x] - &array[0]) < 0); } 

然后编译为32位模式并运行:

 % gcc huge.c -m32 -Wall && ./a.out &array[0]: 0x77567008 &array[x]: 0xf7567008 &array[x] >= &array[0]: 1 &array[x] - &array[1]: 2147483647 &array[x] - &array[0]: -2147483648 (&array[x] - &array[0]) < 0: 1 

内存分配成功,起始地址为0x77558008, &array[x]0xf7504008&array[x]大于&array[0] 。 差异&array[x] - &array[1]产生了一个正结果,而&array[x] - &array[0] ,其未定义的行为,现在产生了负面结果!

首先,FWIW,引用C11 ,章节§6.5.6/ P9,(emphsis mine

当减去两个指针时,两个指针都指向同一个数组对象的元素,或者指向数组对象的最后一个元素的元素; 结果是两个数组元素的下标的差异 。 […]

因此,您不需要为个别指针 (定位)本身而烦恼。 这是重要的区别 (即像|ab|这样的东西)


也就是说,如果必须进行“比较”,(关系运算符的使用, <><=>= ),标准说,

比较两个指针时,结果取决于指向的对象的地址空间中的相对位置。 [....]如果指向的对象是同一聚合对象的成员,则指向具有较大下标值的数组元素的指针比指向具有较低下标值的相同数组的元素的指针要大。 [....]

因此,对于像&array[x] <= &array[0]这样的语句 ,当x > 0时,它将计算为0FALSY )。

感谢Joachim的另一个答案

是的,因为&array[x]被定义为等于array+x

6.5.2.1p2 :

后缀表达式后跟方括号[]中的表达式是数组对象元素的下标名称。 下标运算符[]的定义是E1 [E2]与(*((E1)+(E2)))相同。 由于适用于binary +运算符的转换规则,如果E1是数组对象(等效地,指向数组对象的初始元素的指针)并且E2是整数,则E1 [E2]指定E2的第E2个元素。 E1(从零开始计数)。

C11标准数组元素之间的地址差异定义为取决于元素的相对(逻辑)顺序的数字。 如添加剂操作说明中所述:

当减去两个指针时,两个指针都指向同一个数组对象的元素,或者指向数组对象的最后一个元素的元素; 结果是两个数组元素的下标的差异。 结果的大小是实现定义的,其类型(有符号整数类型)是标头中定义的ptrdiff_t。 如果结果在该类型的对象中无法表示,则行为未定义。 换句话说,如果表达式P和Q分别指向数组对象的第i和第j个元素,则表达式(P) – (Q)具有值ij,条件是该值适合于对象输入ptrdiff_t。 此外,如果表达式P指向数组对象的元素或者指向数组对象的最后一个元素,并且表达式Q指向同一数组对象的最后一个元素,则表达式((Q)+1) – (P)具有与((Q) – (P))+ 1和 – ((P) – ((Q)+1))相同的值,并且如果表达式P指向一个,则值为零即使表达式(Q)+1没有指向数组对象的元素,也是数组对象的最后一个元素。

因此,您的示例中的差异定义x - 0

根据C11规范(ISO / IEC 9899:2011(E))§6.5.8/ 5:

当比较两个指针时,…如果指向的对象是同一聚合对象的成员,则… 指向具有较大下标值的数组元素的指针比指向具有较低下标值的相同数组的元素的指针要大

这意味着&array[x] <= &array[0]将为false,除非x等于零。

假设遍历数组也可以通过递增指针来实现,那么后续索引的绝对地址增加似乎是相当基本的。

 char[] foobar; char *foobarPtr = foobar; foobar[0] == *foobarPtr++; foobar[1] == *foobarPtr++; 

https://www.tutorialspoint.com/cprogramming/c_pointer_to_an_array.htm

 index = &array[x] - &array[0]; 

是语法糖

 index = (array+x) - (array+0) 

因为在C中任何数组都被视为指针。

现在给定pointer arithmetic ,它将被重写为index = x

您可以谷歌搜索或在ISO9899内搜索的相关主题是pointer arithmetic和desugaring数组作为指针 。