C ++ 프로그래머가 왜 'new'사용을 최소화해야합니까?


873

std :: list <std :: string>을 사용할 때 스택 오버플로 질문 메모리 누수가 std :: string으로 우연히 발견되었으며 주석 중 하나가 다음 과 같이 말합니다.

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

나는 그가 무슨 뜻인지 잘 모르겠습니다.

C ++ 에서 개체 를 가능한 한 자주 값으로 만들어야하는 이유는 무엇이며 내부적으로 어떤 차이가 있습니까?
답을 잘못 해석 했습니까?

답변:


1037

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

스택

스택은 항상 순차적으로 메모리를 할당합니다. 메모리를 역순으로 해제해야하기 때문에 그렇게 할 수 있습니다 (선입 선출, 최종 출발 : 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인스턴스 및 모든 문자열의 내용을 할당 하며 모든 것이 자동으로 해제 됩니다.


55
마지막에 RAII를 언급 한 경우 +1이지만 예외 및 스택 해제에 대한 내용이 있어야합니다.
Tobu

7
@Tobu : 예,하지만이 게시물은 이미 길기 때문에 OP의 질문에 계속 집중하고 싶었습니다. 블로그 게시물이나 그 밖의 것을 작성하고 여기에서 링크하겠습니다.
André Caron

15
스택 할당에 대한 단점 (적어도 C ++ 1x까지) 을 언급하는 훌륭한 보충 자료가 될 것입니다.주의하지 않으면 불필요하게 복사 해야하는 경우가 종종 있습니다. 예를 들어이 Monster밖으로 옷을 사는 Treasure받는 사람 World이 죽는다. 그것의 Die()방법으로 그것은 세상에 보물을 추가합니다. world->Add(new Treasure(/*...*/))보물이 죽은 후에는 다른 보물을 보존 해야합니다 . 대안은 shared_ptr(과도하게 남을 수 있음), auto_ptr(소유권 양도에 대한 의미가 불충분 함), 가치를 지나치거나 (폐기 됨) move+ unique_ptr(아직 널리 구현되지 않음)입니다.
kizzx2 2016 년

7
스택 할당 로컬 변수에 대해 말한 내용이 약간 잘못 될 수 있습니다. "스택"은 스택 프레임 을 저장하는 호출 스택을 나타냅니다 . LIFO 방식으로 저장된 스택 프레임입니다. 특정 프레임의 로컬 변수는 마치 마치 구조체의 멤버 인 것처럼 할당됩니다.
someguy

7
@ someguy : 사실, 설명은 완벽하지 않습니다. 구현은 할당 정책에 자유가 있습니다. 그러나 변수는 LIFO 방식으로 초기화되고 파괴되어야하므로 유사성이 유지됩니다. 나는 그것이 더 이상 답을 복잡하게 만드는 일이라고 생각하지 않습니다.
André Caron

171

스택이 더 빠르고 누출 방지 기능이 있기 때문에

C ++에서는 주어진 함수의 모든 로컬 범위 객체에 대해 공간을 스택에 할당하는 단일 명령이 필요하며 해당 메모리를 누출하는 것은 불가능합니다. 이 의견은 "힙을 사용하지 말고 스택을 사용하십시오" 와 같은 말을하려는 것 입니다.


18
"공간을 할당하기 위해 단 하나의 명령 만 필요합니다"– 오, 말도 안됩니다. 물론 스택 포인터에 추가하는 데 단 하나의 명령이 필요하지만 클래스에 흥미로운 내부 구조가 있으면 스택 포인터에 추가하는 것보다 훨씬 많은 것이 있습니다. 컴파일러는 컴파일 타임에 참조를 관리하기 때문에 Java에서는 공간을 할당하는 명령이 없다고 말하는 것도 똑같이 유효합니다.
Charlie Martin

31
@Charlie가 맞습니다. 자동 변수는 빠르고 정확하며 더 정확합니다.
Oliver Charlesworth

28
@Charlie : 클래스 내부는 어느 쪽이든 설정해야합니다. 필요한 공간 할당을 비교하고 있습니다.
Oliver Charlesworth

51
기침 int x; return &x;
peterchen

16
빨리 그렇습니다. 그러나 확실하지 않습니다. 아무것도 아닙니다. 당신은 StackOverflow에 :)를 얻을 수 있습니다
rxantos

