最近我在ideone.com(gcc-4.3.4)上运行了以下代码
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <new>
using namespace std;
void* operator new( size_t size ) throw(std::bad_alloc)
{
void* ptr = malloc( 2 * 1024 * 1024 * 1024);
printf( "%p\n", ptr );
return ptr;
}
void operator delete( void* ptr )
{
free( ptr );
}
int main()
{
char* ptr = new char;
if( ptr == 0 ) {
printf( "unreachable\n" );
}
delete ptr;
}
得到了这个输出:
(nil)
unreachable
虽然new
永远不会返回空指针,因此调用者可以依赖它,编译器可能已经消除了ptr == 0
检查并将相关代码视为无法访问。
为什么编译器不会删除该代码?它只是一个错过的优化还是有其他原因?
答案 0 :(得分:7)
我认为这很简单,你有两个根本不同的东西混淆不清:
malloc()可以返回任何内容,尤其是零。
标准要求全局C ++分配函数void * operator new(size_t) throw(std::bad_alloc)
返回指向所需存储量的指针(+适当对齐),否则通过异常退出。
如果您想要替换全局分配功能,则您的责任提供符合标准规则的替代品。最简单的版本如下:
void * operator new(size_t n) throw(std::bad_alloc) {
void * const p = std::malloc(n);
if (p == NULL) throw std::bad_alloc();
return p;
}
任何严肃的实现实际上都应该包含一个循环来调用已注册的new-handler直到分配成功,并且只有在没有新的处理程序时才抛出。
你写的程序很简单。
Digression:为什么这个new
定义了这种方式?当您说T * p = ::new T();
时,请考虑标准分配顺序。它相当于:
void * addr = ::operator new(sizeof(T)); // allocation
T * p = ::new (addr) T(); // construction
如果第二行抛出(即构造失败),则使用相应的释放函数释放存储器。但是,如果第一次调用失败,那么执行必须永远不会到达第二行!实现这一目标的唯一方法是通过异常退出。 (分配函数的无抛出版本仅供手动使用,其中用户代码可以在继续构造之前检查分配器的结果。)
答案 1 :(得分:6)
C ++ 11在这个问题上很清楚:
void* operator new(std::size_t size);
:... 3必需行为:将非空指针返回到适当对齐的存储(3.7.4),否则抛出bad_alloc
异常。 此要求与此功能的替换版本绑定。
您点击未定义的行为。
[编辑] 现在,为什么会妨碍优化呢?编译器供应商倾向于花时间梦想对常用的代码模式进行优化。对于他们来说,优化更快的未定义行为通常没什么好处。 (某些UB可能在该特定编译器上定义良好,仍然可以进行优化,但上面的示例可能不会。)
答案 2 :(得分:5)
我认为你期待的优化器太多了。当优化器获得此代码时,它认为new char
只是另一个函数调用,其返回值存储在堆栈中。所以它没有看到if
条件值得特别对待。
这可能是因为您覆盖了operator new
这一事实,并且超出了优化工具的薪资等级而看到了那里,看到您调用了malloc
,可以返回NULL
,以及确定此重写版本不会返回NULL
。 malloc
看起来像Just Another Function Call。谁知道?您也可以链接自己的版本。
还有一些被覆盖的运算符更改其在C ++中的行为的其他示例:operator &&
,operator ||
和operator ,
。当不被覆盖时,每个都有一个特殊的行为,但在被覆盖时表现得像标准运算符。例如,如果左侧评估为operator &&
,则false
甚至根本不会计算其右侧。但是,如果被覆盖,则operator &&
的双方在传递给operator &&
之前计算;短路功能完全消失。 (这样做是为了支持使用运算符重载来定义C ++中的迷你语言;有关此示例,请参阅Boost Spirit库。)
答案 3 :(得分:3)
编译器为什么要这样做?
使用new
的不透明实现,无法知道实现是否正确。你的是非标准的,所以你很幸运,它毕竟检查过。
答案 4 :(得分:2)
有多个operator new
;见here。并且您没有宣布您的那个可能性抛出异常。所以编译器不应该推断它永远不会返回空指针。
我不太了解最新的C ++ 11标准,但我想它只是标准定义的operator new
(一个抛出异常),它应该返回一个非零指针,没有任何用户定义的。
在当前的GCC主干中,文件libstdc++-v3/libsupc++/new
似乎没有任何特定的属性告诉GCC nil永远不会返回...即使我相信它是未定义的行为,因为抛出新的nil
答案 5 :(得分:0)
虽然new应该永远不会返回空指针
它不应该在正常操作中。但是,当有人插入记忆,或者它只是死亡,或者它刚刚满了时,一个疯狂的情况呢?
为什么编译器不会删除该代码?这只是一个错过了 优化还是有其他原因吗?
因为新的可能会失败。如果你使用no-throw版本,它可以返回NULL(或c ++ 11中的nulptr)。
答案 6 :(得分:0)
我检查了使用g ++ -O3 -S生成的程序集,标准new,gcc(4.4.5)不会删除if(ptr == 0)
。
似乎gcc没有编译器标志或函数属性来优化NULL检查。
所以看来gcc目前不支持这种优化。