4

I was reading an excellent answer to another question which gave these specific examples:

char[] array = {'a', 'b', 'c', '\0'};
Thing* t = new Thing[size];
t[someindex].dosomething();

I commented that, as good practice, you should add delete[] to the second example, as this is the bit people forget leading to memory leaks.

But I can't remember if you need to delete[] in the first instance. I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.

deworde
  • 2,481
  • 3
  • 28
  • 55
  • 6
    `new` and `delete` come in pairs. No `delete` without `new` and vice versa. – Thomas Sablik Jul 08 '19 at 08:45
  • 2
    @ThomasSablik Yes generally, but when for example making a `unique_ptr` with `new` (Which you actually shouldn't ever do), you still shoudn't use `delete` on the pointer ever. – ruohola Jul 08 '19 at 08:47
  • 3
    @ruohola Internally it still uses the `delete` operator, so what Thomas claimed still holds up. – Neijwiert Jul 08 '19 at 08:48
  • @Neijwiert Yes ofc, just that there are some cases when it's fine for the programmer to only use one of them. – ruohola Jul 08 '19 at 08:49
  • @ruohola I said that for each `new` call there has to be one `delete` call and vice versa. I didn't say that the programmer has to do both calls. Of course it's possible that one call is in the library and the other has to be done by the programmer. But that's very bad coding style. – Thomas Sablik Jul 08 '19 at 09:02
  • 2
    Since C++11 direct use use of `new` `delete` is obsolete and not recommended. Use RAII as far as possible. In case of presented example also with C++03 `std::vector` should be used! – Marek R Jul 08 '19 at 09:08
  • @marek True, but sometimes somebody gets all excited by C-arrays or you have to deal with existing code. – deworde Jul 08 '19 at 09:38
  • @deworde If you are allowed to change the existing code to fix leaks, why can't you change it so that `new` and `new[]` don't appear? – Caleth Jul 08 '19 at 11:00
  • @Caleth: The selected code is an example given in an answer to a different question. My question is using those code examples, so I referred back to it (intended to add a link) In real code, while you can of course rewrite large parts of the codebase to use vector over c-arrays, it's unwise to modify potentially complex working code in that way, unless you're actively working on it. Beyond that, while good practice, "replace with std::vector" is not relevant to the question, which is specifically about c-array behaviour. Good point that I screwed up the intro, though. – deworde Jul 08 '19 at 12:47

3 Answers3

10

I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.

This reasoning is correct when the array is defined inside a function, and is therefore on the stack. You must never use delete on any objects that have been created on the stack without the use of new. Using delete on stack allocated objects will lead to undefined behavior.

If the definition char[] array = {'a', 'b', 'c', '\0'}; appeared at file scope, however, it will have static storage duration and will not be on the stack, but it still won't have been dynamically allocated and still must not be delete[]d.

So, either way, don't delete[] the array....

Tony Delroy
  • 94,554
  • 11
  • 158
  • 229
ruohola
  • 16,015
  • 6
  • 33
  • 67
8
  1. You always and only use delete when you have used new and delete[] when you have used new[]. Never mix them. And never mix with (2).
  2. You always and only use free when you have made a classic C allocation like malloc or calloc. Never mix with (1). But don't free memory allocated with alloca ;)

Note that in some cases, the allocation or deallocation can be hidden inside a function or something. For instance, there is a non standard C-function that copies a string like this: char *str = strdup("Hello");. In this case, you should use free(str) even though you did not manually invoke malloc.

So if you do a declaration on the form T t[10] or T[] t={a,b,c} you do NOT deallocate this.

int main()
{
    int x;
    std::cin << x;

    T *a = new T;
    delete a;
    T *b = new T[x];
    delete[] b;
    T *c = (T*)malloc(x*sizeof(*c));
    free(c);

    // No free or delete for these
    T d;
    T e[10];
    // Assuming a, b and c are instances of T or that you have overloaded the
    // operator so that you can write T t=a; 
    // In either case, you should not free nor delete this
    T f[] = {a, b, c};
}

In C, you can use VLA:s like int arr[x] where x is not known at compile time. That's not possible in C++. You don't use free on these arrays either.

I don't think you do, because there's no new so it's on the stack, but I'm not 100% sure that's right.

This is not correct. The standard does not dictate if the objects should be on the stack or the heap. It's up to the compiler, so what happens in reality is a question of how compilers usually solves the task.

Furthermore, the "fact" that statically allocated memory ends up on the stack and dynamically allocated memory ends up on the heap is only true most of the time. Granted, this is usually what happens, but there are some rare cases when it's not. And these occurrences are not so rare or obscure that they can be ignored. Global variables typically does not end up on the stack.

So if memory should be freed/deleted is not a question of if it lives in the stack or the heap. It's about how it has been allocated.

And in modern C++ you very rarely use new, new[], delete or delete[] at all. Instead, you use smart pointers and containers. Look at this link: What is a smart pointer and when should I use one? and also https://en.cppreference.com/w/cpp/container/vector If you are dealing with old legacy C++, it might be a good idea to refactor it to modern memory management. If you change a new-delete pair to a smart pointer, then you have saved a line. If you change a new without a corresponding delete, then you have solved a bug.

TL;DR

When do you need to delete C-style arrays?

For the case T a[n] (with or without initialization) you should never do it. For the case T *a = new T[n] you should always do it. Whenever you have used new you should delete it afterwards, and never otherwise. (There may be some rare exceptions to this. See the rest of the post.)

Also, AFIK, there is no established definition of "C-style arrays". Personally, I would not consider T *a = new T[n] an array at all. I would not consider T *a = malloc(n*sizeof(*a)) an array either. The reason is simple. Pointers and arrays are NOT the same thing, and this goes for both C and C++. This question is about C, but is applicable for C++ too: Is an array name a pointer?

klutt
  • 25,535
  • 14
  • 43
  • 72
5

I commented that, as good practice, you should add delete[] to the second example, as this is the bit people forget leading to memory leaks.

You also should add delete[] in order to get destructors called. In addition to releasing some internally allocated memory, destructors may have other side effects.

Semantically, new and delete (and new[] and delete[]) come in pairs. new starts the object lifetime, and delete ends the object lifetime started by new. However, usually it is advisable to make deletion hidden inside a smart pointer.

The idea is to bind object lifetimes to the notion of "object ownership". Static objects are owned by the whole program. Automatic objects are owned by the code block they are defined in. Dynamic objects have no clearly defined ownership in C++ language itself, although the language gives the tools to implement the notion of object ownership by the writers of the code.

Smart pointers are one of these tools. std::unique_ptr is used when at every moment of time the object has only one owner, but you may want to pass ownership between blocks of code. std::shared_ptr is used when there can be multiple owners, and the object lifetime should end when the last owner stops to be interested in the object.

Kit.
  • 2,376
  • 1
  • 12
  • 14
  • While I am aware in C++ that far, *far* better options exist than C-style arrays, they still exist inside legacy code and [retain niche uses in C++](https://stackoverflow.com/questions/6111565/now-that-we-have-stdarray-what-uses-are-left-for-c-style-arrays), so the scope of this question is strictly limited to c-arrays, rather than more modern alternatives. Managed pointers seem even more out-of-scope. – deworde Jul 08 '19 at 13:01
  • 1
    Actually, you are lucky that you have any heap at all (I don't have it in one of my projects and am only supposed to use area allocators in another). Still, there is nothing wrong in (properly) using smart pointers with C-style arrays. – Kit. Jul 08 '19 at 13:30