107

왜 복잡한가.

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

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

이것은 간단합니다. 그러나 "작업 수행"에서 예외가 발생하면 어떻게됩니까? 죄송합니다 : 메모리 누수. "물건"이 return일찍 발생하면 어떻게됩니까 ? 죄송합니다 : 메모리 누수.

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

또는, 당신은 이것을 할 수 있습니다 :

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

없음 delete. "스택"에 오브젝트가 작성되었으며 범위를 벗어나면 소멸됩니다. 객체를 반환하여 내용을 호출 함수로 전송할 수도 있습니다. 객체를 함수에 전달할 수 있습니다 (일반적으로 참조 또는 const-reference : void SomeFunc(std::string &iCanModifyThis, const std::string &iCantModifyThis)등).

new와 없이 delete. 누가 메모리를 소유하는지 또는 누가 메모리를 삭제할 책임이 있는지에 대해서는 의문의 여지가 없습니다. 당신이 할 경우 :

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

는 것을 알 수있다 otherString의 복사본을 가지고 데이터 의이 someString. 포인터가 아닙니다. 별도의 개체입니다. 내용은 동일 할 수 있지만 다른 내용에는 영향을주지 않고 변경할 수 있습니다.

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

아이디어를 보십니까?


1
참고 사항 ...에서 객체가 동적으로 할당되고 main()프로그램 기간 동안 존재하는 경우 상황으로 인해 스택에서 쉽게 만들 수 없으며 객체에 대한 포인터가 액세스 해야하는 모든 함수에 전달됩니다. , 프로그램 충돌시 누수가 발생할 수 있습니까, 아니면 안전합니까? 나는 모든 프로그램의 메모리를 할당 해제하는 OS가 논리적으로 할당 해제해야하기 때문에 후자를 가정하지만,에 관해서는 아무것도 가정하고 싶지 않습니다 new.
저스틴 타임-복원 모니카

4
@JustinTime 프로그램의 수명 동안 유지 될 동적으로 할당 된 객체의 메모리를 해제하는 것에 대해 걱정할 필요가 없습니다. 프로그램이 실행될 때, OS는 물리적 메모리 또는 가상 메모리의 아틀라스를 생성합니다. 가상 메모리 공간의 모든 주소는 실제 메모리의 주소에 매핑되며 프로그램이 종료되면 가상 메모리에 매핑 된 모든 항목이 해제됩니다. 따라서 프로그램이 완전히 종료되는 한 할당 된 메모리가 절대 삭제되지 않을까 걱정할 필요가 없습니다.
Aiman ​​Al-Eryani

75

에 의해 생성 된 객체 new는 결국 delete누출되지 않아야합니다 . 소멸자는 호출되지 않으며 메모리는 해제되지 않습니다. C ++에는 가비지 콜렉션이 없으므로 문제가됩니다.

값을 기준으로 작성된 개체 (예 : 스택)는 범위를 벗어나면 자동으로 죽습니다. 소멸자 호출은 컴파일러에 의해 삽입되며 함수 반환시 메모리가 자동으로 해제됩니다.

스마트 포인터 좋아 unique_ptr, shared_ptr매달려있는 참조 문제를 해결하지만, 코딩 훈련을 필요로하고 다른 잠재적 인 문제 (copyability, 참조 루프 등)이있다.

또한 멀티 스레드가 많은 시나리오에서는 new스레드 간의 경합 지점입니다. 과도하게 사용하면 성능에 영향을 줄 수 있습니다 new. 각 스레드에는 자체 스택이 있으므로 스택 객체 생성은 스레드 로컬 정의입니다.

가치 객체의 단점은 호스트 함수가 반환되면 객체가 죽는다는 것입니다. 값으로 복사, 반환 또는 이동해야만 호출자에게 참조를 전달할 수 없습니다.


9
+1. Re "에 의해 생성 된 개체 new는 결국 delete누출되지 않아야합니다 ." -더 안타깝게도 와 new[]일치해야하며 메모리 또는 메모리가 부족한 경우 컴파일러에 대해 경고하는 경우가 거의 없습니다 (Cppcheck와 같은 일부 도구는 가능할 때 수행). delete[]delete new[]delete[] new
Tony Delroy 2016 년

