What is wrong with using dynamically allocated arrays in C ++?

Like the following code:

int size = myGetSize(); std::string* foo; foo = new std::string[size]; //... // using the table //... delete[] foo; 

I heard that such use (and not this code for sure, but dynamic allocation in general) can be unsafe in some cases and should only be used with RAII. Why?

+29
c ++ dynamic-allocation
Jun 10 '14 at 8:19
source share
9 answers

I see three main problems with your code:

  • Use bare, owning pointers.

  • Using bare new .

  • Using dynamic arrays.

Each of them is undesirable for its own reasons. I will try to explain each of them in turn.

(1) violates what I like to call subexpressive correctness, and (2) violates the correctness of the wording. The idea here is that no statement, or even any subexpression, in itself should be a mistake. I accept the term “mistake” freely to mean “may be a mistake”.

The idea of ​​writing good code is that if it goes wrong, it is not your fault. Your basic thinking should be like a paranoid coward. Not writing code at all is one way to achieve this, but since it rarely meets the requirements, it’s best to make sure that whatever you do is not your fault. The only way you can systematically prove that this is not your mistake is that not a single part of your code is the main cause of the error. Now consider the code again:

  • new std::string[25] - an error because it creates a dynamically allocated object that has leaked. This code can conditionally become not an error if someone else, somewhere else and in each case, remembers to clear it.

    This requires, first of all, that the meaning of this expression be stored somewhere. This happens in your case, but in more complex terms it can be difficult to prove that it will ever happen in all cases (unspecified order of evaluation, I look at you).

  • foo = new std::string[125]; is a mistake, because again foo a resource leak, unless the stars are aligned, and someone remembers, in each case and at the right time, to clear.

The correct way to write this code so far would be as follows:

 std::unique_ptr<std::string[]> foo(std::make_unique<std::string[]>(25)); 

Note that each subexpression in this expression is not the main cause of a program error. This is not your mistake.

