问题 将数组的地址转换为其他数据类型


int main()
{
    char arr[5][7][6];
    char (*p)[5][7][6] = &arr;
    printf("%d\n", (&arr + 1) - &arr);
    printf("%d\n", (char *)(&arr + 1) - (char *)&arr);
    printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);
    printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

    return 0;
}

当我运行上面的代码时,我得到以下输出:

 1
 210 
 42
 210

为什么输出不是 1 在每种情况下?


11346
2017-07-16 13:53


起源

暗示: 6 * 7 = 42 , 5 * 7 * 6 = 210
@Armin你能解释一下吗? - Alex
另一个提示 - 您不会将地址转换为其他类型,直到 后 你已经完成了对指针的算术运算,这是假设原始类型而不是最终类型... - twalberg
@twalberg的例子是真的,肯定不是最后三个。
@Armin由于运算符优先级规则,上述所有最后三个例子都是如此 - (char *)(&arr + 1) 例如,知道这一点 arr 是一个 char[5][7][6], 意思就是 &arr + 1 是 arr + 5*7*6 要么 arr + 210,然后施放到 char *  - (char *)(arr) + 1 将是转换的方式 arr 到了 char * 在算术完成之前。 - twalberg


答案:


注意 &arr 是完整的三维字符数组的地址,而 arr 指向第一个元素,即二维char数组。如下图所示:

 0xbf8ce2c6
+------------------+     ◄-- arr  =  0xbf8ce2c6  
|    0xbf8ce2f0    |  
|   +------------------+     ◄-- arr + 1 = 0xbf8ce2f0
|   |   0xbf8ce31a |   |
|   |   +------------------+      ◄-- arr + 2 = 0xbf8ce31a 
|   |   0xbf8ce344 |   |   |
|   |   |   +------------------+      ◄-- arr + 3 = 0xbf8ce344
|   |   0xbf8ce36e |   |   |   |
|   |   |   |  +------------------+      ◄-- arr + 4 = 0xbf8ce36e
|   |   |   |  |   |   |   |   |  |
+---|---|---|--|---+   |   |   |  |  Each are 7*6, 2-Dimensional 
    |   |   |  |       |   |   |  |  Consists Of 42 bytes 
    +---|---|--|-------+   |   |  |  
        |   |  |           |   |  |
        +---|--|-----------+   |  |
            |  |               |  |
            +--|---------------+  |
               |                  |
               +------------------+

 The diagram show: 
 1. How a 3-dimensional can be interpreted as series of 2-dimensional arrays
 2. Here (arr + i) points to a 2-D array 
 3. Notice difference between: (arr + i + 1) - (arr + i) = 0x2a = 42, where i = [0, 4]

类型 &arr 是 char(*)[5][7][6] 这是char三维数组的地址 [5][7][6]。 价值方面的区别 &arr 和 &arr + 1 是 5 * 7 * 6 * sizeof(char) = 210
因为大小 char[5][7][6] 是 5 * 7 * 6 * sizeof(char)
在你的代码中 &arr 指向三维数组和 &arry + 1 下一个三维数组(我们的代码中不存在)。

检查此工作代码 codepade

int main()
{
    char arr[5][7][6];
    printf(" &arr  : %p", &arr);
    printf(" &arr+1: %p", &arr + 1);

    return 0;
}

输出:

 &arr  : 0xbf5dd7de
 &arr+1: 0xbf5dd8b0

之间的区别 (&arr + 1) - (&arr) = 0xbf5dd8b0 - 0xbf5dd7de = 0xd2 = 210

在你的第二个printf:

printf("%d\n", (char *)(&arr + 1) - (char *)&arr);

你强制转换类型的地址 char(*)[5][7][6] 平淡无奇 (char*),因为sizeof char[5][7][6] 是 210 两个地址都是210远。 (记得 sizeof(char) == 1)。这是输出的原因: 210

正如我在第一次发言中说的那样, arr 是第一个元素的地址,它是二维chars数组。类型 arr 是 char(*)[7][6]。现在一个元素(二维数组的大小是 6 * 7 * sizeof(char) = 42)。
(注意:您可以将三维数组视为一维数组,其中每个元素都是一个二维数组)。

在你的第三个printf:

printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr);

您将类型转换为无符号值(但不是地址/指针类型)。和...之间的不同 arr + 1 和 arr 是 42 * sizeof(char) = 42 (这相当于。的大小 char[7][6])。所以printf语句输出: 42