3
@TonyDelroy 컴파일러가이를 경고 할 수없는 상황이 있습니다. 함수가 포인터를 반환하면 new (단일 요소) 또는 new [] 인 경우 만들 수 있습니다.
fbafelipe 2018 년

32
  • C ++은 자체 메모리 관리자를 사용하지 않습니다. C #과 같은 다른 언어, Java에는 메모리를 처리하는 가비지 수집기가 있습니다.
  • C ++ 구현은 일반적으로 운영 체제 루틴을 사용하여 메모리를 할당하며 너무 많은 새 / 삭제는 사용 가능한 메모리를 조각화 할 수 있습니다.
  • 모든 응용 프로그램에서 메모리를 자주 사용하는 경우 메모리를 미리 할당하고 필요하지 않은 경우 해제하는 것이 좋습니다.
  • 부적절한 메모리 관리로 인해 메모리 누수가 발생할 수 있으며 실제로 추적하기가 어렵습니다. 따라서 기능 범위 내에서 스택 객체를 사용하는 것이 입증 된 기술입니다.
  • 스택 객체를 사용하는 단점은 반환, 함수 전달 등으로 객체의 여러 복사본을 만듭니다. 그러나 스마트 컴파일러는 이러한 상황을 잘 알고 있으며 성능에 맞게 최적화되었습니다.
  • 메모리가 두 곳에서 할당되고 해제되면 C ++에서는 정말 지루합니다. 릴리스에 대한 책임은 항상 의문 사항이며 대부분 일반적으로 액세스 가능한 포인터, 스택 객체 (최대 가능) 및 auto_ptr (RAII 객체)과 같은 기술에 의존합니다.
  • 가장 좋은 점은 메모리를 제어하고 가장 나쁜 점은 응용 프로그램에 부적절한 메모리 관리를 사용하면 메모리를 제어 할 수 없다는 것입니다. 메모리 손상으로 인한 충돌은 가장 까다 롭고 추적하기가 어렵습니다.

5
실제로 메모리를 할당하는 모든 언어에는 c를 포함하여 메모리 관리자가 있습니다. 대부분은 매우 간단하다. 즉 int * x = malloc (4); int * y = malloc (4); ... 첫 번째 호출은 메모리를 할당하기 위해 메모리를 할당합니다 (일반적으로 청크 1k / 4k). 두 번째 호출은 실제로 메모리를 할당하지 않지만 할당 된 마지막 청크 조각을 제공합니다. IMO 가비지 수집기는 메모리의 자동 할당 해제 만 처리하기 때문에 메모리 관리자가 아닙니다. 메모리 관리자라고하려면 메모리 할당 해제뿐만 아니라 메모리 할당도 처리해야합니다.
Rahly

1
로컬 변수는 스택을 사용하므로 컴파일러는 malloc()필요한 메모리를 할당하기 위해 호출자 또는 그 친구에게 호출을 보내지 않습니다 . 그러나 스택은 스택 내에서 어떤 항목도 해제 할 수 없으며, 스택 메모리가 해제되는 유일한 방법은 스택 맨 위에서 해제하는 것입니다.
Mikko Rantalainen

C ++은 "운영 체제 루틴을 사용하지 않습니다"; 그것은 언어의 일부가 아니며, 일반적인 구현 일뿐입니다. C ++은 운영 체제 없이도 실행될 수 있습니다.
einpoklum

22

가능한 한 적은 수의 새로운 작업을 수행해야하는 몇 가지 중요한 이유가 누락되었습니다.

new비 결정적 실행 시간이있는 운영자

호출 new하면 OS에서 프로세스에 새 실제 페이지를 할당하거나 발생시키지 않을 수 있습니다. 자주 수행하는 경우 속도가 느려질 수 있습니다. 또는 이미 적절한 메모리 위치가 준비되어있을 수도 있습니다. 프로그램이 실시간 시스템이나 게임 / 물리 시뮬레이션과 같이 일관되고 예측 가능한 실행 시간을 가져야하는 경우 new시간이 중요한 루프 를 피해야 합니다.

연산자 new는 암시 적 스레드 동기화입니다

