大型C ++阵列会导致iOS上的分段错误

时间:2018-12-14 15:05:33

标签: c++ ios segmentation-fault clang

我正在使用C ++构建iOS应用,但遇到大型阵列问题。问题是,如果数组达到一定大小,我将得到EXC_BAD_ACCESS(SIGSEGV)类型的子类型KERN_PROTECTION_FAILURE的异常,带有分段错误(11)终止信号。

有趣的是,无论将数组放置在堆栈上还是堆上,我都会遇到该异常。

将数组放入堆栈的代码如下:

class Model
{
public:
  Model() { };

private:
  static constexpr std::size_t VERTEX_COUNT =  25894;

  Vertex _vertices[VERTEX_COUNT] =
  {
    { { 46.629387f, 647.478271f,  58.987785f }, {  0.140482f, 0.716024f, 0.683795f }, false },
    { { 86.409439f, 639.203247f,  57.095085f }, {  0.273239f, 0.689217f, 0.671059f }, false },
    { { 94.825722f, 586.618164f,  91.772812f }, {  0.375726f, 0.404750f, 0.833671f }, false },
    { { 50.570183f, 586.068481f, 100.536209f }, { -0.003906f, 0.451161f, 0.892434f }, false },
    // 25894 array entries in total
  };

  // all the rest
}

用于填充数组的结构如下:

struct Vertex
{
  Vertex()
  {
  }

  Vertex(glm::vec3 coords, glm::vec3 norm, bool selected) :
    coordinates(coords),
    normal(norm),
    isSelected(selected)
  {
  }

  glm::vec3 coordinates;
  glm::vec3 normal;
  bool      isSelected;
};

实例化Model实例后,以上代码在iOS 11.4上崩溃。

现在,即使我更改了行,也会发生这种情况

Vertex _vertices[VERTEX_COUNT] =

to(在堆上分配内存)

Vertex* _vertices = new Vertex[VERTEX_COUNT]

std::unique_ptr<Vertex[]> _vertices = std::unique_ptr<Vertex[]>(new Vertex[VERTEX_COUNT]

或将整个数组定义移至Model的构造函数中。

到目前为止,使它起作用的唯一方法是更改​​

Vertex _vertices[VERTEX_COUNT] =

static constexpr Vertex _vertices[VERTEX_COUNT] =

,然后将相应的constexpr构造函数添加到Vertex结构中。但是,我需要能够在运行时编辑数组,因此不能将其声明为static constexpr

有人知道这里可能发生什么吗?

2 个答案:

答案 0 :(得分:2)

您应使用new创建数组,而不初始化其元素。当您通过初始化将数组创建到堆上时,编译器需要为栈准备足够的空间以为数组的多个对象调用ctor。

看下面的例子(它说明使用初始化创建动态数组很危险):

struct vertex {
    float x,y,z;
    vertex() {}
    vertex(double x,double y,double z){}
};

int main() {
    vertex* v = new vertex[3] {
            {1.43,2,3},
            {3,4.34,5},
            {3,4,5}
    };
}
        // main function in assembler code
        push    rbp
        mov     rbp, rsp
        sub     rsp, 48   // <--- stack pointer is decresed
        mov     eax, 36
        mov     edi, eax
        call    operator new[](unsigned long)
        mov     rdi, rax
        // call ctors for vertex

asm代码中最重要的一行是sub rsp,48。现在,我们将数组的大小更改为具有6个顶点:

vertex* v = new vertex[6] {
        {1.43,2,3},
        // 4 lines here
        {3,4,5}

现在编译器生成sub rsp, 80,如您所见,从堆栈指针减去的值增加了。

顶点数组越大,从堆栈获取的空间就越大。堆栈是有限的。也许这就是即使将数组分配到堆上,应用程序也会崩溃的原因。堆栈的所有内存都用于初始化数组的顶点。


我在https://godbolt.org/上编译了这段代码,选择了clang 6.0,没有进行任何优化。 (启用的优化在输出的asm代码中发生了很大变化)。当然,其他编译器可能会生成不同的代码,而不是sub rsp,BIG_VALUE,它们可以为每个顶点的ctor分别占据堆栈空间。

答案 1 :(得分:1)

在嵌入式系统中,经验法则是将常量和大量数据声明为static

static Vertex database[] = {/*...*/};

如果您的数据是只读的,请使用const关键字:

static const Vertex database[] = {/*...*/};

检查编译器和链接器文档,以查看是否可以为数据创建内存段以及如何将数据库分配给该内存段。

您的编译器可能会对上述技术施加限制,例如只能使用structVertex不能具有任何虚拟方法。最坏的情况是,您必须使用2d数组:

static const double Vertices[MAXIMUM_ROWS][3] = {/*...*/};

通过使用static const,编译器可以将数据放入只读数据段中。这样可以将数据放入只读存储设备中,例如Flash或ROM(是的,我知道Flash可以被写入/编程,但在大多数情况下,它被视为ROM)。