【问题标题】:C++ string expansion leads to allocation errorsC++ 字符串扩展导致分配错误
【发布时间】:2017-11-25 15:43:15
【问题描述】:

我有一个memory leaking library 来检查我的应用程序是否存在内存泄漏。但是,一个非常简单的程序已经导致内存泄漏。

考虑以下将 14 个字符分配给字符串 Message 的程序:

string Message;
int main() {
    Message = "This is a test";
    }

不会导致内存错误。

但是,当我尝试初始化长度超过 15 个字符(比如说 20 个)的字符串 Message 时,它​​给了我一个内存泄漏错误:

string Message;
int main() {
    Message = "This is a test which";
    }

导致错误:

Leaks found: 1 allocations (31 bytes)

显然,如果您初始化一个字符串(最多 15 个字符 + \0),c++ 将分配 16 个字节的空间:

string Message;

但是,如果我将消息初始化为:

string Message = "This is a test which is long enough to hold 'This is a test which'";

之前的内存泄漏错误消失了。

所以,当我尝试使用超出字符串缓冲区实际声明大小的动态字符串大小时,C++ 不能很好地分配内存?

可视化:

string Message; //Allocates 16 bytes of memory whereof the 16th position is \0
Message = "This is a test which"; //longer then 15 --> Memory Leak

但是,如果我执行以下操作,它不会产生任何错误:

string Message = "This is a test which can hold a long string";
Message = "This is a test which"; //NO ERRORS

如何在 C++ 中解决这个问题?我更喜欢坚持使用字符串,但是我需要知道如何正确扩展字符串。因此,如果字符串内容溢出先前分配的内存,则让 C++ 处理内存分配。

【问题讨论】:

  • 这段代码没有问题。因此,这似乎是 (a) 泄漏检测器中的错误,(b) C++ 实现中的错误(不太可能),或 (c) 误报。
  • std::string 类实现可能会根据字符串的长度使用不同的方案为字符串分配内存。如果泄漏检测器没有正确处理全局变量破坏,它可能会报告误报。
  • 那么,如果我循环将字符串分配给消息变量,应用程序使用的内存池怎么可能不断增长?
  • 看起来您的检漏仪无法正常工作。尽管如此,它确实观察到的差异可能是由于编译器的 std::string 实现中的小字符串优化 (SSO)。
  • 顺便说一句... “与 Java 和 C# 等较新的语言不同,C 和 C++ 不会自动为您管理内存。” - 这句话本身就应该给出您了解该工具的专业性和可靠性。

标签: c++ string memory memory-management memory-leaks


【解决方案1】:

消息是一个全局变量。

您的泄漏检测器必须在构造全局变量之后但在 main 启动之前拍摄内存状态的快照。

然后在 main 结束后,全局变量被破坏之前查看内存。

std::string 通常(但不总是——依赖于实现)对短字符串有一个实现优化......但比这更长的字符串在堆上分配。

您的泄漏检测器正在查看堆上的字符串,但没有意识到将很快调用的消息析构函数会在短时间内将其释放回堆。

【讨论】:

    【解决方案2】:

    leaker.h 标头试图通过添加一组#defines 来拦截内存分配:

    /* preprocessor magic to override built-in allocation functions with our own */
    #define malloc(size)        _malloc(size, __FILE__, __func__, __LINE__)
    #define calloc(n, size)     _calloc(n, size, __FILE__, __func__, __LINE__)
    #define free(ptr)           _free(ptr, __FILE__, __func__, __LINE__)
    #define realloc(ptr, size)  _realloc(ptr, size, __func__, __FILE__, __LINE__)
    
    #ifdef __cplusplus
    
    /* hackish solution to the problem of overriding C++ operator new/delete */
    extern const char *_leaker_file;
    extern const char *_leaker_func;
    extern unsigned long _leaker_line;
    
    #define new (_leaker_file=__FILE__, _leaker_func=__func__, \
        _leaker_line=__LINE__) && 0 ? NULL : new
    #define delete _leaker_file=__FILE__, _leaker_func=__func__, \
        _leaker_line=__LINE__, delete
    

    如果我们忘记了明确不允许重新定义诸如 newdelete 之类的关键字,那么只有在 std::string 实现的每个部分都定义为内联函数时,此 hack 才有机会发挥作用。

    如果std::string 的某些部分在运行时库中实现(并非不可能),这些定义不会对那里的预编译代码产生任何影响。然后检测将只是部分的,会错过一些分配或释放。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-22
      • 1970-01-01
      • 1970-01-01
      • 2014-05-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多