Development Tip

C ++ 프로그래머가 'new'사용을 최소화해야하는 이유는 무엇입니까?

yourdevel 2020. 9. 28. 10:16
반응형

C ++ 프로그래머가 'new'사용을 최소화해야하는 이유는 무엇입니까?


std :: list <std :: string>을 사용할 때 std :: string 으로 Stack Overflow 질문 Memory leak을 우연히 발견 했으며 주석 중 하나는 다음같이 말합니다.

new너무 많이 사용 하지 마십시오. 당신이 한 곳에서 새로운 것을 사용한 이유를 볼 수 없습니다. C ++에서 값별로 객체를 생성 할 수 있으며 이는 언어 사용의 큰 장점 중 하나입니다. 힙에 모든 것을 할당 할 필요는 없습니다. 자바 프로그래머처럼 생각하지 마세요.

그게 무슨 뜻인지 잘 모르겠습니다. 왜 객체는 가능한 한 자주 C ++에서 값으로 생성되어야하며 내부적으로 어떤 차이가 있습니까? 내가 답을 잘못 해석 했습니까?


널리 사용되는 메모리 할당 기술에는 자동 할당과 동적 할당의 두 가지가 있습니다. 일반적으로 스택 및 힙 각각에 해당하는 메모리 영역이 있습니다.

스택

스택은 항상 순차적 인 방식으로 메모리를 할당합니다. 역순 (First-In, Last-Out : FILO)으로 메모리를 해제해야하기 때문에 그렇게 할 수 있습니다. 이것은 많은 프로그래밍 언어에서 지역 변수에 대한 메모리 할당 기술입니다. 최소한의 부기가 필요하고 할당 할 다음 주소가 암시 적이기 때문에 매우 빠릅니다.

C ++에서는 스토리지가 범위 끝에서 자동으로 청구되기 때문에 이를 자동 스토리지 라고합니다. 현재 코드 블록 (으로 구분 {}) 실행이 완료되는 즉시 해당 블록의 모든 변수에 대한 메모리가 자동으로 수집됩니다. 이것은 또한 리소스를 정리하기 위해 소멸자 가 호출 되는 순간 입니다.

더미

힙은보다 유연한 메모리 할당 모드를 허용합니다. 부기는 더 복잡하고 할당이 느립니다. 암시 적 릴리스 지점이 없기 때문에 delete또는 delete[]( freeC에서)를 사용하여 메모리를 수동으로 릴리스해야합니다 . 그러나 암시 적 릴리스 지점이 없다는 것이 힙 유연성의 핵심입니다.

동적 할당을 사용하는 이유

힙을 사용하는 것이 더 느리고 잠재적으로 메모리 누수 또는 메모리 조각화로 이어 지더라도 덜 제한적이므로 동적 할당에 대한 완벽한 사용 사례가 있습니다.

동적 할당을 사용하는 두 가지 주요 이유 :

  • 컴파일 타임에 얼마나 많은 메모리가 필요한지 모릅니다. 예를 들어, 텍스트 파일을 문자열로 읽을 때 일반적으로 파일의 크기를 알지 못하므로 프로그램을 실행할 때까지 할당 할 메모리 양을 결정할 수 없습니다.

  • 현재 블록을 떠난 후에도 지속될 메모리를 할당하려고합니다. 예를 들어, string readfile(string path)파일의 내용을 반환 하는 함수를 작성할 수 있습니다. 이 경우 스택이 전체 파일 내용을 보관할 수 있더라도 함수에서 돌아와 할당 된 메모리 블록을 유지할 수 없습니다.

동적 할당이 종종 불필요한 이유

C ++에는 소멸 자라는 깔끔한 구조가 있습니다. 이 메커니즘을 사용하면 리소스의 수명을 변수의 수명에 맞춰 리소스를 관리 할 수 ​​있습니다. 이 기술을 RAII 라고 하며 C ++의 특징입니다. 리소스를 개체로 "랩"합니다. std::string완벽한 예입니다. 이 스 니펫 :

int main ( int argc, char* argv[] )
{
    std::string program(argv[0]);
}

실제로 가변적 인 양의 메모리를 할당합니다. std::string소멸자에서 힙 해제를 사용하여 객체의 메모리를 할당합니다. 이 경우 않았다 하지 수동으로 동적 메모리 할당의 장점을 가지고 여전히 모든 자원을 관리 할 필요가있다.

특히이 스 니펫에서 다음을 의미합니다.

int main ( int argc, char* argv[] )
{
    std::string * program = new std::string(argv[0]);  // Bad!
    delete program;
}

불필요한 동적 메모리 할당이 있습니다. 이 프로그램은 더 많은 타이핑 (!)을 필요로하며 메모리 할당 해제를 잊어 버릴 위험이 있습니다. 명백한 이점없이이를 수행합니다.