注意:  你应该读 sizeof(int)== sizeof(void *)?,因为你是对地址进行类型转换。并且此转换尚未完全定义。 (我的解释是你的输出和我给出的输出)。

有关进一步说明,请查看下面的工作代码 codepade:

int main()
{
    char arr[5][7][6];
    printf(" arr  : %p\n", arr);
    printf(" arr+1: %p", arr + 1);

    return 0;
}

输出是:

 arr  : 0xbf48367e
 arr+1: 0xbf4836a8

区别对待 (arr + 1) - (arr) = 0xbf4836a8  - 0xbf48367e = 0x2a = 42

最后的printf:

 printf("%d\n", (unsigned)(p + 1) - (unsigned)p);

只需区分 &arr+1 和 &arr = 210 (类似于第二个printf)因为 p 指向3-D char数组(=&arr)。而且你将它类型化为值类型(不是指针类型)。

另外, (只是为了理解目的添加,我想读者会发现它有用),

让我们学习另一个区别 arr 和 &arr 使用sizeof运算符可以帮助您更深入地理解概念。对于这第一次阅读: sizeof 操作者

当你申请时 sizeof 运算符到数组标识符,结果是整个数组的大小而不是   由数组标识符表示的指针的大小。

检查此工作代码 codepade

int main()
{
    char arr[5][7][6];
    printf(" Sizeof(&arr)  : %lu and value &arr: %p\n", sizeof(&arr), &arr);
    printf(" Sizeof(arr)   : %lu and value arr : %p\n", sizeof(arr), arr);
    printf(" Sizeof(arr[0]): %lu and value a[0]: %p\n",sizeof(arr[0]), arr[0]);
    return 0;
}

它的输出:

Sizeof(&arr)  : 4 and value &arr: 0xbf4d9eda
Sizeof(arr)   : 210 and value arr : 0xbf4d9eda
Sizeof(arr[0]): 42 and value a[0]: 0xbf4d9eda
  • 这里 &arr 只是一个地址,系统地址是四个字节,这是完整的三维字符数组的地址。

  • arr 是三维数组的名称,和 sizeof 运算符给出的数组的总大小 210 = 5 * 7 * 6 * sizeof(char)

正如我在图中所示 arr 指向作为二维数组的第一个元素。因为 arr = (arr + 0)。现在用 * 取消引用运营商 (arr + 0) 给出地址的价值 *(arr + 0) = arr[0]

  • 注意 sizeof(arr[0]) 给 42 = 7 * 6 * sizeof(char)。并且这在概念上证明了一个三维数组,但是二维数组的数组。

因为上面在我的回答很多时候我写的像: “的大小 char[5][7][6] 是 5 * 7 * 6 * sizeof(char)“。 所以我在@下面添加一个有趣的代码codepade

int main(){
 printf(" Char         : %lu \n", sizeof(char));
 printf(" Char[5]      : %lu \n", sizeof(char[6]));
 printf(" Char[5][7]   : %lu \n", sizeof(char[7][6]));
 printf(" Char[5][7][6]: %lu \n", sizeof(char[5][7][6]));

 return 1;
}

输出:

 Char         : 1 
 Char[5]      : 6 
 Char[5][7]   : 42 
 Char[5][7][6]: 210 

5
2017-07-16 14:24



