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

时间:2017-07-19 09:21:39

标签: c arrays language-lawyer

换句话说,在做

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

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

7 个答案:

答案 0 :(得分:10)

保证地址排序。关系运算符的行为在C11 6.5.8p5

中定义
  

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

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

但令人惊讶的是差异 &array[x] - &array[0]仅在

时定义
  • x是元素的实际索引或大于数组
  • 中的最大索引的索引
  • x不大于PTRDIFF_MAX

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

  

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

如果签名的ptrdiff_t与无符号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 <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stddef.h>

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]及其未定义的行为现在产生了否定的结果!

答案 1 :(得分:3)

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

  

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

因此,您不需要为单个指针(定位)本身而烦恼。重要的是差异(即类似|a-b|

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

  

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

因此,对于&array[x] <= &array[0]之类的语句,当0时,它将评估为x > 0 FALSY )。

Thanks to the other answer by Joachim

答案 2 :(得分:2)

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

6.5.2.1p2

  

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

答案 3 :(得分:2)

C11标准将数组元素之间的地址差异定义为取决于元素的相对(逻辑)顺序的数字。正如additive operators

的说明中所述
  

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

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

答案 4 :(得分:2)

来自C11规范(ISO / IEC 9899:2011(E))§6.5.8/ 5:

  

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

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

答案 5 :(得分:0)

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

char[] foobar;
char *foobarPtr = foobar;

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

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

答案 6 :(得分:0)

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

的语法糖
index = (array+x) - (array+0)

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

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

您可以在ISO9899内谷歌搜索或搜索的相关主题是pointer arithmeticdesugaring arrays as pointers