네, 들었습니다 .OS는 페이지 테이블이 일관성을 유지해야하며 그러한 호출 new로 인해 스레드가 암시 적 뮤텍스 잠금을 획득하게해야합니다. new많은 스레드에서 일관되게 호출하는 경우 실제로 스레드를 직렬화하고 있습니다 (32 개의 CPU 로이 작업을 수행했습니다. 각 CPU는 각각 new수백 바이트를 얻습니다.

slow, fragmentation, error prone 등과 같은 나머지는 이미 다른 답변에서 언급되었습니다.


2
신규 / 삭제 배치를 사용하고 미리 메모리를 할당하면 둘 다 피할 수 있습니다. 또는 메모리를 직접 할당 / 해제 한 다음 생성자 / 소멸자를 호출 할 수 있습니다. 이것이 std :: vector가 일반적으로 작동하는 방식입니다.
rxantos

1
@rxantos OP를 읽으십시오.이 질문은 불필요한 메모리 할당을 피하는 것에 관한 것입니다. 또한 게재 위치 삭제가 없습니다.
Emily L.

@Emily 이것은 OP가 의미하는 void * someAddress = ...; delete (T*)someAddress
바입니다

1
스택 사용은 실행 시간에서도 결정적이지 않습니다. 전화를 걸 mlock()거나 비슷한 것을 하지 않는 한 . 시스템의 메모리가 부족하고 스택에 사용할 수있는 물리적 메모리 페이지가 없기 때문에 OS가 실행을 진행하기 전에 일부 캐시 (깨끗한 더티 메모리)를 디스크에 스왑하거나 기록해야 할 수 있기 때문입니다.
Mikko Rantalainen

1
@mikkorantalainen 그것은 기술적으로 사실이지만 메모리가 부족한 상황에서는 디스크로 밀어 넣을 때 수행 할 수있는 작업이 없으므로 어쨌든 모든 베팅이 중단됩니다. 어쨌든 새로운 전화를 피하기 위해 조언을 무효화하지는 않습니다.
Emily L.

21

Pre-C ++ 17 :

스마트 포인터로 결과를 래핑하더라도 미묘한 누출이 발생하기 쉽습니다 .

스마트 포인터로 객체를 감싸는 것을 기억하는 "주의"사용자를 고려하십시오.

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

이 코드는 또는 중 하나 전에 생성 되었다는 보장없기 때문에 위험 합니다 . 따라서 다른 하나가 성공한 후 실패 하거나 실패한 경우 첫 번째 오브젝트는 오브젝트 를 제거하고 할당을 해제 할 수 없기 때문에 누출 됩니다.shared_ptrT1T2new T1()new T2()shared_ptr

해결책 :을 사용하십시오 make_shared.

포스트 C ++ 17 :

이것은 더 이상 문제가되지 않습니다. C ++ 17은 이러한 작업 순서에 제약을가합니다.이 경우 각 호출 new()은 그 뒤에 다른 작업없이 해당 스마트 포인터를 즉시 구성해야합니다. 이것은 두 번째 new()가 호출 될 때 첫 번째 오브젝트가 이미 스마트 포인터에 랩핑되어 있음을 의미하므로 예외가 발생할 경우 누수가 방지됩니다.

C ++ 17에 의해 도입 된 새로운 평가 순서에 대한 더 자세한 설명은 Barry 의해 또 다른 대답 으로 제공되었습니다 .

@Remy Lebeau 덕분 에 C ++ 17에서 여전히 문제 가 있음을 지적 해 주셔서 감사합니다 (생성에도 불구하고). shared_ptr생성자가 제어 블록을 할당하고 throw하지 못할 수 있으며,이 경우 전달 된 포인터는 삭제되지 않습니다.

해결책 :을 사용하십시오 make_shared.


5
다른 솔루션 : 한 줄에 둘 이상의 객체를 동적으로 할당하지 마십시오.
안티몬

3
@Antimony : 그렇습니다. 이미 할당하지 않았을 때와 비교하여 이미 할당했을 때 둘 이상의 객체를 할당하는 것이 더 유혹적입니다.
user541686

1
더 좋은 대답은 예외가 호출되고 아무것도 포착하지 못하면 smart_ptr이 누출된다는 것입니다.
Natalie Adams

2
C ++ 17 new이후의 경우에도 성공하면 계속해서 누출이 발생할 수 있으며 후속 shared_ptr구성은 실패합니다. std::make_shared()그것도 해결할 것입니다
Remy Lebeau

1
@Mehrdad 문제의 shared_ptr생성자는 공유 포인터와 삭제기를 저장하는 제어 블록에 메모리를 할당하므로 이론적으로 메모리 오류가 발생할 수 있습니다. 복사, 이동 및 앨리어싱 생성자 만 던지지 않습니다. make_shared제어 블록 자체 내에 공유 객체를 할당하므로 2 대신 할당이 1 개뿐입니다.
Remy Lebeau

17

대체로, 그것은 자신의 약점을 일반적인 규칙으로 높이는 사람입니다. 아무것도 잘못있다 그 자체 사용하여 개체를 만드는 new연산자. 몇 가지 주장은 당신이 어떤 훈련을 통해 그렇게해야한다는 것입니다. 만약 당신이 객체를 생성한다면 당신은 그것을 파괴해야합니다.

가장 쉬운 방법은 자동 스토리지에서 오브젝트를 작성하는 것이므로 C ++은 범위를 벗어날 때 오브젝트를 삭제하는 것을 알고 있습니다.

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

    // do things

 }

