배치 구문

Placement syntax

C++ 프로그래밍 언어에서는 배치 구문을 사용하여 프로그래머가 개개의 오브젝트, 즉 메모리 내의 "배치"의 메모리 관리를 명시적으로 지정할 수 있습니다.일반적으로 개체가 동적으로 생성되면 할당 함수는 개체에 메모리를 할당하고 새로 할당된 메모리 내에서 개체를 초기화하도록 호출됩니다.배치 구문을 사용하면 프로그래머는 할당 함수에 추가 인수를 제공할 수 있습니다.일반적인 용도는 오브젝트를 초기화할 수 있는 적절한 스토리지 영역에 포인터를 제공하여 메모리 할당을 오브젝트 [citation needed]구축에서 분리하는 것입니다.

의 "배치" 버전은new그리고.delete연산자와 함수를 배치라고 합니다.new및 배치deleteA.[1]new expression, placement 또는 그 외의 방법으로 호출한다.new 할당자 함수라고도 하며 이름은operator new마찬가지로 a.delete 표현은 A를 호출한다.delete 디할로케이터 함수라고도 하며 이름은operator delete를 클릭합니다.[2][3]

조금도new배치 구문을 사용하는 표현식은 배치입니다.newexpression 및 anyoperator new또는operator delete필수 첫 번째 매개 변수보다 많은 값을 사용하는 함수(std::size_t)는 배치 신규 또는 배치 삭제 기능입니다.[4]배치 새 함수는 다음 두 가지 입력 파라미터를 사용합니다.std::size_t그리고.void *.

역사

이전 버전의 C++에서는 placement new와 같은 은 없었습니다.대신 개발자들은 유사한 [5]효과를 얻기 위해 컨스트럭터 내에서 이것에 대한 명시적인 할당을 사용했습니다. 관행은 나중에 폐지되고 폐지되었으며 C++ Programming Language 제3판에서는 이 기술에 대해 언급하지 않았습니다.

표현.

비배치용 표준 C++ 구문new표현은[2]

new new-type-id ( optional-initializer-expression-list )

placement 구문은 다음 명령어 바로 뒤에 식 목록을 추가합니다.new키워드를 지정합니다.이 식 리스트가 배치입니다.임의의 수의 [2][3][6]식을 포함할 수 있습니다.

new ( expression-list ) new-type-id ( optional-initializer-expression-list )

기능들

배치 새 함수는 배치되지 않은 새 함수의 오버로드입니다.비배치 및 배열에 대한 배치되지 않은 새 함수 선언new표현은 다음과 같습니다.[7][8]

    무효* 교환입니다. 신규(표준::size_t) 던지다(표준::bad_flashed(불량_flashing));     무효* 교환입니다. 신규[](표준::size_t) 던지다(표준::bad_flashed(불량_flashing)); 

Standard C++ 라이브러리는 이러한 기능에 대해 각각2개의 배치 오버로드를 제공합니다.선언은 다음과 같습니다.[7][8]

    무효* 교환입니다. 신규(표준::size_t, 컨스턴트 표준::하지 않다&) 던지다();     무효* 교환입니다. 신규(표준::size_t, 무효*) 던지다();     무효* 교환입니다. 신규[](표준::size_t, 컨스턴트 표준::하지 않다&) 던지다();     무효* 교환입니다. 신규[](표준::size_t, 무효*) 던지다(); 

모든 오버로드에서 첫 번째 파라미터는operator new함수는 유형입니다.std::size_t이 함수가 호출되면 할당하는 메모리의 양(바이트 단위)을 지정하는 인수로 전달됩니다.모든 함수가 유형을 반환해야 합니다.void *이것은 함수가 [2]할당하는 스토리지에 대한 포인터입니다.

배치 삭제 기능도 있습니다.배치되지 않은 삭제 함수의 오버로드 버전입니다.배치되지 않은 삭제 함수는 다음과 [7][8]같이 선언됩니다.

    무효 교환입니다. 삭제하다(무효*) 던지다();     무효 교환입니다. 삭제하다[](무효*) 던지다(); 

