【问题标题】:In C, is it guaranteed that the array start address is smaller than the other elements' addresses?在C中,是否保证数组起始地址小于其他元素的地址?
【发布时间】:2017-07-19 09:21:38
【问题描述】:

也就是说做的时候

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

是否始终保证(根据 C 标准)&array[0]

【问题讨论】:

  • 猜测为 6.5.6:9
  • "&array[x] =.

标签: c arrays language-lawyer


【解决方案1】:

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

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

因此,如果x 是元素的索引,或者比最大索引大一,&array[x] >= &array[0] 总是为真。 (如果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 的宽度相同,则可能有一个数组,其中存在一个大于PTRDIFF_MAX 的索引x;然后&array[x] >= &array[0] 仍然存在,但&array[x] - &array[0] 具有完全未定义的行为。


这是一个演示。我的电脑是运行 64 位 Ubuntu Linux 的 x86-64,但它也能够运行 32 位程序。在 32 位 X86 Linux + GCC 中,ptrdiff_t 是 32 位有符号整数,size_t 是 32 位无符号整数。在 64 位 Linux 中以 32 位模式运行的程序可以使用 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,&amp;array[x]0xf7504008&amp;array[x]大于&amp;array[0]&amp;array[x] - &amp;array[1] 的差异产生了积极的结果,而 &amp;array[x] - &amp;array[0] 的未定义行为现在产生了消极的结果!

【讨论】:

  • @DavidBowling 或者我自己试图成为 FGITW 的愚蠢
  • 在 C99 中已经相同,在 C++17 中的完整性仍然相同。我真的很讨厌没有cmets的DV!
  • @SergeBallesta 我在 fgitwing 时得到了 dvs,所以 np :D
【解决方案2】:

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

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

因此,您无需为单个指针(定位)本身而烦恼。重要的是差异(例如,|a-b| 之类的东西)


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

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

因此,对于像&amp;array[x] &lt;= &amp;array[0] 这样的语句,当x &gt; 0 时,它将评估为0 (FALSY)。

Thanks to the other answer by Joachim

【讨论】:

    【解决方案3】:

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

    6.5.2.1p2:

    后缀表达式后跟方括号 [] 中的表达式 是数组对象元素的下标名称。这 下标运算符 [] 的定义是 E1[E2] 等同于 (*((E1)+(E2)))。由于适用于 二进制 + 运算符,如果 E1 是一个数组对象(相当于一个指针 到数组对象的初始元素)并且 E2 是一个整数, E1[E2] 表示 E1 的第 E2 个元素(从零开始计数)。

    【讨论】:

      【解决方案4】:

      C11 标准将数组元素之间的地址差异定义为一个数字,该数字取决于元素的相对(逻辑)顺序。在additive operators的描述中指定:

      当两个指针相减时,两个指针都指向 相同的数组对象,或数组对象的最后一个元素; 结果是两个数组的下标之差 结果的大小是实现定义的,它的 类型(有符号整数类型)是 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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

        • 指向同一数组元素的指针如果x小于0,则指针无效,无法比较。
        【解决方案6】:

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

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

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

        【讨论】:

          【解决方案7】:
          index = &array[x] - &array[0];
          

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

          因为在 C 中,任何数组都被脱糖为指针。

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

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

          【讨论】:

            猜你喜欢
            • 2019-05-09
            • 2013-04-30
            • 1970-01-01
            • 1970-01-01
            • 2018-06-08
            • 2020-05-23
            • 2014-10-25
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多