检查一下 图 - Grijesh Chauhan
@Alex这次使用每个typecase再次尝试你的代码: (char (*)[5][7][6])。例如: printf("%d\n", ((char (*)[5][7][6])(arr + 1) - ((char (*)[5][7][6])arr); 并告诉输出的原因。 - Grijesh Chauhan


好吧,如果我想拆分头发:首先,代码在整个地方调用未定义的行为 printf() 声明。两个指针的区别有类型 ptrdiff_t,为此,正确的转换说明符是 %td不是 %d

剩下的只是猜测。假设您的系统合理,并且 数字 指针值 &arr 它总是相同的,无论它转换成什么类型​​。

现在, (&arr + 1) - &arr 当然是1,根据指针算法的规则。 (两个指针之间的实际差异是 210 * sizeof(int) 字节,但这不是学校数学而是指针算术,这就是为什么结果以大小为单位给出的原因 sizeof(T),哪里 T 是指针的基本类型。)

然后 (char *)(&arr + 1) - (char *)&arr 投射指针 char *,以及以来的大小 char 是1,这将打印出差异 以字节为单位 你在这里有效地欺骗/滥用指针算法。

此外: printf("%d\n", (unsigned)(arr + 1) - (unsigned)arr) 正在减去两个类型的指针 int (*)[7][6]。那是什么 arr 衰败成。当然,7 * 6 = 42,所以尺寸之间的差异 arr + 1 和 arr 是42个元素。

p但是,它不是指向数组第一个元素的指针,而是指向数组本身的指针。其类型正确表示为 int (*)[5][6][7]。现在,如果您使用打印差异 那种类型, 但你不要让编译器通过欺骗指针来做分裂 unsigned那么你会得到的 5 * 6 * 7,这是210。


6
2017-07-16 14:02



贬低谁,发表评论。我没有看到任何理由进行downvote。
答案写得很差,并没有解释所涉及的类型。然后回答是以这样的方式写成,以便做出真实的陈述和炫耀,但不是教给那些不理解它们的人。另外,转换为 char * 减法不是欺骗或滥用指针算术;它完全由C标准定义。 - Eric Postpischil


(&arr + 1) - &arr

&arr 是由7个7个数组组成的5个数组的数组的地址 char。添加一个产生的地址 下一个 5个阵列的7个阵列的数组6 char 如果我们有一个这些对象的数组而不是一个对象,那将是。减去原始地址, &arr,产生两个地址之间的差异。根据C标准,此差异表示为两个地址之间的元素数,其中元素类型是指向的对象的类型。由于该类型是由5个7个数组组成的5个数组的数组 char,两个地址之间的距离是一个元素。换句话说,距离 &arr 至 (&arr + 1) 是  5个阵列的7个阵列的数组6 char

(char *)(&arr + 1) - (char *)&arr

&arr + 1 再次指向7个7个数组的5个数组的下一个数组的指针 char 将会。当它转换为 char *,结果是指向下一个数组的第一个字节的指针。 Similarlly, (char *)&arr 是指向第一个数组的第一个字节的指针。然后减去两个指针会产生元素之间的差异。因为这些指针是指针 char,差异是产生的数量 char。所以不同的是5个7个数组的5个数组的数组中的字节数 char,这是5•7•6 char,或210 char

(unsigned)(arr + 1) - (unsigned)arr

以来 arr 不用于 & (要么 sizeof 或其他特殊情况),它自动转换为5个阵列的5个阵列的数组6 char 指向第一个元素的指针。因此,它是指向7个6的数组的数组的指针 char。然后 arr + 1 是指向7个6的数组的下一个数组的指针 char。将此地址转换为 unsigned,结果,在您正在使用的C实现中,实际上是对象的内存地址。 (这很常见,但C标准无法保证,当地址为64位时肯定会中断 unsigned 是32位。)同样, (unsigned)arr 是第一个对象的地址。减去地址后,结果就是它们之间的距离(以字节为单位)。所以不同的是7个7的数组的数组中的字节数 char,这是7•6字节,或42字节。请注意这种情况下的关键区别: &arr 是一个指向由5个7个数组组成的5个数组的数组的指针 char但是 arr 是一个指向7个6的数组的数组的指针 char

(unsigned)(p + 1) - (unsigned)p

p 是一个指向由5个7个数组组成的5个数组的数组的指针 char。然后 p+1 是指向下一个数组的指针。转换为 unsigned 如上所述。减去产生差异的字节,所以它是5个7个数组的5个数组的数组的大小 char,所以它又是210字节。

作为旁白:

的类型 (&arr + 1) - &arr 是 ptrdiff_t 并应打印 %td

的类型 (char *)(&arr + 1) - (char *)&arr 是 ptrdiff_t 并应打印 %td

的类型 (unsigned)(arr + 1) - (unsigned)arr 是 unsigned int 并应打印 %u

的类型 (unsigned)(p + 1) - (unsigned)p 是 unsigned int 并应打印 %u


5
2017-07-16 14:19



你认为代码在运行时会导致不确定的行为吗? - Grijesh Chauhan
@GrijeshChauhan:添加,减少和转换为 char *在此代码中定义。转换为 unsigned 没有完全定义但在典型的C实现中表现良好,前提是结果地址在范围内 unsigned 类型。未定义使用不正确的格式说明符。 - Eric Postpischil
谢谢!!我现在知道了,我再次阅读了你的答案部分(我投了第二部分)。但我的评论投票限制越过了。谢谢 - Grijesh Chauhan