【问题标题】:Large C++ array causes segmentation fault on iOS大型 C++ 数组导致 iOS 上的分段错误
【发布时间】:2018-12-14 15:05:32
【问题描述】:

我正在使用 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;
};

一旦模型实例被实例化,上面的代码就会在 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。

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

【问题讨论】:

  • 听起来您正在创建多个Model 实例,因此很快就会耗尽内存。 static _vertices 成员做到了,所以只有 1 个大数组而不是 N
  • 把constexpr去掉,在类外初始化不就行了吗?
  • @AndyG:即使我只创建Model 的单个实例,我也遇到了问题。一旦创建了这个实例,问题就来了。
  • @TedLyngmo:你的意思是让数组只是静态的吗?
  • 是的,我就是这么想的。该数组有多大(以字节为单位)? 621456-828608字节什么的?你能做new char[828608];或`char a[828608];没有问题?

标签: c++ ios segmentation-fault clang


【解决方案1】:

您应该通过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 分部分占用堆栈空间。

【讨论】:

  • 这对我来说很有意义,并且可以解释为什么我为堆栈或堆上的数组分配内存实际上并不重要。但是我还没有理解如何在初始化数组时防止堆栈被顶点结构弄乱。如下修改代码似乎还是一样的效果: vertex* v = new vertex[6]; v[0] = 顶点(1.43, 2, 3); v[1] = 顶点(3, 4.34, 5); v[2] = 顶点(3, 4, 5); ...您对如何更改代码以防止该问题有任何意见吗?
  • 对我来说最简单的方法是将所有双精度和布尔值移动到数据段中,因此您定义了两个全局数组double valD[] = {1,2,3}; bool valB[] = {true, false, true};。编译器将两个数组的所有数据都放入数据段——它是安全的。然后,您通过new 在堆上分配数组,并在一个 for 循环中为顶点调用 ctor,并为数组 for (int i = 0; i &lt; 25K; ++i){ a[i] = Vertex(valD[i],valD[i+1],..valB[i])} 的所有元素提供参数。
【解决方案2】:

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

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

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

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

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

您的编译器可能会对上述技术施加限制,例如只能使用struct 或Vertex 不能有任何虚拟方法。最坏的情况,你必须使用二维数组:

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-02-25
    • 1970-01-01
    • 2019-03-24
    • 1970-01-01
    • 2010-12-18
    • 2018-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多