이제 엔드 브레이스 이후 해당 블록에서 떨어지면 범위를 벗어난 것을 관찰하십시오 foo. C ++는 자동으로 dtor를 호출합니다. Java와 달리 GC가이를 찾을 때까지 기다릴 필요가 없습니다.

당신이 쓴

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

당신은 그것을 명시 적으로 일치시키고 싶을 것입니다

     delete foo;
  }

또는 더 나은 방법 File *으로 "스마트 포인터"로 할당하십시오 . 주의하지 않으면 누출이 발생할 수 있습니다.

대답 자체는 사용 new하지 않으면 힙에 할당 하지 않는다고 잘못 가정합니다 . 사실, C ++에서는 그 사실을 모릅니다. 기껏해야 하나의 포인터와 같이 적은 양의 메모리가 스택에 확실히 할당되어 있음을 알고 있습니다. 그러나 File 구현이 다음과 같은지 고려하십시오.

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

다음 FileImpl것이다 여전히 스택에 할당 될 수있다.

그리고 네, 당신은 더 잘해야합니다.

     ~File(){ delete fd ; }

수업에서도; 당신이하지 않은 경우에도 그것없이, 당신은 힙에서 메모리 누수가 있습니다 분명히 전혀 힙에 할당합니다.


4
참조 된 질문의 코드를 살펴 봐야합니다. 해당 코드에는 많은 문제가 있습니다.
André Caron

7
나는 new 그 자체 를 사용하는 데 아무런 문제가 없다는 데 동의 하지만, 주석이 언급 한 원래 코드를 보면 new남용되고 있습니다. 코드는 Java 또는 C #과 같이 작성 new되며, 실제로 모든 변수에 사용됩니다.
luke

5
페어 포인트. 그러나 일반적인 규칙은 일반적으로 일반적인 함정을 피하기 위해 시행됩니다. 이것이 개인의 약점이든 아니든, 메모리 관리는 이와 같은 일반적인 규칙을 보증 할만큼 복잡합니다! :)
Robben_Ford_Fan_boy 2016 년

9
@Charlie는 : 코멘트는 않습니다 없습니다 당신이 사용하지 않을해야한다고 new. 동적 할당과 자동 스토리지 중에서 선택할 수 있는 경우 자동 스토리지를 사용하십시오.
André Caron

8
@Charlie :을 사용하는 데 아무런 문제가 new없지만을 사용 delete하면 잘못하고 있습니다!
Matthieu M.

16

new()가능한 적게 사용해서는 안됩니다 . 최대한 조심스럽게 사용해야 합니다. 그리고 그것은 실용주의에 의해 요구되는만큼 자주 사용되어야합니다.

암시 적 파괴에 의존하여 스택에 객체를 할당하는 것은 간단한 모델입니다. 객체의 필수 범위가 해당 모델에 맞는 경우 NULL 포인터를 new()연결 delete()하고 확인하면서을 사용할 필요가 없습니다 . 스택에 수명이 짧은 객체가 많이있는 경우 힙 조각화 문제를 줄여야합니다.

