运算符 new 不会在 Android 上抛出 bad_alloc
Operator new does not throw bad_alloc on Android
我正在 Android NDK (android-ndk-r9b) 上开发 C++ 游戏。如果我这样写:
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
。我真的很困惑。谁能帮帮我?
为了产生工作异常,您需要使用默认库以外的其他 C++ 标准库之一进行构建。添加例如APP_STL := gnustl_static
到 jni/Application.mk
。此外,您需要启用例外,将 LOCAL_CPP_FEATURES += exceptions
添加到 Android.mk
,或将 APP_CPPFLAGS += -fexceptions
添加到 jni/Application.mk
。
这是 Android GNU libstdc++ 构建中的错误。如果您查看 _GLIBCXX_THROW_OR_ABORT
的 operator new
implementation, you'll see it call _GLIBCXX_THROW_OR_ABORT
if malloc
return NULL
. Next, if you look on definition,您会看到它仅在定义了 __EXCEPTIONS
时才会抛出 bad_alloc;否则,它只是调用 abort
。由于某种原因,__EXCEPTIONS
宏未在 GNU libstdc++ 的 Android 版本中定义,因此它调用 abort
- 正是您在您的案例中看到的。
我已经用 Android NDK r10d 和 CrystaX NDK 10.1 检查了这种行为 - 在这两种情况下都是一样的。我已经提交 ticket to fix this in CrystaX NDK. For fixing that in Google's NDK, you should also file ticket in Google's NDK bug tracker
UPD:看来情况并没有那么简单...进一步调查后,我发现了更多细节,表明事情比我上面描述的要复杂一些。看得更远;当有严格的结果时将更新答案。
UPD2:经过深入调查,我发现我之前的回答是完全错误的。事实上,__EXCEPTIONS
是在构建 GNU libstdc++ 时定义的,所以 operator new
实际上会抛出 bad_alloc
if malloc
return NULL
。问题实际上出在您的代码中,但要弄清楚有点棘手。请参阅下面的解释。
TL;DR: operator new
return 指向 "allocated" 内存的指针(所以从它的角度来看,没有理由抛出 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
内部使用)return指向"allocated"内存的指针,但实际上没有分配;该区域仅标记为 "need to be allocated in future, when application will actually refer it"。出于性能原因,这就是 Linux 内核处理它的方式。在下一步中,当您(或编译器)用零填充数组时,应用程序实际上会访问这些页面,但不幸的是,系统中没有可用内存,因此 Linux 内核调用 OOM 杀手,结果终止进程。
这不是 Android 特有的。事实上,在 GNU/Linux 系统上也会发生同样的情况;唯一的区别是系统可用的内存量(在 Android 上它比在服务器上低得多,原因很明显)。
我正在 Android NDK (android-ndk-r9b) 上开发 C++ 游戏。如果我这样写:
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
。我真的很困惑。谁能帮帮我?
为了产生工作异常,您需要使用默认库以外的其他 C++ 标准库之一进行构建。添加例如APP_STL := gnustl_static
到 jni/Application.mk
。此外,您需要启用例外,将 LOCAL_CPP_FEATURES += exceptions
添加到 Android.mk
,或将 APP_CPPFLAGS += -fexceptions
添加到 jni/Application.mk
。
这是 Android GNU libstdc++ 构建中的错误。如果您查看 _GLIBCXX_THROW_OR_ABORT
的 operator new
implementation, you'll see it call _GLIBCXX_THROW_OR_ABORT
if malloc
return NULL
. Next, if you look on definition,您会看到它仅在定义了 __EXCEPTIONS
时才会抛出 bad_alloc;否则,它只是调用 abort
。由于某种原因,__EXCEPTIONS
宏未在 GNU libstdc++ 的 Android 版本中定义,因此它调用 abort
- 正是您在您的案例中看到的。
我已经用 Android NDK r10d 和 CrystaX NDK 10.1 检查了这种行为 - 在这两种情况下都是一样的。我已经提交 ticket to fix this in CrystaX NDK. For fixing that in Google's NDK, you should also file ticket in Google's NDK bug tracker
UPD:看来情况并没有那么简单...进一步调查后,我发现了更多细节,表明事情比我上面描述的要复杂一些。看得更远;当有严格的结果时将更新答案。
UPD2:经过深入调查,我发现我之前的回答是完全错误的。事实上,__EXCEPTIONS
是在构建 GNU libstdc++ 时定义的,所以 operator new
实际上会抛出 bad_alloc
if malloc
return NULL
。问题实际上出在您的代码中,但要弄清楚有点棘手。请参阅下面的解释。
TL;DR: operator new
return 指向 "allocated" 内存的指针(所以从它的角度来看,没有理由抛出 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
内部使用)return指向"allocated"内存的指针,但实际上没有分配;该区域仅标记为 "need to be allocated in future, when application will actually refer it"。出于性能原因,这就是 Linux 内核处理它的方式。在下一步中,当您(或编译器)用零填充数组时,应用程序实际上会访问这些页面,但不幸的是,系统中没有可用内存,因此 Linux 内核调用 OOM 杀手,结果终止进程。
这不是 Android 特有的。事实上,在 GNU/Linux 系统上也会发生同样的情况;唯一的区别是系统可用的内存量(在 Android 上它比在服务器上低得多,原因很明显)。