【问题标题】:Avoiding memory leaks while mutating c-strings在变异 c 字符串时避免内存泄漏
【发布时间】:2009-07-26 22:07:20
【问题描述】:

出于教育目的,我在一些测试程序中使用 cstrings。我想用“...”之类的占位符来缩短字符串。

也就是说,如果我的最大长度设置为 13,"Quite a long string" 将变为 "Quite a lo..."。此外,我不想破坏原始字符串 - 因此缩短的字符串必须是副本。

下面的(静态)方法是我想出的。我的问题是:为我的缩短字符串分配内存的类也应该负责释放它吗? 我现在要做的是将返回的字符串存储在单独的“用户类”中,并将内存释放到该用户类。

const char* TextHelper::shortenWithPlaceholder(const char* text, size_t newSize) {
    char* shortened = new char[newSize+1];

    if (newSize <= 3) {
        strncpy_s(shortened, newSize+1, ".", newSize);
    }
    else {
        strncpy_s(shortened, newSize+1, text, newSize-3);
        strncat_s(shortened, newSize+1, "...", 3);  
    }
    return shortened;
}

【问题讨论】:

  • 既然您使用的是 C++,为什么不使用 std::string 作为您的返回类型呢?那么内存管理就简单多了。
  • 我在 OP 中没有看到任何内容说必须只使用 C 字符串。 @Don 的问题是有效的 imo
  • 好吧,我知道有 std::string。但我喜欢为 C 语言的细微之处而苦恼;)这只是出于个人目的。
  • strncpy 等绝对不是 C 的“好位”。您应该看看 libowfat 以了解 C 的一些非常好的位。如果您个人不喜欢C++ 的根本改进,你的整个问题都注定了。您应该重新标记它,因为它不再是关于 C++ 的问题。如果您想在 C 语言中找到解决问题的好方法,请查看 libowfat:fefe.de/libowfat
  • @vog 重新标记的问题。顺便说一句,“好位”的意思是讽刺-无论如何,感谢您的链接。

标签: c++ memory-management connection-string cstring


【解决方案1】:

此类函数的标准方法是让用户传入一个 char[] 缓冲区。您可以在 sprintf() 之类的函数中看到这一点,例如,它将目标缓冲区作为参数。这允许调用者负责分配和释放内存,将整个内存管理问题放在一个地方。

【讨论】:

  • 这是 C 中的标准方法。但是,在 C++ 中,标准方法是使用 std::string。
  • 我应该提到我使用 pdcurses。使用 C++ 部分是因为我想为 pdcurses 库提供一些类似 OO 的“小部件”。 +1 “提醒”我 C 方法。
【解决方案2】:

为了避免缓冲区溢出和内存泄漏,在这种情况下,您应该始终使用 C++ 类,例如 std::string

只有最后一个实例应该将类转换为低级别的东西,例如char*。这将使您的代码简单而安全。只需将您的代码更改为:

std::string TextHelper::shortenWithPlaceholder(const std::string& text,
                                               size_t newSize) {
    return text.substr(0, newSize-3) + "...";
}

在 C 上下文中使用该函数时,您只需使用 cstr() 方法:

some_c_function(shortenWithPlaceholder("abcde", 4).c_str());

就是这样!

一般来说,您不应该像在 C 中一样使用 C++ 进行编程。将 C++ 视为一种完全不同的语言更为合适。