자동 스토리지를 최대한 자주 사용해야하는 이유

기본적으로 마지막 단락이 요약합니다. 가능한 한 자주 자동 저장소를 사용하면 프로그램이 다음과 같이됩니다.

  • 입력이 더 빠릅니다.
  • 달리면 더 빠릅니다.
  • 메모리 / 리소스 누수가 덜 발생합니다.

보너스 포인트

참조 된 질문에는 추가 문제가 있습니다. 특히 다음 클래스 :

class Line {
public:
    Line();
    ~Line();
    std::string* mString;
};

Line::Line() {
    mString = new std::string("foo_bar");
}

Line::~Line() {
    delete mString;
}

실제로 다음 중 사용하는 것보다 훨씬 더 위험합니다.

class Line {
public:
    Line();
    std::string mString;
};

Line::Line() {
    mString = "foo_bar";
    // note: there is a cleaner way to write this.
}

그 이유는 std::string복사 생성자 올바르게 정의하기 때문입니다 . 다음 프로그램을 고려하십시오.

int main ()
{
    Line l1;
    Line l2 = l1;
}

원래 버전을 사용하면이 프로그램은 delete동일한 문자열을 두 번 사용하므로 충돌 할 가능성이 있습니다 . 수정 된 버전을 사용하면 각 Line인스턴스는 자체 메모리를 가진 자체 문자열 인스턴스를 소유하며 둘 다 프로그램이 끝날 때 해제됩니다.

기타 참고 사항

RAII를 광범위하게 사용하는 것은 위의 모든 이유 때문에 C ++에서 모범 사례로 간주됩니다. 그러나 즉시 명확하지 않은 추가 이점이 있습니다. 기본적으로 부품의 합보다 낫습니다. 전체 메커니즘 . 확장됩니다.

Line클래스를 빌딩 블록으로 사용하는 경우 :

 class Table
 {
      Line borders[4];
 };

그때

 int main ()
 {
     Table table;
 }

4 개의 std::string인스턴스, 4 개의 Line인스턴스, 1 개의 Table인스턴스 및 모든 문자열의 내용을 할당 하면 모든 것이 자동으로 해제 됩니다.


스택이 더 빠르고 누출되지 않기 때문에

C ++에서는 주어진 함수의 모든 로컬 범위 개체에 대해 스택에 공간을 할당하는 데 단 하나의 명령이 필요하며 해당 메모리를 누수하는 것은 불가능합니다. 그 주석은 "힙이 아닌 스택을 사용하라" 와 같은 말을 의도했거나 의도 했어야했다 .


그 이유는 복잡합니다.

첫째, C ++는 가비지 수집되지 않습니다. 따라서 모든 새 항목에 대해 해당 삭제가 있어야합니다. 이 삭제를 넣지 않으면 메모리 누수가있는 것입니다. 이제 다음과 같은 간단한 경우 :

std::string *someString = new std::string(...);
//Do stuff
delete someString;

이것은 간단합니다. 그러나 "Do stuff"에서 예외가 발생하면 어떻게됩니까? 죄송합니다. 메모리 누수입니다. "Do stuff"문제가 return일찍 발생하면 어떻게 되나요? 죄송합니다. 메모리 누수입니다.

그리고 이것은 가장 간단한 경우 입니다. 그 문자열을 누군가에게 돌려 주면 이제 삭제해야합니다. 그리고 그들이 그것을 인수로 전달하면 그것을받는 사람이 그것을 삭제해야합니까? 언제 삭제해야합니까?

또는 다음과 같이 할 수 있습니다.

std::string someString(...);
//Do stuff

아니 delete. 객체는 "스택"에 생성되었으며 범위를 벗어나면 폐기됩니다. 객체를 반환하여 해당 내용을 호출 함수로 전송할 수도 있습니다. 객체를 함수에 전달할 수 있습니다 (일반적으로 참조 또는 상수 참조 : void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis). 등).

All without new and delete. There's no question of who owns the memory or who's responsible for deleting it. If you do:

std::string someString(...);
std::string otherString;
otherString = someString;

It is understood that otherString has a copy of the data of someString. It isn't a pointer; it is a separate object. They may happen to have the same contents, but you can change one without affecting the other:

someString += "More text.";
if(otherString == someString) { /*Will never get here */ }

See the idea?


Objects created by new must be eventually deleted lest they leak. The destructor won't be called, memory won't be freed, the whole bit. Since C++ has no garbage collection, it's a problem.

Objects created by value (i. e. on stack) automatically die when they go out of scope. The destructor call is inserted by the compiler, and the memory is auto-freed upon function return.

Smart pointers like unique_ptr, shared_ptr solve the dangling reference problem, but they require coding discipline and have other potential issues (copyability, reference loops, etc.).