Finally, with regard to (3), dynamic arrays are errors in C ++ and, in principle, will never be used. There are several standard defects that apply only to dynamic arrays (and are not considered worthy of fixation). The simple argument is that you cannot use arrays without knowing their size. You could say that you can use the value of the watch or tombstone to dynamically mark the end of the array, but this makes the correctness of your program cost-dependent and not type-dependent and therefore not statically checked (the definition itself is "unsafe" ") You cannot say that this is not your fault.

Thus, you still have to maintain separate storage for the size of the array. And guess what, your implementation should duplicate this knowledge anyway, so that it can name destructors when you say delete[] to unravel the empty one. Instead, the correct way is not to use dynamic arrays, but instead to allocate memory allocation (and make it customizable using allocators, why are we on it), from creating an elementary object. Wrapping all of this (allocator, storage, number of elements) in one convenient class is a C ++ method.

So the final version of your code is this:

 std::vector<std::string> foo(25); 
+45
Jun 10 '14 at 8:38
source share

The code you offer is no exception and an alternative:

 std::vector<std::string> foo( 125 ); // no delete necessary 

there is. And, of course, vector knows the size later and can check borders in debug mode; it can be passed (by reference or even by value) to a function that it can then use without any additional arguments. The new array follows C for arrays and arrays in C are seriously broken.

As far as I can see, there is never a case when a new array is suitable.

+9
Jun 10 '14 at 11:31
source share

I heard that such use (and not this code for sure, but dynamic allocation in general) can be unsafe in some cases and should only be used with RAII. Why?

Take this example (similar to yours):

 int f() { char *local_buffer = new char[125]; get_network_data(local_buffer); int x = make_computation(local_buffer); delete [] local_buffer; return x; } 

This is trivial.

Even if you write the code correctly, someone can come back in a year and add a conditional or ten or twenty to their function:

 int f() { char *local_buffer = new char[125]; get_network_data(local_buffer); int x = make_computation(local_buffer); if(x == 25) { delete[] local_buffer; return 2; } if(x < 0) { delete[] local_buffer; // oops: duplicated code return -x; } if(x || 4) { return x/4; // oops: developer forgot to add the delete line } delete[] local_buffer; // triplicated code return x; } 

Now, to make sure that the code does not have memory leaks, it is more complicated: you have several code paths, and each of them must repeat the delete statement (and I specially introduced the memory leak to give you an example).

This is still a trivial case where there is only one resource (local_buffer), and it (naively) assumes that the code does not throw any exceptions between allocation and deallocation. The problem leads to unreachable code when your function allocates ~ 10 local resources, can throw, and has several return paths.

Moreover, the progression above (a simple, trivial case, expanded to a more complex function with several exit paths, expanded to several resources, etc.) is a natural progression of the code in the development of most projects. Without using RAII, the developer creates a natural way to update the code, which will reduce the quality, throughout the life cycle of the project ( this is called cruft, and this is very bad ).

TL; DR: using raw pointers in C ++ to manage memory is bad practice (although to implement the role of an observer, the implementation with the source pointers is fine). Resource management with raw poiners violates SRP and DRY principles).

+8
Jun 10 '14 at 8:48
source share

There are two main disadvantages:

  • new does not guarantee that allocated memory is initialized with 0 or null . They will have undefined values ​​if you do not initialize them.

  • Secondly, the memory is dynamically allocated, which means that it is not placed on the heap on the stack . The difference between heap and stack is that the stacks are cleared when the variable goes out of scope, but heap not cleared automatically, and C ++ does not contain a built-in garbage collector, which means t28> the call is missed, you ran out of memory leak .

+2
Jun 10 '14 at 8:29
source share

a raw pointer is difficult to handle correctly, for example. WRT. copying objects.

it’s much easier and safer to use a well-tested abstraction like std::vector .

in short, there is no need to reinvent the wheel unnecessarily; others have already created some excellent wheels that are unlikely to match quality and price.

+2
Jun 10 '14 at 8:33
source share

If the allocated memory is not freed when it is no longer needed, this will lead to a memory leak. It is not indicated what will happen with a memory leak, but modern operating systems collect it when the program terminates. Memory leaks can be very dangerous, as the system may run out of memory.

+1
Jun 10 '14 at 8:23
source share

At the end, delete can be skipped. The code shown is not “wrong” in the strict sense, but C ++ offers automatic memory management for variables as soon as their volume remains; using a pointer in your example is not required.

+1
Jun 10 '14 at 8:23
source share

Keep the allocation in the try block, and the catch block should free up all the allocated memory so far, as well as with the normal exit from the exception block, and the catch block should not fall through the normal execution block to avoid double deletion

0
Jun 10 '14 at 8:27
source share

See JPL Encoding Standards . Dynamic memory allocation leads to unpredictable execution. I saw problems with the allocation of dynamic memory in perfectly encoded systems - that fragmentation of memory occurs over time, like a hard disk. Allocating memory blocks from the heap will take longer and longer until it is impossible to allocate the requested size. At this point, you start returning NULL pointers, and the whole program crashes due to the fact that few people check the conditions for lack of memory. It is important to note that you may have enough memory in the book, but fragmentation prevents it from being allocated. This is discussed in the .NET CLI using "handles" instead of pointers , where the runtime can collect garbage using labels and - collect the garbage collector, move memory around. During sweep, it compresses the memory to prevent fragmentation and updating of the handles. While pointers (memory addresses) cannot be updated. This is a problem because garbage collection is no longer deterministic. Although, .NET has added mechanisms to make it more deterministic. However, if you follow the recommendations of JPL (section 2.5), you do not need fancy garbage collection. You dynamically allocate everything you need for initialization, then reuse the allocated memory without freeing it, then there is no risk of fragmentation, and you can still have a deterministic garbage collection.

-one
Jun 10 '14 at 19:14
source share



All Articles