【讨论】:

    【解决方案3】:

    我从不乐意将指针返回到本地分配的内存。我喜欢对任何调用我的函数进行清理的人保持健康的不信任。

    相反,您是否考虑过接受一个缓冲区来复制缩短的字符串?

    例如。

    const char* TextHelper::shortenWithPlaceholder(const char* text, 
                                                   size_t textSize, 
                                                   char* short_text, 
                                                   size_t shortSize)
    

    其中 short_text = 复制缩短字符串的缓冲区,shortSize = 提供的缓冲区大小。您还可以继续返回指向 short_textconst char*,以方便调用者(如果 shortSize 不够大,则返回 NULL)。

    【讨论】:

    • 这种策略虽然避免了内存泄漏,但如果调用者不注意,它包含缓冲区溢出的危险,这比内存泄漏还要严重。在 C++ 中,没有必要冒险。只需使用 std::string。
    • +1 表示函数原型。我猜想,将 text 和 short_text 的位置颠倒为 shortWithPlaceholder(char* short_text, size_t shortSize, const char* text, size_t textSize) 会更像 string.h 函数。
    • @vog:我知道这个问题是基于“教育目的......”
    • @Alan:我认为冒着缓冲区溢出的风险对于“教育目的”也是一个坏主意。 YMMV。
    【解决方案4】:

    确实,您应该只使用std::string,但如果必须,请查看现有库以获取使用指南。

    在C标准库中,最接近你正在做的函数是

    char * strncpy ( char * destination, const char * source, size_t num );
    

    所以我会选择这个:

    const char* TextHelper::shortenWithPlaceholder(
        char * destination, 
        const char * source, 
        size_t newSize);
    

    调用者负责内存管理 - 这允许调用者使用堆栈、堆、内存映射文件或保存该数据的任何源。你不需要记录你使用new[]来分配内存,调用者不需要知道使用delete[]而不是freedelete,甚至是更低级别的操作系统调用。将内存管理留给调用者更灵活,更不容易出错。

    返回一个指向目的地的指针只是让你做这样的事情的好方法:

    char buffer[13];
    printf("%s", TextHelper::shortenWithPlaceholder(buffer, source, 12));
    

    【讨论】:

    • ++ 实际上,正是这种微妙之处使我采用了我的问题中所示的方法。在我看来,它不像 C 那样有细节;)
    【解决方案5】:

    最灵活的方法是返回一个包装分配内存的辅助对象,这样调用者就不必担心了。该类存储一个指向内存的指针,并具有一个复制构造函数、一个赋值运算符和一个析构函数。

    class string_wrapper
    {
        char *p;
    
    public:
        string_wrapper(char *_p) : p(_p) { }
        ~string_wrapper() { delete[] p; }
    
        const char *c_str() { return p; }
    
        // also copy ctor, assignment
    };
    
    // function declaration
    string_wrapper TextHelper::shortenWithPlaceholder(const char* text, size_t newSize)
    {
        // allocate string buffer 'p' somehow...
    
        return string_wrapper(p);
    }
    
    // caller
    string_wrapper shortened = TextHelper::shortenWithPlaceholder("Something too long", 5);
    
    std::cout << shortened.c_str();
    

    大多数实际程序为此使用std::string

    【讨论】:

    • 我最初的回答只是“使用 std::string”,但问题确实说这是出于教育目的。
    • 而“包装分配内存的辅助对象”是对 std::string 的公平描述!
    【解决方案6】:

    在您的示例中,调用者别无选择,只能负责释放分配的内存。

    然而,这是一个容易出错的习惯用法,我不建议使用它。

    允许您使用几乎相同的代码的另一种方法是将shortened 更改为引用的计数指针,并让方法返回引用的计数指针而不是裸指针。

    【讨论】:

      【解决方案7】:

      有两种我认为同样常见的基本方式: a) TextHelper 返回 c 字符串并忘记它。用户必须删除内存。 b) TextHelper 维护一个已分配字符串的列表,并在销毁时释放它们。

      现在这取决于您的使用模式。 b) 对我来说似乎有风险:如果 TextHelper 必须解除分配字符串,则在用户完成使用缩短的字符串之前不应这样做。您可能不知道这一点何时到来,因此您要保持 TextHelper 处于活动状态,直到程序终止。这导致内存使用模式等于内存泄漏。我建议 b) 仅当字符串在语义上属于提供它们的类时,类似于 std::string::c_str()。您的 TextHelper 看起来更像是一个不应与已处理字符串关联的工具箱,因此如果我必须在两者之间进行选择,我会选择 a)。给定一个固定的 TextHelper 接口,您的用户类可能是最好的解决方案。

      【讨论】:

        【解决方案8】:

        编辑:不,我错了。我误解了你想做什么。调用者必须删除实例中的内存。

        C++ 标准规定删除 0/NULL 什么都不做(换句话说,这样做是安全的),因此无论您是否调用过该函数,都可以删除它。编辑:我不知道这是如何被遗漏的……您的另一个选择是放置删除。在这种情况下,即使是错误的形式,您也应该使用placement new 将分配/释放保持在同一个地方(否则不一致会使调试变得荒谬)。

        也就是说,您是如何使用代码的?我看不出你什么时候会多次调用它,但如果你这样做了,如果你不记得每个不同的内存块,就会有潜在的内存泄漏(我认为)。

        我只会使用std::auto_ptrBoost::shared_ptr。它会在退出时自行删除,并且可以与 char* 一起使用。

        您可以做的另一件事是考虑如何分配 TextHelper。这是一个理论上的ctor:

        TextHelper(const char* input) : input_(input), copy(0) { copy = new char[sizeof(input)/sizeof(char)]; //mess with later }
        ~TextHelper() { delete copy; }
        

        【讨论】:

        • 我使用的函数在TextHelper中是静态的。只会让事情更像 C。我可能应该在我的问题中遗漏任何 C++ 参考,但损害已经造成。
        猜你喜欢
        • 2014-07-31
        • 2018-05-25
        • 1970-01-01
        • 2018-04-08
        • 2013-06-24
        • 1970-01-01
        • 2011-05-04
        相关资源
        最近更新 更多