그러나 객체의 수명이 현재 범위를 넘어 확장해야하는 경우 new()정답입니다. delete()삭제 된 객체와 포인터 사용과 함께 제공되는 다른 모든 문제를 사용하여 호출 시점과 방법 및 NULL 포인터의 가능성에 주의를 기울여야합니다 .


9
"객체의 수명이 현재 범위를 넘어서 확장해야하는 경우 new ()가 정답입니다."우선적으로 값으로 반환하거나 constref 또는 포인터 가 아닌 호출자 범위 변수를 허용하지 않는 이유는 무엇입니까?
Tony Delroy 2016 년

2
@ 토니 : 네! 누군가 언급 한 내용을 듣게되어 기쁩니다. 이 문제를 방지하기 위해 만들어졌습니다.
Nathan Osman

@TonyD ... 또는 그것들을 결합하십시오 : 값으로 스마트 포인터를 반환하십시오. 이렇게하면 발신자와 많은 경우 (즉, make_shared/_unique사용 가능한 경우) 수신자가 new또는에 필요하지 않습니다 delete. (A) C ++은 RVO, 이동 의미론 및 출력 매개 변수와 같은 기능을 제공합니다. 이는 동적으로 할당 된 메모리를 반환하여 객체 생성 및 수명 연장을 처리하는 것이 불필요하고 부주의하게됨을 의미합니다. (B) 동적 할당이 필요한 상황에서도 stdlib는 사용자에게 추악한 내부 세부 사항을 완화하는 RAII 래퍼를 제공합니다.
underscore_d

14

new를 사용하면 오브젝트가 힙에 할당됩니다. 일반적으로 확장을 예상 할 때 사용됩니다. 다음과 같은 객체를 선언하면

Class var;

스택에 배치됩니다.

항상 힙에 배치 한 객체에 대해 new를 사용하여 destroy를 호출해야합니다. 메모리 누수가 발생할 가능성이 있습니다. 스택에있는 객체는 메모리 누수가 발생하지 않습니다!


2
+1 "[heap]은 일반적으로 확장을 예상 할 때 사용됩니다"– std::string또는 std::map예, 예리한 통찰력을 추가하는 것과 같습니다 . 내 초기 반응은 "코드 생성 범위에서 객체의 수명을 분리하는 데 매우 일반적으로 사용 되었음"이지만 실제로 const는 "확장"이 포함 된 경우를 제외하고 값으로 반환하거나 비 참조 또는 포인터로 호출자 범위 값을 수락하는 것이 좋습니다. 너무. 팩토리 메소드와 같은 다른 사운드 사용이 있습니다 ....
Tony Delroy

12

힙을 과도하게 사용하지 않는 한 가지 주목할만한 이유는 특히 C ++에서 사용하는 기본 메모리 관리 메커니즘의 성능과 관련된 성능 때문입니다. 할당이 사소한 경우에 매우 빨리 할 수 있지만, 많은 일을 new하고 delete뿐만 아니라 메모리 조각화에 엄격한 주문 리드없이 비 균일 한 크기의 개체를, 그러나 또한 할당 알고리즘을 복잡하게하고 절대적으로 어떤 경우에 성능을 파괴 할 수있다.

그것이 해결하기 위해 메모리 풀이 생성되어 기존 힙 구현의 고유 한 단점을 완화하면서 필요한 힙을 계속 사용할 수있게하는 문제입니다.

그래도 문제를 완전히 피하는 것이 좋습니다. 스택에 넣을 수 있다면 그렇게하십시오.


항상 상당한 양의 메모리를 할당 한 다음 속도에 문제가있는 경우 배치 새로 만들기 / 삭제를 사용할 수 있습니다.
rxantos

메모리 풀은 조각화를 피하고 할당 해제 속도를 높이고 (수천 개의 객체에 대해 하나의 할당 해제) 할당 해제를보다 안전하게 만들어야합니다.
Lothar

10

나는 포스터 말을 의미 생각 You do not have to allocate everything on theheap오히려 댄 stack.

기본적으로 객체는 할당 기의 상당한 작업을 필요로하는 힙 기반 할당보다 저렴한 스택 할당 비용으로 인해 스택에 할당됩니다 (물론 객체 크기가 허용되는 경우). 힙에 할당 된 데이터를 관리합니다.


