【问题标题】:Why does int*[] decay into int** but not int[][]?为什么 int*[] 衰减为 int** 而不是 int[][]?
【发布时间】:2013-01-06 15:21:53
【问题描述】:

我试图了解类型衰减的本质。例如,我们都知道数组在特定上下文中衰减为指针。我的尝试是了解int[] 如何等同于int*,但二维数组如何与预期的指针类型不对应。这是一个测试用例:

std::is_same<int*, std::decay<int[]>::type>::value; // true

这会按预期返回 true,但不会:

std::is_same<int**, std::decay<int[][1]>::type>::value; // false

为什么这不是真的?我终于找到了让它返回 true 的方法,那就是将第一个维度设为指针:

std::is_same<int**, std::decay<int*[]>::type>::value; // true

并且该断言适用于任何具有指针但最后一个是数组的类型。例如 (int***[] == int****; // true)。

我能解释一下为什么会这样吗?为什么数组类型与预期的指针类型不对应?

【问题讨论】:

    标签: c++


    【解决方案1】:

    为什么int*[] 衰减为int** 而不是int[][]

    因为用它做指针运算是不可能的。

    例如,int p[5][4] 表示(int 的长度为 4 的数组)的数组。不涉及指针,它只是一个大小为5*4*sizeof(int) 的连续内存块。当您要求特定元素时,例如int a = p[i][j],编译器真的是这样做的:

    char *tmp = (char *)p           // Work in units of bytes (char)
              + i * sizeof(int[4])  // Offset for outer dimension (int[4] is a type)
              + j * sizeof(int);    // Offset for inner dimension
    int a = *(int *)tmp;            // Back to the contained type, and dereference
    

    显然,它只能这样做,因为它知道“内部”维度的大小。投射到int (*)[4] 会保留此信息;它是一个指向(int 的长度为 4 的数组)的指针。但是,int ** 没有;它只是一个指向(指向int)的指针。

    有关此问题的另一种看法,请参阅 C 常见问题解答的以下部分:

    (这都是针对 C 的,但这种行为在 C++ 中基本不变。)

    【讨论】:

    • 另一种查看方式:int[M][N]int** 需要两次转换(不允许),而不是一次转换(允许)。第一次转换需要将int[M][N] 转换为指向数组第一个元素的指针。 [first]元素的类型是int[N],所以int[M][N]首先转换成int(*)[N],然后需要转换成int**,需要inner数组int[N]的第一个元素转换成int*
    【解决方案2】:

    C 并不是真正“设计”为一门语言;取而代之的是,根据需要添加功能,努力不破坏早期的代码。在开发 C 语言的时代,这种进化方法是一件好事,因为这意味着在大多数情况下,开发人员可以在语言可能需要做的所有事情都完成之前获得早期改进的好处。不幸的是,数组和指针处理的演变方式导致了各种规则,回想起来,这些规则很不幸。

    在今天的 C 语言中,有一个相当丰富的类型系统,变量有明确定义的类型,但事情并非总是如此。声明char arr[8];将在当前范围内分配 8 个字节,并使 arr 指向其中的第一个。编译器不会知道arr 表示一个数组——它会像任何其他char* 一样表示一个字符指针。据我了解,如果有人声明 char arr1[8], arr2[8];,那么声明 arr1 = arr2; 将是完全合法的,在概念上与 char *st1 = "foo, *st2 = "bar"; st1 = st2; 有点等价,但几乎总是代表一个错误。

    数组分解为指针的规则源于数组和指针实际上是同一事物的时代。从那时起,数组已经被认为是一种独特的类型,但是这种语言需要在本质上与它们不兼容的日子保持兼容。在制定规则时,应该如何处理二维数组的问题不是问题,因为没有这样的东西。可以执行char foo[20]; char *bar[4]; int i; for (i=0; i&lt;4; i++) bar[i] = foo + (i*5); 之类的操作,然后以与现在使用二维数组相同的方式使用bar[x][y],但编译器不会以这种方式查看事物——它只是将bar 视为指针到一个指针。如果想让 foo[1] 指向与 foo[2] 完全不同的地方,那么完全可以合法地这样做。

    当两个二维数组被添加到 C 中时,没有必要保持与早期声明二维数组的代码的兼容性,因为没有。虽然可以指定char bar[4][5]; 将生成与使用foo[20] 显示的代码等效的代码,在这种情况下char[][] 可以用作char**,但人们认为就像分配数组变量在 99% 的情况下都是错误的,如果这是合法的,那么数组行的重新分配也是如此。因此,C 中的数组被识别为不同的类型,它们有自己的规则,虽然有点奇怪,但它们就是这样。

    【讨论】:

    • 这很有趣,但是您知道其中的任何参考吗? (这是一个真正的问题,我真的很想了解这方面的历史。)
    • 不幸的是,我没有任何单一的参考,尽管在其他 SO 问题中已经讨论了历史的各个部分。
    • 至少有几个 SO 线程来支持该声明?不错的帖子。
    【解决方案3】:

    因为int[M][N]int** 是不兼容的类型。

    但是,int[M][N] 可以衰减为 int (*)[N] 类型。所以如下:

    std::is_same<int(*)[1], std::decay<int[1][1]>::type>::value;
    

    应该给你true

    【讨论】:

    • Nawaz 的回答很简单,很好!
    【解决方案4】:

    二维数组不存储为指向指针的指针,而是存储为连续的内存块。

    声明为int[y][x] 类型的对象是大小为sizeof(int) * x * y 的块,而int ** 类型的对象是指向int* 的指针

    【讨论】:

    • 准确地说,“声明为int[y][x]类型的对象大小为sizeof(int) * x * y的块”。
    猜你喜欢
    • 2015-03-08
    • 2011-09-07
    • 2016-02-26
    • 2011-07-01
    • 2010-09-11
    • 2019-02-08
    • 2020-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多