【问题标题】:Offset between two array element addresses in CC中两个数组元素地址之间的偏移量
【发布时间】:2017-11-22 01:58:55
【问题描述】:

我有一个问题要我找出两个数组元素地址之间的字节偏移量:

double myArray[5][7];

如果 C 以列优先顺序存储数据,则 &myArray[3][2]&myArray[0][0] 的偏移量(以字节为单位)将为:

按照列的主要顺序,我认为元素会这样排列:

[0][0] -- [1][0] -- [2][0] -- [3][0] -- ..... -- [3][2]

因此,在我看来,以字节为单位的偏移量是计算 [0][0] 和 [3][2] 之间的跳转次数并将其乘以 8,因为它是一个双精度数组。然而,令我困惑的是,它使用 & 运算符要求偏移量。这会以某种方式改变答案,因为它在两个地址之间询问还是过程仍然相同?我认为它会是一样的,但我不是 100% 确定。

如果我的想法是正确的,这会是 8*15 字节吗?

【问题讨论】:

  • 没有 & 那些表达式将产生 而不是地址。
  • 正确,但是找到两个内存位置之间的偏移量会改变过程吗?我想我要说的是这个问题的框架让我感到困惑,我是否做得正确。
  • 找到 3.14 和 2.78 之间的偏移量是没有意义的。 “偏移”只对内存地址有意义。
  • 好吧,我假设有可能将问题定义为“元素 [3][2] 和 [0][0] 之间的偏移量”,也许可以假设地址部分。无论如何,您是在告诉我我的方法是正确的方法吗?
  • 我认为该问题的作者只是想以更像 C 的方式提出问题。您应该可以通过以下方式进行测试:printf("%d\n", (char*)&myArray[3][2] - (char*)&myArray[0][0]);

标签: c arrays offset


【解决方案1】:

二维数组的内存布局将是一块连续的内存。(根据您的问题)

int x[2][3] = {{0,1,2},{3,4,5}};

这将在(您的问题)中列出

--+--+--+--+--+--+
 0| 3| 1| 4|2 |5 |
--+--+--+--+--+--+

但在C 中,它的存储方式类似于

--+--+--+--+--+--+
 0| 1| 2| 3|4 |5 |
--+--+--+--+--+--+

现在你是绝对正确的,你可以考虑在[0][0][3][2] 之间跳转,但是有一个更好的方法可以在不考虑所有这些的情况下做到这一点,你可以确定它们的偏移量将是它们的地址差异。

您可以简单地获取他们的地址并减去它们。

ptrdiff_t ans = &a[3][2]-&a[0][0];(这基本上是两个元素之间的差距)

