[英]Avoiding memory leaks while mutating c-strings

For educational purposes, I am using cstrings in some test programs. I would like to shorten strings with a placeholder such as "...".


That is, "Quite a long string" will become "Quite a lo..." if my maximum length is set to 13. Further, I do not want to destroy the original string - the shortened string therefore has to be a copy.

也就是說,如果我的最大長度設置為13,“相當長的字符串”將變成“相當......”。此外,我不想破壞原始字符串 - 因此縮短的字符串必須是副本。

The (static) method below is what I come up with. My question is: Should the class allocating memory for my shortened string also be responsible for freeing it? What I do now is to store the returned string in a separate "user class" and defer freeing the memory to that user class.


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;

8 个解决方案


The standard approach of functions like this is to have the user pass in a char[] buffer. You see this in functions like sprintf(), for example, which take a destination buffer as a parameter. This allows the caller to be responsible for both allocating and freeing the memory, keeping the whole memory management issue in a single place.

像這樣的函數的標准方法是讓用戶傳入char []緩沖區。您可以在sprintf()等函數中看到這一點,它將目標緩沖區作為參數。這允許調用者負責分配和釋放內存,將整個內存管理問題保存在一個地方。


In order to avoid buffer overflows and memory leaks, you should always use C++ classes such as std::string in this case.

為了避免緩沖區溢出和內存泄漏,在這種情況下,應始終使用C ++類,例如std :: string。

Only the very last instance should convert the class into something low level such as char*. This will make your code simple and safe. Just change your code to:

只有最后一個實例才能將類轉換為低級別,例如char *。這將使您的代碼簡單而安全。只需將您的代碼更改為:

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

When using that function in a C context, you simply use the cstr() method:


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

That's all!

In general, you should not program in C++ the same way you program in C. It's more appropriate to treat C++ as a really different language.

通常,您不應該像在C中編程一樣使用C ++編程。將C ++視為一種非常不同的語言更為合適。


I've never been happy returning pointers to locally allocated memory. I like to keep a healthy mistrust of anyone calling my function in regard to clean up.


Instead, have you considered accepting a buffer into which you'd copy the shortened string?



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

where short_text = buffer to copy shortened string, and shortSize = size of the buffer supplied. You could also continue to return a const char* pointing to short_text as a convenience to the caller (return NULL if shortSize isn't large enough to).

其中short_text =緩沖區以復制縮短的字符串,shortSize =緩沖區的大小。您還可以繼續返回指向short_text的const char *,以方便調用者(如果shortSize不夠大,則返回NULL)。


Really you should just use std::string, but if you must, look to the existing library for usage guidance.

實際上你應該只使用std :: string,但如果必須,請查看現有的庫以獲取使用指南。

In the C standard library, the function that is closest to what you are doing is


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

So I'd go with this:


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

The caller is responsible for memory management - this allows the caller to use the stack, or a heap, or a memory mapped file, or whatever source to hold that data. You don't need to document that you used new[] to allocate the memory, and the caller doesn't need to know to use delete[] as opposed to free or delete, or even a lower-level operating system call. Leaving the memory management to the caller is just more flexible, and less error prone.

調用者負責內存管理 - 這允許調用者使用堆棧,堆,或內存映射文件,或任何來源來保存該數據。您不需要記錄您使用new []來分配內存,並且調用者不需要知道使用delete []而不是free或delete,甚至是更低級別的操作系統調用。將內存管理留給調用者只是更靈活,更不容易出錯。

Returning a pointer to the destination is just a nicety to allow you to do things like this:


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


The most flexible approach is to return a helper object that wraps the allocated memory, so that the caller doesn't have to worry about it. The class stores a pointer to the memory, and has a copy constructor, an assignment operator and a destructor.


class string_wrapper
    char *p;

    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();

Most real programs use std::string for this purpose.

大多數真正的程序都使用std :: string來實現此目的。


In your example the caller has no choice but to be responsible for freeing the allocated memory.


This, however, is an error prone idiom to use and I don't recommend using it.


One alternative that allows you to use pretty much the same code is to change shortened to a referenced counted pointer and have the method return that referenced counted pointer instead of a bare pointer.



There are two basic ways that I consider equally common: a) TextHelper returns the c string and forgets about it. The user has to delete the memory. b) TextHelper maintains a list of allocated strings and deallocates them when it is destroyed.

我認為有兩種基本方法同樣常見:a)TextHelper返回c字符串並忘記它。用戶必須刪除內存。 b)TextHelper維護一個已分配字符串的列表,並在銷毀時解除分配它們。

Now it depends on your usage pattern. b) seems risky to me: If TextHelper has to deallocate the strings, it should not do so before the user is done working with the shortened string. You probably won't know when this point comes, so you keep the TextHelper alive until the program terminates. This results in a memory usage pattern equal to a memory leak. I'd recommend b) only if the strings belong semantically to the class that provides them, similar to the std::string::c_str(). Your TextHelper looks more like a toolbox that should not be associated with the processed strings, so if I had to choose between the two, I'd go for a). Your user class is probably the best solution, given a fixed TextHelper interface.

現在它取決於您的使用模式。 b)對我來說似乎有風險:如果TextHelper必須釋放字符串,則在用戶使用縮短的字符串之前不應該這樣做。您可能不知道這一點何時到來,因此您將TextHelper保持活動狀態,直到程序終止。這導致內存使用模式等於內存泄漏。我建議b)只有字符串在語義上屬於提供它們的類,類似於std :: string :: c_str()。你的TextHelper看起來更像是一個不應該與處理過的字符串相關聯的工具箱,所以如果我必須在兩者之間做出選擇,我會選擇a)。鑒於固定的TextHelper接口,您的用戶類可能是最佳解決方案。


Edit: No, I'm wrong. I misunderstood what you were trying to do. The caller must delete the memory in your instance.


The C++ standard states that deleting 0/NULL does nothing (in other words, this is safe to do), so you can delete it regardless of whether you ever called the function at all. Edit: I don't know how this got left out...your other alternative is placement delete. In that case, even if it is bad form, you should also use placement new to keep the allocation/deallocation in the same place (otherwise the inconsistency would make debugging ridiculous).

C ++標准規定刪除0 / NULL什么都不做(換句話說,這樣做是安全的),所以你可以刪除它,無論你是否曾經調用過這個函數。編輯:我不知道這是怎么被遺漏的......你的另一個選擇是放置刪除。在這種情況下,即使它是不好的形式,你也應該使用placement new來保持分配/釋放在同一個地方(否則不一致會使調試變得荒謬)。

That said, how are you using the code? I don't see when you would ever call it more than once, but if you do, there are potential memory leaks (I think) if you don't remember each different block of memory.


I would just use std::auto_ptr or Boost::shared_ptr. It deletes itself on the way out and can be used with char*.

我只想使用std :: auto_ptr或Boost :: shared_ptr。它在出路時自行刪除,可以與char *一起使用。

Another thing you can do, considering on how TextHelper is allocated. Here is a theoretical ctor:


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



粤ICP备14056181号  © 2014-2021 ITdaan.com