Standard C++ 라이브러리는 이러한 기능에 대해 각각2개의 배치 오버로드를 제공합니다.선언은 다음과 같습니다.[7][8]

    무효 교환입니다. 삭제하다(무효*, 컨스턴트 표준::하지 않다&) 던지다();     무효 교환입니다. 삭제하다(무효*, 무효*) 던지다();     무효 교환입니다. 삭제하다[](무효*, 컨스턴트 표준::하지 않다&) 던지다();     무효 교환입니다. 삭제하다[](무효*, 무효*) 던지다(); 

모든 오버로드에서 첫 번째 파라미터는operator delete함수는 유형입니다.void *할당 [2]해제할 스토리지의 주소입니다.

new 함수 및 delete 함수 모두 함수는 글로벌하고 네임스페이스에 존재하지 않으며 정적 링크도 [2]없습니다.

사용하다

배치 구문에는 기본 배치, 예외 방지, 사용자 지정 할당자 및 디버깅의 4가지 주요 용도가 있습니다.

디폴트 배치

의 배치 과부하operator new그리고.operator delete추가 고용을 하는 것void *파라미터는 포인터 배치라고도 하는 기본 배치에 사용됩니다.C++ 프로그램이 대체 또는 재정의하는 것은 허용되지 않는 표준 C++ 라이브러리에 의한 정의는 다음과 같습니다.[7][8][9]

무효* 교환입니다. 신규(표준::size_t, 무효* p) 던지다() { 돌아가다 p; } 무효* 교환입니다. 신규[](표준::size_t, 무효* p) 던지다() { 돌아가다 p; } 무효 교환입니다. 삭제하다(무효*, 무효*) 던지다() { } 무효 교환입니다. 삭제하다[](무효*, 무효*) 던지다() { } 

디폴트 배치에는 다양한 용도가 있습니다.

Bjarne Strostrup은 그의 저서 The Design and Evolution of C++에서 포인터 배치 new가 특정 하드웨어 주소에서 특정 객체를 예상하는 하드웨어에 필요하다고 처음 관찰했습니다.멀티프로세서 컴퓨터의 [10]여러 프로세서 간에 공유되는 영역 등 특정 메모리 영역에 상주할 필요가 있는 오브젝트 구축에도 필요합니다.

그러나 다른 용도로는 C++ 언어에서는 [3]허용되지 않는 컨스트럭터를 직접 호출하는 것이 있습니다.

C++언어는 프로그램이 디스트럭터를 직접 호출할 수 있도록 합니다.또한 C++언어를 사용하여 오브젝트를 파기할 수 없기 때문에deleteexpression, 이것이 포인터 배치 새 expression을 통해 생성된 개체를 파괴하는 방법입니다.예를 [11][12]들어 다음과 같습니다.

p->~T(); 

사용 사례

placement new는 오퍼레이터가 메모리를 새로 할당하는 것을 원하지 않지만(이미 할당되어 있고 개체를 배치하는 경우), 개체를 구성하려는 경우 사용됩니다.이것이 필요한 일반적인 상황의 예는 다음과 같습니다.

  • 서로 다른 두 프로세스 간에 공유되는 메모리 내의 개체를 생성하려고 합니다.
  • 개체를 페이지 불가능한 메모리에 생성하려고 합니다.
  • 예를 들어 std::벡터<>를 실장할 때 메모리 할당을 구조로부터 분리할 필요가 있습니다(std::벡터<>:reserve 참조).

기본적인 문제는 컨스트럭터가 특수한 함수라는 것입니다.시작할 때 오브젝트는 없고 원시 메모리만 존재합니다.이 작업이 완료되면 완전히 초기화된 개체가 생성됩니다.따라서 i) 컨스트럭터를 오브젝트 ii로 호출할 수 없습니다.단, 비정적 멤버에 액세스(초기화)해야 합니다.이로 인해 컨스트럭터를 직접 호출하는 것은 오류가 됩니다.해결책은 연산자 new의 배치 형태입니다.