Also, in heavily multithreaded scenarios, new is a point of contention between threads; there can be a performance impact for overusing new. Stack object creation is by definition thread-local, since each thread has its own stack.

The downside of value objects is that they die once the host function returns - you cannot pass a reference to those back to the caller, only by copying, returning or moving by value.


  • C++ doesn't employ any memory manager by its own. Other languages like C#, Java has garbage collector to handle the memory
  • C++ implementations typically use operating system routines to allocate the memory and too much new/delete could fragment the available memory
  • With any application, if the memory is frequently being used it's advisable to pre-allocate it and release when not required.
  • Improper memory management could lead memory leaks and it's really hard to track. So using stack objects within the scope of function is a proven technique
  • The downside of using stack objects are, it creates multiple copies of objects on returning, passing to functions etc. However smart compilers are well aware of these situations and they've been optimized well for performance
  • It's really tedious in C++ if the memory being allocated and released in two different places. The responsibility for release is always a question and mostly we rely on some commonly accessible pointers, stack objects (maximum possible) and techniques like auto_ptr (RAII objects)
  • The best thing is that, you've control over the memory and the worst thing is that you will not have any control over the memory if we employ an improper memory management for the application. The crashes caused due to memory corruptions are the nastiest and hard to trace.

I see that a few important reasons for doing as few new's as possible are missed:

Operator new has a non-deterministic execution time

Calling new may or may not cause the OS to allocate a new physical page to your process this can be quite slow if you do it often. Or it may already have a suitable memory location ready, we don't know. If your program needs to have consistent and predictable execution time (like in a real-time system or game/physics simulation) you need to avoid new in your time critical loops.

Operator new is an implicit thread synchronization

Yes you heard me, your OS needs to make sure your page tables are consistent and as such calling new will cause your thread to acquire an implicit mutex lock. If you are consistently calling new from many threads you are actually serialising your threads (I've done this with 32 CPUs, each hitting on new to get a few hundred bytes each, ouch! that was a royal p.i.t.a. to debug)

The rest such as slow, fragmentation, error prone, etc have already been mentioned by other answers.


Pre-C++17:

Because it is prone to subtle leaks even if you wrap the result in a smart pointer.

Consider a "careful" user who remembers to wrap objects in smart pointers:

foo(shared_ptr<T1>(new T1()), shared_ptr<T2>(new T2()));

This code is dangerous because there is no guarantee that either shared_ptr is constructed before either T1 or T2. Hence, if one of new T1() or new T2() fails after the other succeeds, then the first object will be leaked because no shared_ptr exists to destroy and deallocate it.

Solution: use make_shared.

Post-C++17:

This is no longer a problem: C++17 imposes a constraint on the order of these operations, in this case ensuring that each call to new() must be immediately followed by the construction of the corresponding smart pointer, with no other operation in between. This implies that, by the time the second new() is called, it is guaranteed that the first object has already been wrapped in its smart pointer, thus preventing any leaks in case an exception is thrown.

A more detailed explanation of the new evaluation order introduced by C++17 was provided by Barry in another answer.

Thanks to @Remy Lebeau for pointing out that this is still a problem under C++17 (although less so): the shared_ptr constructor can fail to allocate its control block and throw, in which case the pointer passed to it is not deleted.

Solution: use make_shared.


To a great extent, that's someone elevating their own weaknesses to a general rule. There's nothing wrong per se with creating objects using the new operator. What there is some argument for is that you have to do so with some discipline: if you create an object you need to make sure it's going to be destroyed.

The easiest way of doing that is to create the object in automatic storage, so C++ knows to destroy it when it goes out of scope:

 {
    File foo = File("foo.dat");

    // do things

 }

Now, observe that when you fall off that block after the end-brace, foo is out of scope. C++ will call its dtor automatically for you. Unlike Java, you don't need to wait for the GC to find it.

Had you written

 {
     File * foo = new File("foo.dat");

you would want to match it explicitly with

     delete foo;
  }

or even better, allocate your File * as a "smart pointer". If you aren't careful about that it can lead to leaks.

The answer itself makes the mistaken assumption that if you don't use new you don't allocate on the heap; in fact, in C++ you don't know that. At most, you know that a small amout of memory, say one pointer, is certainly allocated on the stack. However, consider if the implementation of File is something like

  class File {
    private:
      FileImpl * fd;
    public:
      File(String fn){ fd = new FileImpl(fn);}

then FileImpl will still be allocated on the stack.

And yes, you'd better be sure to have

     ~File(){ delete fd ; }

in the class as well; without it, you'll leak memory from the heap even if you didn't apparently allocate on the heap at all.


new() shouldn't be used as little as possible. It should be used as carefully as possible. And it should be used as often as necessary as dictated by pragmatism.

Allocation of objects on the stack, relying on their implicit destruction, is a simple model. If the required scope of an object fits that model then there's no need to use new(), with the associated delete() and checking of NULL pointers. In the case where you have lots of short-lived objects allocation on the stack should reduce the problems of heap fragmentation.

However, if the lifetime of your object needs to extend beyond the current scope then new() is the right answer. Just make sure that you pay attention to when and how you call delete() and the possibilities of NULL pointers, using deleted objects and all of the other gotchas that come with the use of pointers.


When you use new, objects are allocated to the heap. It is generally used when you anticipate expansion. When you declare an object such as,

Class var;

it is placed on the stack.

You will always have to call destroy on the object that you placed on the heap with new. This opens the potential for memory leaks. Objects placed on the stack are not prone to memory leaking!


One notable reason to avoid overusing the heap is for performance -- specifically involving the performance of the default memory management mechanism used by C++. While allocation can be quite quick in the trivial case, doing a lot of new and delete on objects of non-uniform size without strict order leads not only to memory fragmentation, but it also complicates the allocation algorithm and can absolutely destroy performance in certain cases.

That's the problem that memory pools where created to solve, allowing to to mitigate the inherent disadvantages of traditional heap implementations, while still allowing you to use the heap as necessary.

Better still, though, to avoid the problem altogether. If you can put it on the stack, then do so.


I think the poster meant to say You do not have to allocate everything on theheap rather than the the stack.

Basically objects are allocated on the stack (if the object size allows, of course) because of the cheap cost of stack-allocation, rather than heap-based allocation which involves quite some work by the allocator, and adds verbosity because then you have to manage data allocated on the heap.


I tend to disagree with the idea of using new "too much". Though the original poster's use of new with system classes is a bit ridiculous. (int *i; i = new int[9999];? really? int i[9999]; is much clearer.) I think that is what was getting the commenter's goat.

When you're working with system objects, it's very rare that you'd need more than one reference to the exact same object. As long as the value is the same, that's all that matters. And system objects don't typically take up much space in memory. (one byte per character, in a string). And if they do, the libraries should be designed to take that memory management into account (if they're written well). In these cases, (all but one or two of the news in his code), new is practically pointless and only serves to introduce confusions and potential for bugs.

When you're working with your own classes/objects, however (e.g. the original poster's Line class), then you have to begin thinking about the issues like memory footprint, persistence of data, etc. yourself. At this point, allowing multiple references to the same value is invaluable - it allows for constructs like linked lists, dictionaries, and graphs, where multiple variables need to not only have the same value, but reference the exact same object in memory. However, the Line class doesn't have any of those requirements. So the original poster's code actually has absolutely no needs for new.