这就产生了答案。 printf("answer = %td",ans*sizeof(a[0][0]);(一个差距=sizeof(a[0][0]))[在你的情况下double]

或者更好的方法是

ptrdiff_t ans = (char*)&a[3][2] - (char*)&a[0][0];//number of bytes between them.

我将解释一下为什么char*在这里很重要:

(char*)&a[0][0]&a[0][0] 都包含相同的东西value-wise(这还不够通用)

但它在指针运算中很重要。 (解释不同)。

不使用强制转换时,解释为数组元素的数据类型。这意味着现在它考虑doubles 的差异。当你施放它时,它会在char-s 中吐出结果或差异。

为什么会这样?因为所有数据存储器都是byte 可寻址的,而char 是单字节的。


这比预想的要多,首先让我们看看C中的数组是什么?

C 并没有真正的多维数组。在C 中,它被实现为一个数组数组。是的,这些多维数组元素以行优先顺序存储。

为了更清楚一点,我们可以查看标准 §6.5.2.1

的示例

考虑由声明定义的数组对象

      int x[3][5];

这里的x3 x 5ints 数组; 更准确地说,x 是一个数组 三个元素对象,每个对象都是一个由五个ints 组成的数组。在里面 表达式x[i],相当于(*((x)+(i)))x是第一个 转换为指向五个整数的初始数组的指针。然后i 是 根据x 的类型进行调整,这在概念上需要 将i 乘以指针指向的对象的大小, 即五个 int 对象的数组。结果被添加并 间接应用于产生一个由五个整数组成的数组。当用于 表达式x[i][j],该数组又被转换为指针 到ints 的第一个,所以x[i][j] 产生一个int。

所以我们可以说double myArray[5][7]; 这里myArray[3][2]myArray[0][0] 不是同一个数组的一部分。

现在我们已经完成了 - 让我们进入其他内容:

来自标准 §6.5.6.9

当两个指针相减时,都应该指向 相同的数组对象,或数组对象的最后一个元素; 结果是两个数组的下标之差 元素。

但这里myArray[3]myArray[0] 表示两个不同的数组。这意味着myArrayp[3][2]myArray[0][0] 都属于不同的数组。而且它们不是最后一个元素。 所以减法&myArray[3][2] - &myArray[0][0]的行为不会被标准定义。

埃里克(Eric Postpischil)指出了这个想法。

【讨论】:

  • 我知道,正如我之前所问的,只要我们的数组被设为列优先,该打印语句才能正常工作,对吧?所以也许我必须以列优先的顺序在堆上分配,因为 C 默认情况下会以行优先的方式制作堆栈数组?
  • @dj1121.:即使内存布局是行主要顺序或列主要顺序,它也可以工作-但是当然ans的值不同-正如您所说的jump不同。
  • 它被要求以字节为单位回答,您的代码以整数回答
  • 那么这种情况下的字节数会是8*15吗?
  • @dj1121.: 将是sizeof(x[0][0])*anssizeof(double)*ans
【解决方案2】:

在行主要遍历中,声明为array[height][width],用法为array[row][column]。在行优先中,步进到下一个数字会给您下一列,除非您超出宽度并“换行”到下一行。每行将width 添加到您的索引中,每列添加1,使行成为“主要”索引。

为了获得列主要等效项,您假设下一个值是下一行,当行超过高度时,它会“换行”到下一列。这是index = column * height + row描述的。

因此,对于 高度 5 的数组 array[5][7],索引 [3][2] 产生 2*5 + 3 = 13

让我们用一些代码来验证一下。您只需切换索引的顺序即可获得以列为主的行为。

#include <stdio.h>                                                                                                             

int main() {                                                                                                                   
    double array[7][5];                                                                                                    
    void *root = &array[0][0];                                                                                             
    void *addr = &array[2][3];                                                                                             
    size_t off = addr - root;                                                                                              
    printf("memory offset: %d number offset: %d\n", off, off/sizeof(double));                                                                        
    return 0;                                                                                                              
}

运行此程序会产生 104 的地址偏移量,即 13 个双精度数。

编辑:抱歉回答错误

【讨论】:

  • 问题询问到[3][2] 的偏移量,而不是[2][3]。并且不要尝试使用%d 打印size_t 值。
  • 我的代码通过切换这些值将 C 的行优先性质转换为列优先。
  • 嗯...“column-major”是“row-wise”的同义词。两者都是用 C 描述自然 2D 数组布局的正确方法。无需“转换”任何东西。
  • 另外,C 语言不支持 void * 指针上的指针算术。您的 addr - root 在 C 语言中是非法的。
  • 好的,我收回。就我而言,术语一定有一些混淆。我假设“column-major”意味着 major index (第一个索引)是列索引。显然,在这种情况下还有其他意思。不过,您的算术 合法。 C 语言不允许对指向不完整类型的指针进行指针算术运算,包括 void * 指针(请自行查看 coliru.stacked-crooked.com/a/6a3ba81d2632420a
【解决方案3】:

简单的答案

C 没有多维数组,所以我们必须将double myArray[5][7] 解释为一维数组的一维数组。在double myArray[5][7] 中,myArray 是一个包含 5 个元素的数组。这些元素中的每一个都是一个由 7 个double 组成的数组。

因此,我们可以看到myArray[0][0]myArray[0][1] 都是myArray[0] 的成员,并且它们是相邻的成员。因此,元素继续进行[0][0][0][1][0][2] 等。

当我们考虑myArray[1] 时,我们看到它出现在myArray[0] 之后。由于myArray[0] 是一个由7 个double 组成的数组,myArray[1]myArray[0] 之后开始7 个double

现在我们可以看到myArray[3][2]myArray[0][0] 之后是3 个数组(7 个double)和2 个元素(double)。如果double 是 8 个字节,那么这个距离就是 3•7•8 + 2•8 = 184 字节。

正确答案

令我惊讶的是,我在 C 标准中找不到指定 n 元素数组的大小等于 n 乘以一个元素大小的文本。直观地说,这是“显而易见的”——直到我们认为在没有平面地址空间的架构中实现可能存在一些问题,需要它以复杂的方式访问数组。因此,我们不知道double的7个数组的大小,所以我们一般无法计算myArray[3][2]myArray[0][0]的距离。

我不知道任何 C 实现,其中 n 元素数组的大小不是一个元素大小的 n 倍,因此计算将起作用在所有正常的 C 实现中,但我认为根据 C 标准不一定如此。

在程序中计算距离

建议可以使用(char *) &amp;myArray[3][2] - (char *) &amp;myArray[0][0] 计算地址。虽然这并不严格符合 C,但它可以在常见的 C 实现中工作。它通过将地址转换为指向char 的指针来工作。减去这两个指针,然后以char(字节)为单位得出它们之间的距离。

使用uintptr_t 是另一种选择,但我将省略对其及其注意事项的讨论,因为这个答案已经太长了。

计算距离的错误方法

有人可能会认为&amp;myArray[3][2] 是指向double 的指针,而&amp;myArray[0][0] 是指向double 的指针,所以&amp;myArray[3][2] - &amp;myArray[0][0] 是它们之间的距离,以double 为单位测量。但是,该标准要求被减去的指针必须指向同一数组对象的元素或最后一个元素之后的元素。 (此外,为此目的,一个对象可以充当一个元素的数组。)但是,myArray[3][2]myArray[0][0] 不在同一个数组中。 myArray[3][2]myArray[3] 中,myArray[0][0]myArray[0] 中。此外,它们都不是myArray 的元素,因为它的元素是数组,但myArray[3][2]myArray[0][0]double,而不是数组。

鉴于此,有人可能会问(char *) &amp;myArray[3][2] - (char *) &amp;myArray[0][0] 是如何工作的。不也是减去指向不同数组中元素的指针吗?但是,字符类型是特殊的。 C 标准说字符指针可用于访问表示对象的字节。 (从技术上讲,我没有看到标准说可以减去这些指针——它只是说指向对象的指针可以转换为指向字符类型的指针,然后连续递增以指向对象的剩余字节。但是,我认为这里的意图是让指向对象字节的字符指针像对象的字节是一个数组一样。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 2019-10-22
    • 1970-01-01
    • 2010-11-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多