이 연산자는 다음과 같이 구현됩니다.

    무효* 교환입니다. 신규(표준::size_t 세어보세요, 무효* 여기서) { 돌아가다 여기서; }     무효* 교환입니다. 신규[](표준::size_t 세어보세요, 무효* 여기서) { 돌아가다 여기서; } 

예외 방지

일반적으로 (배치되지 않은) 새로운 함수는 다음과 같은 유형의 예외를 발생시킵니다.std::bad_alloc사용 가능한 메모리가 모두 소진되는 등의 오류가 발생한 경우.이것은 Strustrup의 주석 첨부 C++ 참조 매뉴얼에서 함수를 정의하는 방식이 아니라 C++ 언어가 표준화되었을 때 표준화 위원회가 변경한 것입니다.함수의 원래 동작은 다음과 같습니다.NULL에러가 발생했을 때의 포인터는 배치 [3][4][6]구문을 통해 액세스 할 수 있습니다.

프로그램에서 이 작업을 수행하려면 표준 C++ 라이브러리 헤더를 포함해야 합니다.<new>소스코드에 있습니다.이 헤더는 글로벌을 선언합니다.std::nothrow오브젝트, 유형std::nothrow_t(헤더에도 선언되어 있습니다).이것은, 오버로드 되고 있는 새로운 함수를 호출하기 위해서 사용됩니다.const std::nothrow_t &두 번째 매개 변수로 사용합니다.예를 [9]들어 다음과 같습니다.

#실패하다 <신규>  구조 T {};  인트 주된() {     // 함수 연산자 new(std::size_t, const std::nothrow_t &)를 호출하여 (성공한 경우) 개체를 구성합니다.     T* p = 신규 (표준::행하지 않다) T;     한다면 (p) {         // 저장소가 할당되어 생성자가 호출되었습니다.         삭제하다 p;     } 또 다른         ; // 오류가 발생했습니다.스토리지가 할당되지 않았고 개체가 구성되지 않았습니다.     돌아가다 0; } 

커스텀 할당자

배치 구문은 사용자 지정 할당자에도 사용됩니다.표준 C++ 라이브러리 헤더에서 할당자 및 할당 해제 기능을 사용하지 않습니다.<new>단, 프로그래머가 사용자 정의 유형에 대해 오버로드된 할당 및 할당 해제 함수를 직접 작성해야 합니다.예를 들어 다음과 [7][8]같이 메모리 관리 클래스를 정의할 수 있습니다.

#실패하다 <cstdlib> 학급 A { 일반의:     무효* 할당하다(표준::size_t);     무효 할당을 해제하다(무효*); }; 

커스텀 배치 할당 및 할당 해제 함수를 [7][8]다음과 같이 정의합니다.

무효* 교환입니다. 신규(표준::size_t 크기, A& 경기장) {     돌아가다 경기장.할당하다(크기); }  무효 교환입니다. 삭제하다(무효* p, A& 경기장) {     경기장.할당을 해제하다(p); } 

프로그램은 배치 구문을 사용하여 다른 인스턴스를 사용하여 개체를 할당합니다.A다음과 [7][8]같이 분류합니다.

A 첫 번째_마이너스탠다드, second_filename(세컨드_프론트); T* p1 = 신규(첫 번째_마이너스탠다드) T; T* p2 = 신규(second_filename(세컨드_프론트)) T; 

이러한 방식으로 스토리지가 할당된 개체를 폐기하려면 주의가 필요합니다.배치 삭제 식이 없기 때문에 이 식을 사용하여 사용자 지정 할당 해제기를 호출할 수 없습니다.커스텀 디할로케이터를 호출하는 파괴 함수를 쓰거나 배치 삭제 함수를 함수 [11][7][8]호출로 직접 호출해야 합니다.

전자는 [8]다음과 같습니다.

