【问题标题】:How to work around a very large 2d array in C++如何在 C++ 中处理一个非常大的二维数组
【发布时间】:2010-09-08 20:49:36
【问题描述】:

我需要创建一个大小为 800x800 的 2D int 数组。但是这样做会造成堆栈溢出(哈哈)。

我是 C++ 新手,所以我应该做一些向量向量之类的事情吗?并且只是将二维数组封装到一个类中?

具体来说,这个数组是我在图形程序中的 zbuffer。我需要为屏幕上的每个像素存储一个 z 值(因此是 800x800 的大尺寸)。

谢谢!

【问题讨论】:

    标签: c++ arrays graphics 2d zbuffer


    【解决方案1】:

    您需要大约 2.5 兆,所以只使用堆就可以了。除非您需要调整它的大小,否则您不需要矢量。有关使用“2D”堆数组的示例,请参阅 C++ FAQ Lite

    int *array = new int[800*800];
    

    (完成后别忘了delete[]。)

    【讨论】:

    • 您不必“新建”内存。据推测,zbuffer 将需要跨函数存在。声明它是全局的(或者如果你已经抽象到某个类,我想在类中)。你可以做类似'int zBuffer[WIDTH*HEIGHT];'假设宽度和高度不变。
    • 当然,但我不建议使用全局变量,因为这通常不是一个好的解决方案——它在范围和生命周期方面并没有给您太大的灵活性。它可能对这个提问者很有效,所以请继续留下你自己的答案。
    【解决方案2】:

    你可以做一个向量的向量,但这会产生一些开销。对于 z 缓冲区,更典型的方法是创建一个大小为 800*800=640000 的数组。

    const int width = 800;
    const int height = 800;
    unsigned int* z_buffer = new unsigned int[width*height];
    

    然后按如下方式访问像素:

    unsigned int z = z_buffer[y*width+x];
    

    【讨论】:

      【解决方案3】:

      我可能会创建一个 800*800 的单维数组。使用这样的单个分配可能比分配 800 个单独的向量更有效。

      int *ary=new int[800*800];
      

      然后,可能将其封装在一个类似于 2D 数组的类中。

      class _2DArray
      {
        public:
        int *operator[](const size_t &idx)
        {
          return &ary[idx*800];
        }
        const int *operator[](const size_t &idx) const
        {
          return &ary[idx*800];
        }
      };
      

      这里显示的抽象有很多漏洞,例如,如果您访问超过“行”的末尾会发生什么? 《Effective C++》这本书对用 C++ 编写好的多维数组进行了很好的讨论。

      【讨论】:

      • operator[] 返回一个 int 指针?也许这就是你的意思: int* operator[](size_t y) { return &ary[ywidth];然后你可以写: my2DArray[y][x] 虽然更好的做法是使用 operator(): int& operator[](int x, int y) { return ary[ywidth+x]; }
      • 好点,但是我不同意重载函数调用运算符必然是更好的做法。如果你包装一个数组,你应该让它看起来像一个数组。
      • C++ FAQ 有一些使用 operator() 的理由。 parashift.com/c++-faq-lite/operator-overloading.html#faq-13.11
      • 单暗阵列将比多暗阵列节省很少或根本没有空间
      • 该常见问题解答是否意味着您不应该使用数组语法或为什么 C++ 标准很烂的一系列原因?我觉得这两种方式都没有说服力。
      【解决方案4】:

      有类似 C 的做法:

      const int xwidth = 800;
      const int ywidth = 800;
      int* array = (int*) new int[xwidth * ywidth];
      // Check array is not NULL here and handle the allocation error if it is
      // Then do stuff with the array, such as zero initialize it
      for(int x = 0; x < xwidth; ++x)
      {
          for(int y = 0; y < ywidth; ++y)
          {
               array[y * xwidth + x] = 0;
          }
      }
      // Just use array[y * xwidth + x] when you want to access your class.
      
      // When you're done with it, free the memory you allocated with
      delete[] array;
      

      您可以使用简单的 get 和 set 方法将 y * xwidth + x 封装在一个类中(如果您想开始学习更高级的 C++,可能会重载 [] 运算符)。如果你刚开始使用 C++ 并且没有开始为 n 维数组创建可重用的完全类模板,我建议你慢慢来,这只会让你在开始时感到困惑。

      一旦您开始从事图形工作,您可能会发现额外调用类的开销可能会减慢您的代码速度。但是,在您的应用程序不够快之前不要担心这一点,并且您可以对其进行分析以显示时间浪费在哪里,而不是在一开始就使其更难使用并可能带来不必要的复杂性。

      我发现 C++ lite FAQ 非常适合此类信息。特别是您的问题由以下人员回答:

      http://www.parashift.com/c++-faq-lite/freestore-mgmt.html#faq-16.16

      【讨论】:

      • 如果将新数组转换为“指向 800 个整数数组的指针”(int[800] buff; // ??),则可以避免 [xw +y] 拼凑
      • 这段代码有个bug,当宽高不相等时会失败。 array[xxwidth + y] 应该是 array[yxwidth + x]。
      【解决方案5】:

      好吧,在 Niall Ryan 开始的基础上,如果性能是一个问题,您可以通过优化数学并将其封装到一个类中来更进一步。

      所以我们将从一些数学开始。回想一下,800 可以写成 2 的幂:

      800 = 512 + 256 + 32 = 2^5 + 2^8 + 2^9
      

      所以我们可以将寻址函数写成:

      int index = y << 9 + y << 8 + y << 5 + x;
      

      因此,如果我们将所有内容封装到一个不错的类中,我们会得到:

      class ZBuffer
      {
      public:
          const int width = 800;
          const int height = 800;
      
          ZBuffer()
          {
              for(unsigned int i = 0, *pBuff = zbuff; i < width * height; i++, pBuff++)
                  *pBuff = 0;
          }
      
          inline unsigned int getZAt(unsigned int x, unsigned int y)
          {
              return *(zbuff + y << 9 + y << 8 + y << 5 + x);
          }
      
          inline unsigned int setZAt(unsigned int x, unsigned int y, unsigned int z)
          {
              *(zbuff + y << 9 + y << 8 + y << 5 + x) = z;
          }
      private:
          unsigned int zbuff[width * height];
      };
      

      【讨论】:

      • 你能说“过早的优化”吗?为什么不把这些花哨的技巧留给编译器呢?
      • 2 个额外的加法和 3 次移位真的比仅仅做一个乘法更快吗?如果它真的更快,编译器不会注意到乘以 const 并自己做吗?另外,如果你改变了 const,你必须记得重写 shift/add 代码。
      • 我认为无论如何创建缓冲区行指针查找表会更快。您可以根据缓冲区大小在类构造函数中创建它。
      【解决方案6】:

      到目前为止,每篇文章都将内存管理留给程序员。这可以而且应该避免。 ReaperUnreal 与我所做的非常接近,除了我会使用向量而不是数组,还会制作维度模板参数并更改访问函数——哦,只是 IMNSHO 清理一下:

      template <class T, size_t W, size_t H>
      class Array2D
      {
      public:
          const int width = W;
          const int height = H;
          typedef typename T type;
      
          Array2D()
              : buffer(width*height)
          {
          }
      
          inline type& at(unsigned int x, unsigned int y)
          {
              return buffer[y*width + x];
          }
      
          inline const type& at(unsigned int x, unsigned int y) const
          {
              return buffer[y*width + x];
          }
      
      private:
          std::vector<T> buffer;
      };
      

      现在你可以在堆栈上分配这个二维数组了:

      void foo()
      {
          Array2D<int, 800, 800> zbuffer;
      
          // Do something with zbuffer...
      }
      

      我希望这会有所帮助!

      编辑:从 Array2D::buffer 中删除了数组规范。感谢 Andreas 捕捉到这一点!

      【讨论】:

      • 我完全不同意对于某些环境的内存管理。根据您的分配模式,默认分配器可能会严重碎片化内存,从而导致难以诊断的性能。我也不明白需要把一个数组变成一个类。混淆恕我直言。
      • 对于某些环境...好的。但是将 Array2D::buffer 更改为使用自定义分配器很容易——好吧,如果您知道默认分配器很糟糕并且有更好的分配器,那么您可能还知道如何添加自定义分配器。跨度>
      • 至于使用类...好吧,为了避免使用堆栈,避免使用手动内存管理,您必须使用类。这里的所有都是它的。如果您愿意手动管理内存,请使用“new”来分配二维数组。
      • 这是如何工作的?尝试编译时出现“只能在类中初始化静态 const 整数数据成员”错误。
      【解决方案7】:

      您可以做的一件事是使用 VC 更改堆栈大小(如果您真的想要堆栈上的数组),这样做的标志是 [/F](http://msdn.microsoft.com/en-us/library/tdkhxaks(VS.80).aspx)

      但是您可能想要的解决方案是将内存放在堆中而不是堆栈中,因为您应该使用 vectorvectors

      以下行声明了一个包含 800 个元素的 vector,每个元素是一个包含 800 个 ints 的 vector,这样您就不用手动管理内存了。

      std::vector<std::vector<int> > arr(800, std::vector<int>(800));
      

      注意两个右尖括号 (&gt; &gt;) 之间的空格,这是为了消除右移运算符的歧义(C++0x 中不再需要)。

      【讨论】:

        【解决方案8】:

        不过,Kevin 的例子很好:

        std::vector<T> buffer[width * height];
        

        应该是

        std::vector<T> buffer;
        

        稍微扩展一下,您当然可以添加运算符重载而不是 at() 函数:

        const T &operator()(int x, int y) const
        {
          return buffer[y * width + x];
        }
        

        T &operator()(int x, int y)
        {
          return buffer[y * width + x];
        }
        

        例子:

        int main()
        {
          Array2D<int, 800, 800> a;
          a(10, 10) = 50;
          std::cout << "A(10, 10)=" << a(10, 10) << std::endl;
          return 0;
        }
        

        【讨论】:

          【解决方案9】:

          或者您可以尝试以下方法:

          boost::shared_array<int> zbuffer(new int[width*height]);
          

          你应该仍然可以这样做:

          ++zbuffer[0];
          

          无需再为管理内存而烦恼,无需处理自定义类,而且很容易扔掉。

          【讨论】:

            【解决方案10】:

            如果您只需要一个实例,您可以在静态存储上分配数组(在文件范围内,或在函数范围内添加static 限定符)。

            int array[800][800];
            
            void fn()
            {
                static int array[800][800];
            }
            

            这样它就不会入栈,你也不必处理动态内存。

            【讨论】:

              猜你喜欢
              • 2020-04-23
              • 2014-08-20
              • 2013-03-26
              • 1970-01-01
              • 2021-10-19
              • 2018-03-12
              • 2014-08-20
              • 1970-01-01
              • 2010-12-27
              相关资源
              最近更新 更多