运营商new不会在Android上抛出bad_alloc

时间:2015-04-24 06:54:50

标签: android c++ android-ndk

我正在开发Android NDK上的C ++游戏(android-ndk-r9b)。如果我写这个:

class Test
{
    char c[1024*1024*1024];
};

//in main
try {

    Test* p;
    while (1) {
        p = new Test();
    }
} catch (bad_alloc) {
    cout << "bad_alloc\n";
}

它不会抛出。如果我试试这个:

void no_memory_by_new() {
    cout << "no_memory_by_new\n";
    throw bad_alloc();
}

//in main
set_new_handler(no_memory_by_new);
Test* p;
while (1) {
    p = new Test();
}

它也不起作用。最后,如果我试试这个:

//in main
set_new_handler(no_memory_by_new);
int* p;
while (1) {
    p = new int[1024*1024*1024];
}

然后调用no_memory_by_new。我真的很困惑。任何人都可以帮助我吗?

2 个答案:

答案 0 :(得分:3)

这是GNU libstdc ++的Android构建中的错误。如果您查看operator new implementation,如果_GLIBCXX_THROW_OR_ABORT返回malloc,您会看到它NULL。接下来,如果您查看_GLIBCXX_THROW_OR_ABORT的{​​{3}},只有__EXCEPTIONS定义时,您才会看到它抛出bad_alloc;否则,只需拨打abort。由于某种原因,__EXCEPTIONS宏未在GNU libstdc ++的Android版本中定义,因此它调用abort - 正如您在案例中看到的那样。

我已经使用Android NDK r10d和CrystaX NDK 10.1检查了这种行为 - 在两种情况下它都是相同的。我已经在CrystaX NDK中提交了definition来解决这个问题。要在Google的NDK中修复此问题,您还应该在ticket

中提交票证

UPD:似乎情况并非如此简单......进一步调查,我发现了更多细节,指出有些事情比我上面描述的要复杂得多。进一步观察;将在有严格结果时更新答案。

UPD2:经过深入调查后,我发现我以前的回答是完全错误的。实际上,在构建GNU libstdc ++时定义了__EXCEPTIONS,因此如果operator new返回bad_allocmalloc实际上会抛出NULL。问题实际上在你的代码中,但要弄清楚它有点棘手。见下面的解释。

TL; DR:operator new返回指向&#34;已分配&#34;的指针内存(因此从它的角度来看,没有理由抛出std::bad_alloc),但首次访问该内存会导致崩溃,因为实际上这些页面不可用。

更详细的解释:

以下是我用于测试的完整代码:

#include <new>
#include <stdio.h>
#include <string.h>

class Test
{
public:
    Test() {
        ::fprintf(stderr, "ctor start\n");
        //memset(c, 0, sizeof(c));
        ::fprintf(stderr, "ctor finish\n");
    }

private:
    char c[1024*1024*1024];
};

int main()
{
    try {
        while (1) {
            Test *p = new Test();
            if (!p)
                return 1;
        }
        return 1;
    } catch (std::bad_alloc) {
        return 0;
    }
}

如果您编译此测试并在设备上运行它,您将在某次迭代中得到std::bad_alloc(我在第三次迭代时得到它)。但是如果你在Test的构造函数中取消注释memset,应用程序将在第一次memset调用时崩溃。如果完全删除Test的构造函数,它也会崩溃 - 只是因为在这种情况下编译器将生成构造函数,它对所有成员进行零初始化 - 即与我们对memset相同。

这里的区别在于malloc(在operator new内部使用)返回指向&#34;已分配&#34;的指针。内存,但实际它没有分配;这个区域只是标记为&#34;将来需要分配,当应用程序实际引用它时#34;。出于性能原因,这就是Linux内核处理它的方式。下一步,当你(或编译器)用零填充数组时,应用程序实际访问那些页面,但不幸的是,系统中没有空闲内存,因此Linux内核调用OOM杀手,结果导致进程终止。 / p>

这不是特定于Android的。事实上,GNU / Linux系统也是如此;唯一的区别是系统可用的内存量(在Android上它远远低于在服务器上,显而易见的原因)。

答案 1 :(得分:0)

为了获得工作异常,您需要使用其他C ++标准库之一进行构建,而不是默认库。添加例如APP_STL := gnustl_staticjni/Application.mk。此外,您需要启用例外,将LOCAL_CPP_FEATURES += exceptions添加到Android.mk,或将APP_CPPFLAGS += -fexceptions添加到jni/Application.mk