10

나는 새로운 "너무 많은"을 사용한다는 생각에 동의하지 않는 경향이 있습니다. 원래 포스터에서 시스템 클래스와 함께 새로운 것을 사용하는 것은 약간 어리 석습니다. ( int *i; i = new int[9999];? 정말로? int i[9999];는 훨씬 더 명확하다.) 나는 그것이 논평자의 염소를 얻는 것이라고 생각 한다 .

이 시스템 객체와 작업 할 때, 그건 아주 당신이 동일한 개체에 대한 하나 이상의 참조가 필요 거라고 드물다. 가치가 동일하기 만하면됩니다. 그리고 시스템 객체는 일반적으로 메모리에서 많은 공간을 차지하지 않습니다. (문자열에서 문자 당 1 바이트). 그리고 만약 그렇다면, 라이브러리는 메모리 관리를 고려하여 설계되어야합니다 (만약 잘 작성 되었다면). 이러한 경우 (자신의 코드에서 한두 가지 뉴스 만 제외하고) new는 사실상 무의미하며 혼란과 버그 가능성을 소개하는 역할 만합니다.

그러나 자신의 클래스 / 객체 (예 : 원래 포스터의 Line 클래스)로 작업 할 때 메모리 풋 프린트, 데이터 지속성 등과 같은 문제에 대해 생각해야합니다. 이 시점에서 동일한 값에 대한 여러 참조를 허용하는 것은 매우 중요합니다. 링크 된 목록, 사전 및 그래프와 같은 구성을 통해 여러 변수가 동일한 값을 가질뿐만 아니라 메모리에서 정확히 동일한 객체 를 참조해야 합니다. 그러나 Line 클래스에는 이러한 요구 사항이 없습니다. 따라서 원래 포스터의 코드는 실제로 전혀 필요하지 않습니다 new.


일반적으로 배열의 크기를 미리 알지 못하면 새 / 삭제가 사용됩니다. 물론 std :: vector는 당신을 위해 새로운 / 삭제를 숨 깁니다. 여전히 사용하지만 std :: vector를 통과합니다. 그래서 오늘날 배열의 크기를 모르고 어떤 이유로 std :: vector의 오버 헤드를 피하고 싶을 때 사용됩니다 (작지만 작지만 여전히 존재합니다).
rxantos

When you're working with your own classes/objects... 그럴 이유가 없습니다! 숙련 된 코더에 의한 컨테이너 디자인의 세부 사항에 대한 Q의 적은 비율입니다. 대조적으로 우울한 비율 stdlib의 존재를 모르거나 '프로그래밍' '코스'에서 끔찍한 임무를 부여받은 초보자의 혼란에 관한 것입니다. 바퀴가 무엇이고 작동 하는지 배웠습니다 . 보다 추상적 인 할당을 촉진함으로써 C ++는 C의 끝없는 'segfault with linked list'에서 우리를 구할 수 있습니다. , 이제 주시기 바랍니다 그것을 보자 .
underscore_d

" 시스템 클래스와 함께 새로운 포스터를 사용하는 것은 다소 말도 안됩니다. ( int *i; i = new int[9999];? 정말로? int i[9999];는 훨씬 더 명확합니다.)" 예, 더 명확하지만 악마의 옹호자를하기 위해 형식이 반드시 나쁜 주장은 아닙니다. 9999 개의 요소를 사용하면 9999 개의 요소에 대한 스택이 충분하지 않은 단단한 임베디드 시스템을 상상할 수 있습니다. 9999x4 바이트는 ~ 40 kB, x8 ~ 80 kB입니다. 따라서 이러한 시스템은 대체 메모리를 사용하여 구현한다고 가정 할 때 동적 할당을 사용해야 할 수도 있습니다. 여전히, 그것은 동적 할당을 정당화 할 수있을뿐 아니라 new; vector이 경우에 실제 수정 될 것이다
underscore_d

@underscore_d에 동의하십시오-그것은 좋은 예가 아닙니다. 나는 내 스택에 40,000 또는 80,000 바이트를 추가하지 않을 것입니다. 실제로 힙에 할당 할 것입니다 ( std::make_unique<int[]>()물론).
einpoklum

3