무효 파괴하다(T* p, A& 경기장) {     p->~T();  // 먼저 소멸자를 명시적으로 호출합니다.     경기장.할당을 해제하다(p);  // 그런 다음 디할로케이터 함수를 직접 호출합니다. } 

이 명령어는 프로그램에서 다음과 같이 호출됩니다.

A 경기장; T* p = 신규(경기장) T; /* ... */ 파괴하다(p, 경기장); 

후자는 파괴자 호출과 삭제 함수 호출을 프로그램에 [7][13]단순히 쓰는 것을 포함합니다.

A 경기장; T* p = 신규(경기장) T; /* ... */ p->~T();  // 먼저 소멸자를 명시적으로 호출합니다. 교환입니다. 삭제하다(p, 경기장);  // 그런 다음 연산자 삭제(void*, A &)를 통해 간접적으로 할당 해제 함수를 호출합니다. 

일반적인 오류는 삭제 식을 사용하여 개체를 삭제하려고 시도하는 것입니다.이것은 잘못된 결과를 낳는다.operator delete호출된 함수입니다.Dewhurst는 이 오류를 피하기 위해 두 가지 방법을 권장합니다.첫 번째는 모든 커스텀 할당자가 Standard C++ 라이브러리의 글로벌한 비배치에 의존하도록 하는 것입니다.operator new따라서 C++ 라이브러리의 메모리 관리를 둘러싼 단순한 래퍼에 지나지 않습니다.두 번째는 개개의 클래스에 대해 새로운 함수를 만들고 삭제하고 배치 [13]구문을 사용하는 것이 아니라 클래스 함수 멤버를 통해 메모리 관리를 맞춤화하는 것입니다.

디버깅

Placement new는 메모리 할당에 실패한 소스 코드의 파일 이름과 행 번호를 프로그램이 인쇄할 수 있도록 하는 간단한 디버깅 도구로도 사용할 수 있습니다.표준 C++ 라이브러리 헤더를 포함할 필요가 없습니다.<new>단, 4개의 배치 함수와 매크로 치환을 선언하는 헤더를 포함해야 합니다.new새로운 표현에서 사용되는 키워드입니다.예를 들어, 이러한 헤더에는 다음이 포함됩니다.[9][14]

#정의되어있는경우(DEBUG_NEW) 무효* 교환입니다. 신규(표준::size_t 크기, 컨스턴트 * 파일, 인트 ); 무효* 교환입니다. 신규[](표준::size_t 크기, 컨스턴트 * 파일, 인트 ); 무효 교환입니다. 삭제하다(무효* p, 컨스턴트 * 파일, 인트 ); 무효 교환입니다. 삭제하다[](무효* p, 컨스턴트 * 파일, 인트 ); #새로운 정의(_FILE__, __LINE__) #실패하다 #정의신규 #엔디프 

이것은 다음과 [9][14]같은 프로그램에서 사용됩니다.

T* p = 신규 T; 

커스텀 작성 배치의 새로운 함수는 예외 발생 시 제공된 파일 및 회선 번호 정보를 사용하여 처리합니다.예를 [9][14]들어 다음과 같습니다.

#실패하다 <신규> #실패하다 <cstdlib>  학급 새 오류 { 일반의:     새 오류(컨스턴트 * 파일, 인트 ) { /* ... */ }     /* ... */ } ;  무효 * 교환입니다. 신규(표준::size_t 크기, 컨스턴트 * 파일, 인트 ) {     한다면 (무효* p = ::교환입니다. 신규(크기, 표준::행하지 않다))         돌아가다 p;     던지다 새 오류(파일, ); } 

배치 삭제

위에서 설명한 바와 같이 배치 삭제 표현은 없습니다.배치를 호출할 수 없습니다.operator delete사용하여 기능하다delete표현.[11][15]

배치 삭제 함수가 배치에서 호출됩니다.new표현.특히 오브젝트 생성자가 예외를 발생시키면 호출됩니다.이 때, 프로그램이 메모리 리크를 일으키지 않게 하기 위해서, 배치 삭제 기능을 호출한다.배치 새 표현식이 먼저 배치를 호출합니다.operator new이 함수는 할당자 함수에서 반환된 원시 스토리지에서 개체 생성자를 호출합니다.컨스트럭터가 예외를 슬로우하는 경우 배치 새 식을 실행한 코드로 예외를 전파하기 전에 해당 스토리지의 할당을 해제해야 합니다.그것이 배치 삭제 [2][4][11][15]함수의 목적입니다.

호출되는 배치 삭제 함수는 배치 새 식에 의해 호출된 배치 새 함수와 일치합니다.따라서 예를 들어 다음 코드가 실행되면 호출되는 배치 삭제 함수는operator delete(void *, const A &)다음과 같습니다.[2][11][15]

#실패하다 <cstdlib> #실패하다 <iostream>  구조 A {}; 구조 E {};  학급 T { 일반의:     T() { 던지다 E(); } };  무효 * 교환입니다. 신규(표준::size_t, 컨스턴트 A&) {     표준::외치다 << > "새로운 위치 호출" << > 표준::; }  무효 교환입니다. 삭제하다(무효*, 컨스턴트 A&) {     표준::외치다 << > "배치 삭제가 호출되었습니다." << > 표준::; }  인트 주된(){     A a;     해라 {         T* p = 신규(a) T;     } 또 만나 (E exp) {         표준::외치다 << > "예외 포착" << > 표준::;     }     돌아가다 0; } 

이것이 포인터 배치 삭제 함수가 표준 C++ 라이브러리에 의해 no-operations로 정의되는 이유입니다.포인터 배치의 새 함수는 스토리지를 할당하지 않으므로 개체 생성자가 [11]예외를 발생시킬 경우 스토리지를 할당 해제할 수 없습니다.

일치하는 배치 삭제 함수가 없는 경우 배치 내에서 생성자에 의해 예외가 발생해도 할당 해제 함수가 호출되지 않습니다.new표현.배치 삭제를 전혀 지원하지 않는 (오래된) 일부 C++ 구현도 있습니다(예외 슬로우 할당자 함수처럼 표준화 시 C++에 추가된 것입니다.두 경우 모두 커스텀할로케이터를 사용하여 할당할 때 컨스트럭터에 의해 예외가 발생하면 메모리 누수가 발생합니다.(오래된 C++ 실장의 경우 메모리 누수는 배치되지 않은 경우에도 발생합니다.new표현)[4][15]

보안.

새 식을 배치하면 보안 공격에 취약합니다.2011년 Kundu와 Bertino는[16] 새로운 배치에 대한 몇 가지 이점을 시연했습니다.공격에는 버퍼 오버플로 공격, 오브젝트 오버플로, 선택적 스택 가드 덮어쓰기, 가상 포인터 서브터퍼지, 메모리 정렬 오류 공격 등이 있습니다.2015년에 GCC는 [16]의 조사 결과를 바탕으로 패치를[17] 발표했습니다.

메모들

  1. ^ 맥클러스키 2000
  2. ^ a b c d e f g h 리스네르 2003, 페이지 72-73, 128-129, 310, 623-625
  3. ^ a b c d 리프먼 1997, 386-389페이지
  4. ^ a b c d 마이어스 1998
  5. ^ 스트루스트럽 1991[페이지 필요]
  6. ^ a b 루돈 2003, 페이지 109–110
  7. ^ a b c d e f g h i j Vermeir 2001, 113–115페이지
  8. ^ a b c d e f g h i j Strustrup 1997, 페이지 255~256, 576
  9. ^ a b c d e 앤더슨 1998a, 345-356페이지
  10. ^ 스트루스트럽 1994, 페이지 214
  11. ^ a b c d e f Solter & Kleper 2005, 페이지 458-461
  12. ^ 시드 & 쿠퍼 2001, 435–436페이지
  13. ^ a b 듀허스트 2003, 페이지 173–176
  14. ^ a b c 용웨이 2007
  15. ^ a b c d 앤더슨 1998b, 631–632페이지
  16. ^ a b c Kundu, Ashish; Bertino, Elisa (June 2011). "A New Class of Buffer Overflow Attacks". 2011 31st International Conference on Distributed Computing Systems: 730–739. doi:10.1109/ICDCS.2011.63 – via IEEE.
  17. ^ "Martin Sebor - [PING] [PATCH] c++/67942 - diagnose placement new buffer overflow". gcc.gnu.org. Retrieved 2020-06-15.

레퍼런스

추가 정보