为什么重载的 `operator new` 没有被调用?

4
我在VS2005中运行了以下代码:
#include <iostream>
#include <string>
#include <new>
#include <stdlib.h>

int flag = 0;

void* my_alloc(std::size_t size)
{
    flag = 1;
    return malloc(size);
}

void* operator new(std::size_t size) { return my_alloc(size); }
void operator delete(void* ptr) { free(ptr); }
void* operator new[](std::size_t size) { return my_alloc(size); }
void operator delete[](void* ptr) { free(ptr); }

int main()
{
    std::string str;
    std::getline(std::cin, str);
    std::cout << str;
    return flag;
}

我输入了一个足够长的字符串(长度超过小字符串优化缓冲区):

0123456789012345678901234567890123456789012345678901234567890123456789

在调试编译中,该过程返回1,而在发布配置中,该过程返回0,这意味着 new 操作符没有被调用!我可以通过设置断点、写入输出/调试输出等方式来验证这一点......

为什么会这样,这是符合标准的行为吗?


1
你有没有尝试通过调试和逐步执行代码来解决问题,而不是仅仅依赖输出结果? - Kolky
1
@Kolky:是的。例如,我在operator new中设置了一个全局标志,并从主函数中返回它——这是一个完全没有输出的解决方案。 - Yakov Galka
1
GCC <= 4.6.2存在一个有趣的bug,在某些编译器设置下,string的分配器会 使用替换后的分配函数。所以这并非完全没有听说过。为了好玩,尝试使用另一种数据结构,比如vector。 - Kerrek SB
1
@KerrekSB:有趣。是的,我知道它可以使用向量运作。这确实是一个错误。请看我的回答。 - Yakov Galka
4个回答

6
经过一些研究,@bart-jan在他们的其他答案中写的(现已删除)实际上是正确的。可以很容易地看出,我的操作符根本没有被调用,而是调用了CRT版本。(对于所有那些盲目发射的人来说,这里没有递归。)问题是“为什么”?
以上内容是针对动态链接的CRT编译的(这是默认设置)。Microsoft在CRT DLL中提供了std::string(以及许多其他标准模板)的一个实例。查看随VS2005一起提供的Dinkumware头文件:
#if defined(_DLL_CPPLIB) && !defined(_M_CEE_PURE)

template class _CRTIMP2_PURE allocator<char>;
// ...
template class _CRTIMP2_PURE basic_string<char, char_traits<char>,
    allocator<char> >;

_CRTIMP2_PURE展开为__declspec(dllimport)。这意味着在 Release 模式下,链接器将std::string链接到 CRT 构建时实例化的版本,该版本使用 new 的默认实现。

不清楚为什么在调试模式下不会发生这种情况。正如 @Violet Giraffe 正确猜测的那样,它一定受到某些开关的影响。但是,我认为它是链接器开关而不是编译器开关。我无法确定哪个开关起作用。

其他答案忽略的剩下的问题是“是否符合标准”?尝试在 VS2010 中运行该代码,无论我编译哪个配置,它都确实调用了我的operator new!查看随 VS2010 一起提供的头文件,可以发现 Dinkumware 删除了上述实例的 __declspec(dllimport)。因此,我相信旧的行为确实是编译器 bug,而且不是标准的。


听起来像是连接器/库的一个错误,即默认版本的函数不允许有条件地被替换。GCC称之为“弱引用”,MSVC也必须有类似的概念。 - Kerrek SB
1
@KerrekSB:不完全是这样。你只是不能在DLL的上下文中这样做。包含std::string实现的DLL已经与某些new实现链接,当它被操作系统加载后,你无法重新绑定它。 - Yakov Galka

0

在你的 new 操作符或其中一个被调用的函数中调用 cout << std::string(),会导致不可预测的程序行为。不要从你的 new 操作符中使用 io。这样做可能会重新进入你的 new 操作符。


std::ostream << (const char *) 也可能调用 operator new! - bert-jan
@Violet:我没有。const char*不是std::string - Yakov Galka
@bert-jan:首先,我认为它不能符合标准。其次,即使没有任何输出,结果也是可重复的。 - Yakov Galka
如果您实例化std::string()或使用std::cout << (...)操作符,除非使用std RTL作为程序扩展(例如Windows DLL),否则将重新进入new()。 - bert-jan
也许与std::string相关联的分配器在这种情况下决定不在发布版本中使用new(),但这只是一个猜测。 - bert-jan

0

你真的应该逐步调试代码。你尝试在发布配置中设置/Od以禁用优化了吗?我想知道那是否会改变行为。


尝试过了,它并没有改变任何东西。和上面一样。 - Yakov Galka
@ybungalobill:好的,尝试链接调试运行时如何?使用/MDd而不是/MD。此外,您现在可以尝试步入代码以查看哪个函数拦截了控制权。也许,在发布版本中定义了某些宏? - Violet Giraffe
在没有优化的情况下,在发布版本中调试库:结果相同。 - Yakov Galka
@ybungalobill:当你进入new时,你看到了什么? - Violet Giraffe
嗯,我的新代码在发布时没有被编译。请指点正确的方向。 - Yakov Galka

-1

你的程序出现了无限递归。

相反,应该这样做

#include <iostream>
#include <string>
#include <new>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>

void* my_alloc( char const* str, std::size_t size )
{
    fprintf( stderr, "%s %lu\n", str, (unsigned long)size );
    return malloc( size ); // I don't care for errors here
}

void* operator new( std::size_t size )      { return my_alloc( "new", size ); }
void operator delete( void* ptr )           { free(ptr); }
void* operator new[]( std::size_t size )    { return my_alloc( "new[]", size ); }
void operator delete[]( void* ptr )         { free( ptr ); }

int main()
{
    std::string str;
    std::cout << "? ";
    std::getline(std::cin, str);
    std::cout << str;
}

相同的结果。请参见上面的编辑版本,其中没有递归(而且我也没有递归)。 - Yakov Galka
我已经检查过了,使用Visual C++ 10。你可能不仅面临着自己原始错误的问题,还有可能遇到编译器错误。但我认为这种可能性非常小,最有可能是用户自身的问题。 - Cheers and hth. - Alf
(可能的)无限递归在您的原始代码中,而不是第二个片段。对于第二个片段,请考虑您很可能“做错了什么”。比如运行错误的exe文件等。第二个代码的问题在Visual C++ 10.0中没有复现。 - Cheers and hth. - Alf
有时编译失败会导致错误的exe文件运行,因为旧的exe文件仍然存在。为了避免这种情况,建议在尝试编译前删除所有的exe文件。 - Cheers and hth. - Alf
1
Alf:你说的对,VS2010的行为是正确的。这确实是一个实现上的错误,请看我的回答。你之前只是像大多数人一样在胡乱猜测;其实根本没有递归。 - Yakov Galka
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接