【问题标题】:What is the type of a pointer to a 2D array?指向二维数组的指针的类型是什么?
【发布时间】:2018-01-06 15:54:27
【问题描述】:

我知道以下不正确:

int arr[2][3] = {}; //some array initialization here
int** ptr;
ptr = arr;

但我很惊讶以下几行确实有效

int arr[2][3] = {}; //some array initialization here
auto ptr = arr;
int another_arr[2][3] = {}; //some array initialization here
ptr = another_arr;

谁能解释第二个代码块中分配给 ptr 的类型是什么,以及下面发生了什么?

【问题讨论】:

    标签: c++ arrays c++11 pointers multidimensional-array


    【解决方案1】:

    好吧,当几乎在任何地方使用时,数组都会衰减为指针。所以很自然地,你的代码 sn-p 也会发生衰减。

    但只有“最外层”的数组维度会衰减为指针。由于数组是行优先的,因此您最终会以 int (*)[3] 作为指针类型,它是指向一维数组的指针,而不是二维数组。它指向第一个“行”。

    如果您希望 ptr 的推导成为 指向数组的指针,则使用 address-of 运算符:

    auto ptr = &arr;
    

    现在ptrint(*)[2][3]

    【讨论】:

    • 对不起,如果这是一个愚蠢的问题,但是如果我不使用关键字auto,我应该如何在开头声明ptr的类型?谢谢。
    • @R.Xin - 使用稍微复杂的语法int (* ptr )[3] = arr; 如果他们需要经常重复这样的声明,大多数人会 typedef。
    • @R.Xin 如果你想使用 StoryTeller 建议的 typedef,我推荐typedef int MyArr[3]; MyArr* ptr;
    • @user2079303 - 关于隐藏指针语义的弊端的重要观点!
    • @StoryTeller 我修正了评论,谢谢。数组大小与类型分离的事实对我来说是反直觉的。我认为using MyArr = int[3] 会更好。
    【解决方案2】:

    auto ptr = arr;
    

    arr 以正常方式衰减为指向其第一个元素的指针;相当于

    auto ptr = &arr[0];
    

    由于arr[0] 是三个ints 的数组,因此ptr 成为int (*)[3] - 指向int[3] 的指针。

    another_arr 以完全相同的方式衰减,所以在

    ptr = another_arr;
    

    赋值的两边都有int (*)[3] 类型,您可以将T* 分配给T* 用于任何类型T

    指向arr 的指针本身的类型为int(*)[2][3]

    如果你想要一个指向数组的指针而不是指向数组第一个元素的指针,你需要使用&:

    auto ptr = &arr; 
    

    【讨论】:

      【解决方案3】:

      首先,让我们看看为什么不能将int arr[2][3] 分配给int **。为了更易于可视化,我们将使用序列初始化您的数组,并考虑它在内存中的样子:

      int arr[2][3] = {{1,2,3},{4,5,6}};
      

      在内存中,数组数据存储为单个块,就像一个常规的一维数组:

      arr: [ 1, 2, 3, 4, 5, 6 ]
      

      变量arr 包含该块的开始地址,并且根据它的类型(int[2][3]),编译器知道将像arr[1][0] 这样的索引解释为“取位于位置(1 *2 + 0) 在数组中”。

      但是,对于指针指针 (int**),指针指针应该包含单个内存地址或内存地址数组,并且 this/these address(es) 指向(an) 其他单个 int 值或 int 数组。假设我们将数组arr 复制到int **ptrptr 中。在记忆中,它看起来像这样:

      ptrptr:     [0x203F0B20, 0x203F17D4]
      0x203F0B20: [ 1, 2, 3 ]
      0x203F17D4: [ 4, 5, 6 ]
      

      因此,除了实际的int 数据之外,还必须为数组的每一行存储一个额外的指针。不是将两个索引转换为单个数组查找,而是必须通过进行第一次数组查找(“获取 ptrptr 中的第二个值以获取 int*”),然后进行另一个数组查找(“获取之前获得的 int*") 所持有的地址处的数组。

      这是一个说明这一点的程序:

      #include <iostream>
      
      int main()
      {
          int arr[2][3] = {{1,2,3},{4,5,6}};
      
          std::cout << "Memory addresses for int arr[2][3]:" << std::endl;
          for (int i=0; i<2; i++)
          {
              for (int j=0; j<3; j++)
              {
                  std::cout << reinterpret_cast<void*>(&arr[i][j]) << ": " << arr[i][j] << std::endl;
              }
          }
      
          std::cout << std::endl << "Memory addresses for int **ptrptr:" << std::endl;
          int **ptrptr = new int*[2];
          for (int i=0; i<2; i++)
          {
              ptrptr[i] = new int[3];
              for (int j=0; j<3; j++)
              {
                  ptrptr[i][j] = arr[i][j];
                  std::cout << reinterpret_cast<void*>(&ptrptr[i][j]) << ": " << ptrptr[i][j] << std::endl;
              }
          }
      
          // Cleanup
          for (int i=0; i<2; i++)
          {
              delete[] ptrptr[i];
              ptrptr[i] = nullptr;
          }
          delete[] ptrptr;
          ptrptr = nullptr;
      
          return 0;
      }
      

      输出:

      Memory addresses for int arr[2][3]:
      0x7ecd3ccc0260: 1
      0x7ecd3ccc0264: 2
      0x7ecd3ccc0268: 3
      0x7ecd3ccc026c: 4
      0x7ecd3ccc0270: 5
      0x7ecd3ccc0274: 6
      
      Memory addresses for int **ptrptr:
      0x38a1a70: 1
      0x38a1a74: 2
      0x38a1a78: 3
      0x38a1a90: 4
      0x38a1a94: 5
      0x38a1a98: 6
      

      请注意,arr 的内存地址总是增加 4 个字节,但对于 ptrptr,值 3 和 4 之间有 24 个字节的跳跃。

      一个简单的赋值不能创建类型int **所需的指针到指针结构,这就是为什么在上面的程序中循环是必要的。它可以做的最好的事情是将int[2][3]类型衰减为指向该数组行的指针,即int (*)[3]。这就是您的auto ptr = arr; 的最终结果。

      【讨论】:

      • 啊,我现在才看到你已经展示了arr内存的初始化,很高兴看到这个深入的解释:-)
      【解决方案4】:

      [...]的类型是什么

      您是否已经尝试让编译器告诉您表达式的类型?

      int main()
      {
          int arr[2][3] = {{0,1,2}, {3,4,5}};  // <-- direct complete initialized here
      
          auto ptr = arr;                     // <-- address assignment only
      
          cout << "arr: " << typeid(arr).name() << endl;
          cout << "ptr: " << typeid(ptr).name() << endl;
          return 0;
      }
      

      我不得不承认输出

      arr: A2_A3_i
      ptr: PA3_i
      

      乍一看似乎不太可读(与其他一些语言相比),但如果有疑问,它可能会有所帮助。它非常紧凑,但很快就会习惯。编码依赖于编译器,如果你使用 gcc,你可以阅读Chapter 29. Demangling 来了解如何。

      编辑:

      对一些 simple_cpp_name 函数进行一些实验,比如这个基本的 hack

      #include <typeinfo>
      #include <cxxabi.h>
      #include <stdlib.h>
      #include <string>
      
      std::string simple_cpp_name(const std::type_info& ti)
      {
          /// simplified code extracted from "Chapter 29. Demangling"
          /// https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_demangling.html
          char* realname = abi::__cxa_demangle(ti.name(), 0, 0, 0);
          std::string name = realname;
          free(realname);
          return name;
      }
      

      将告诉您auto &amp;rfa = arr; 使rfa 具有与arr 相同的类型,即int [2][3]

      【讨论】:

      • 是的,我确实尝试过,但我无法理解运行时特定的代码。
      • @R.Xin 毕竟,除了pointed out by StorryTeller 的原始声明之外,auto ptr 处于间接级别这一事实似乎很容易看到。
      猜你喜欢
      • 2014-01-26
      • 1970-01-01
      • 1970-01-01
      • 2017-04-22
      • 2012-01-26
      相关资源
      最近更新 更多