두 가지 이유 :

  1. 이 경우에는 불필요합니다. 코드를 불필요하게 더 복잡하게 만들고 있습니다.
  2. 힙에 공간을 할당 delete하므로 나중에 기억해야 합니다. 그렇지 않으면 메모리 누수가 발생할 수 있습니다.

2

new새로운 것 goto입니다.

리콜은 왜 goto그렇게 욕을한다 : 그것은 흐름 제어를위한 강력한, 낮은 수준의 도구 인 반면, 사람들이 종종 따라 어려운 코드를 만들어 불필요하게 복잡한 방법을 사용했다. 또한, 가장 유용하고 읽기 쉬운 패턴은 구조화 된 프로그래밍 문 (예 : for또는 while) 으로 인코딩되었습니다 . 궁극적 인 효과는 goto적절한 방법이 있는 코드 가 다소 드물다 goto는 것입니다. 작성하려는 유혹을 받으면 실제로 일을 잘못 알지 않는 한 아마도 일을 잘못하고있을 것입니다 .

new유사하다-종종 불필요하게 복잡하고 읽기 어렵게 만드는 데 사용되며, 가장 유용한 사용 패턴을 다양한 클래스로 인코딩 할 수있다. 또한 아직 표준 클래스가없는 새로운 사용 패턴을 사용해야하는 경우이를 코딩하는 고유 클래스를 작성할 수 있습니다!

난 그 주장 new입니다 이상 goto인한 쌍의 필요성, new그리고 delete문.

마찬가지로 goto, new당신이 사용해야한다고 생각한다면 , 아마도 일을 잘못하고있을 것입니다. 특히 당신이해야 할 동적 할당을 캡슐화하는 것이 인생의 목적이 클래스의 구현 외부에서 그렇게하는 경우에 특히 그렇습니다.


그리고 저는 "기본적으로 필요하지 않습니다"라고 덧붙입니다.
einpoklum

1

핵심적인 이유는 힙의 객체가 단순한 값보다 항상 사용하고 관리하기 어렵 기 때문입니다. 읽고 관리하기 쉬운 코드 작성은 항상 모든 심각한 프로그래머의 최우선 과제입니다.

또 다른 시나리오는 우리가 사용하는 라이브러리가 가치 의미론을 제공하고 동적 할당을 불필요하게 만듭니다. Std::string좋은 예입니다.

그러나 객체 지향 코드의 경우 포인터 new를 사용하여 미리 작성해야합니다. 리소스 관리의 복잡성을 단순화하기 위해 스마트 포인터와 같이 가능한 한 간단하게 만드는 수십 가지 도구가 있습니다. 객체 기반 패러다임 또는 일반 패러다임은 new다른 곳에서 포스터가 언급 한 것처럼 가치 의미론을 가정하고 덜 요구합니다 .

전통적인 디자인 패턴, 특히 GoF 책 에서 언급 된 패턴 new은 일반적인 OO 코드이므로 많이 사용 합니다.


4
이것은 끔찍한 대답입니다. For object oriented code, using a pointer [...] is a must: 말도 안됩니다 . 당신은 작은 부분 집합만을 참조하여 'OO'을 평가 절하하는 경우, 다형성 - 또한 넌센스 : 참조도 작동합니다. [pointer] means use new to create it beforehand: 특히 넌센스 : 참조 또는 포인터는 자동으로 할당 된 객체로 가져와 다형성으로 사용할 수 있습니다. 저를보고 . [typical OO code] use new a lot: 어쩌면 오래된 책이지만 누가 신경 쓰나요? 모든 막연 현대 C ++ 피하고 new/ 원시 포인터 가능한 - &입니다 어떠한 방식으로도 이렇게함으로써 덜 OO
underscore_d

1

위의 모든 정답을 하나 더 지적하면, 어떤 종류의 프로그래밍을하고 있는지에 달려 있습니다. 예를 들어 Windows에서 개발중인 커널-> 스택이 심각하게 제한되어 있으며 사용자 모드에서와 같이 페이지 오류가 발생하지 않을 수 있습니다.

이러한 환경에서는 새로운 또는 C와 유사한 API 호출이 선호되며 심지어 필요합니다.

물론 이것은 규칙에 대한 예외 일뿐입니다.


당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.