Two reasons:

  1. It's unnecessary in this case. You're making your code needlessly more complicated.
  2. It allocates space on the heap, and it means that you have to remember to delete it later, or it will cause a memory leak.

new is the new goto.

Recall why goto is so reviled: while it is a powerful, low-level tool for flow control, people often used it in unnecessarily complicated ways that made code difficult to follow. Furthermore, the most useful and easiest to read patterns were encoded in structured programming statements (e.g. for or while); the ultimate effect is that the code where goto is the appropriate way to is rather rare, if you are tempted to write goto, you're probably doing things badly (unless you really know what you're doing).

new is similar — it is often used to make things unnecessarily complicated and harder to read, and the most useful usage patterns can be encoded have been encoded into various classes. Furthermore, if you need to use any new usage patterns for which there aren't already standard classes, you can write your own classes that encode them!

I would even argue that new is worse than goto, due to the need to pair new and delete statements.

Like goto, if you ever think you need to use new, you are probably doing things badly — especially if you are doing so outside of the implementation of a class whose purpose in life is to encapsulate whatever dynamic allocations you need to do.


The core reason is that objects on heap are always difficult to use and manage than simple values. Writing code that are easy to read and maintain is always the first priority of any serious programmer.

Another scenario is the library we are using provides value semantics and make dynamic allocation unnecessary. Std::string is a good example.

For object oriented code however, using a pointer - which means use new to create it beforehand - is a must. In order to simplify the complexity of resource management, we have dozens of tools to make it as simple as possible, such as smart pointers. The object based paradigm or generic paradigm assumes value semantics and requires less or no new, just as the posters elsewhere stated.

Traditional design patterns, especially those mentioned in GoF book, use new a lot, as they are typical OO code.


One more point to all the above correct answers, it depends on what sort of programming you are doing. Kernel developing in Windows for example -> The stack is severely limited and you might not be able to take page faults like in user mode.

In such environments, new, or C-like API calls are prefered and even required.

Of course, this is merely an exception to the rule.


new allocates objects on the heap. Otherwise, objects are allocated on the stack. Look up the difference between the two.

참고URL : https://stackoverflow.com/questions/6500313/why-should-c-programmers-minimize-use-of-new

반응형