2008-08-18 8 views
30

어레이에 사용할 때 이식성있는 코드에서 새로운 배치를 실제로 사용할 수 있습니까?어레이의 새로운 배치를 이식 가능한 방식으로 사용할 수 있습니까?

새로운 []에서 가져온 포인터가 전달하는 주소와 항상 같지는 않습니다 (5.3.4, 표준에서 12 참고가 올바른지 확인하는 것처럼 보입니다) 이 경우 어레이가 들어갈 버퍼를 어떻게 할당 할 수 있는지보십시오.

다음 예제에서는 문제를 보여줍니다. 비주얼 스튜디오 컴파일이 예는 메모리 손상 결과

#include <new> 
#include <stdio.h> 

class A 
{ 
    public: 

    A() : data(0) {} 
    virtual ~A() {} 
    int data; 
}; 

int main() 
{ 
    const int NUMELEMENTS=20; 

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; 
    A *pA = new(pBuffer) A[NUMELEMENTS]; 

    // With VC++, pA will be four bytes higher than pBuffer 
    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); 

    // Debug runtime will assert here due to heap corruption 
    delete[] pBuffer; 

    return 0; 
} 

메모리에서 찾고, 컴파일러는 항목 수의 카운트를 저장하는 버퍼의 처음 4 바이트를 사용하는 것으로 보인다. 즉, 버퍼는 sizeof(A)*NUMELEMENTS만큼 커야하므로 배열의 마지막 요소는 할당되지 않은 힙에 기록됩니다.

따라서 배치 []를 안전하게 사용하기 위해 구현에서 얼마나 많은 추가 오버 헤드가 필요합니까? 이상적으로는, 다른 컴파일러간에 이식 할 수있는 기술이 필요합니다. 적어도 VC의 경우, 오버 헤드는 클래스마다 다를 수 있습니다. 예를 들어, 예제에서 가상 소멸자를 제거하면 new []에서 반환 된 주소는 내가 전달한 주소와 동일합니다.

+0

아아 저주.귀하의 질문에 속아 : ([배열 배치 - 새로 버퍼에 지정되지 않은 오버 헤드가 필요합니다?) (http://stackoverflow.com/questions/8720425/array-placement-new-requires-unspecified-overhead-in-the -buffer) –

+0

흠 ... 가상 소멸자를 제거 할 때 오버 헤드가 사라지면 오버 헤드가 클래스 'vtable 또는 VStudio의 RTTI 구현에서 발생할 가능성이 높습니다. –

+0

또는 적어도 오버 헤드는 클래스가 중요하지 않은 소멸자를 가지고있는 경우에만 오버 헤드가 사용되는 것일 수도 있습니다 .. –

답변

22

개인적으로 배열에 새 배치를 사용하지 않고 배열의 각 항목에 개별적으로 배치를 사용하는 옵션을 사용합니다. 예를 들어 : 관계없이 사용하는 방법의

int main(int argc, char* argv[]) 
{ 
    const int NUMELEMENTS=20; 

    char *pBuffer = new char[NUMELEMENTS*sizeof(A)]; 
    A *pA = (A*)pBuffer; 

    for(int i = 0; i < NUMELEMENTS; ++i) 
    { 
    pA[i] = new (pA + i) A(); 
    } 

    printf("Buffer address: %x, Array address: %x\n", pBuffer, pA); 

    // dont forget to destroy! 
    for(int i = 0; i < NUMELEMENTS; ++i) 
    { 
    pA[i].~A(); 
    }  

    delete[] pBuffer; 

    return 0; 
} 

, 당신의 pbuffer을 삭제하기 전에 누수로 끝날 수 있기 때문에 수동 배열의 해당 항목의 각을 파괴해야합니다)

: 나는 이것을 컴파일하지는 않았지만, 그것이 작동해야한다고 생각한다 (나는 C++ 컴파일러가 설치되지 않은 머신에있다). 그것은 여전히 ​​요점을 나타냅니다 :) 어떤면에서 도움이되기를 바랍니다!


편집 :

는 요소의 수를 추적해야하는 이유는 당신이 배열에서 삭제 호출하고 소멸자의 각 호출되어 있는지 확인 때 그들을 통해 반복 할 수 있도록이다 개체. 그것이 얼마나 많은지를 모르는 경우에는 이것을 할 수 없을 것입니다.

+1

VC++는 나쁜 컴파일러입니다. 기본 배치 인 new array (new (void *) type [n])는'n' 객체의 내부 생성을 수행합니다 주어진 포인터는 일치하도록 적절하게 정렬되어야합니다. 'alignof (type)'(주의 :'sizeof (type)'은 패딩 때문에'alignof (type)'의 배수이다). 배열의 길이를 가지고 다닐 필요가 있기 때문에 실제로 for-loop를 사용하여 파기 할 것이기 때문에 배열 안에 저장할 필요가 없습니다 (배치 삭제 연산자 없음). – bit2shift

+1

@ bit2shift C++ 표준은 0 바이트의 패딩을 허용하지만 'new []'가 할당 된 메모리를 채 웁니다. ("expr.new"섹션, 특히 예제 (14.3)과 (14.4) 및 그 아래의 설명 참조). 따라서이 표준은 실제로 표준을 준수합니다. \ [C++ 14 표준의 최종 버전 사본이없는 경우 133 페이지 [여기] (http://www.open-std.org/jtc1/sc22/wg21/docs/papers)를 참조하십시오. /2014/n4296.pdf). \] –

+1

@JustinTime 아무도 눈을 뗄 수가없는 결함입니다. – bit2shift

1

gcc는 MSVC와 동일한 작업을 수행하지만 실제로는 ' 그것을 "휴대용"으로 만들지 마십시오.

나는 numElements 개의 그래서 같이, 일정한 실제로 컴파일 시간을 때이 문제를 해결 할 수 있다고 생각 :

typedef A Arr[NUMELEMENTS];

A* p = new (buffer) Arr;

이 새로운 스칼라 배치를 사용해야합니다.

+1

연산자 new()'vs'연산자 new []()'는 배열의 존재 여부에 달려 있습니다. 소스 코드에'[] '이 있는지 여부와 관계없이 컴파일됩니다. –

1

단일 요소를 사용하여 하나의 placement-new의 크기를 계산하는 방법과 비슷하게, 해당 요소의 배열을 사용하여 배열에 필요한 크기를 계산합니다.

요소 수가 알려지지 않은 다른 계산의 크기가 필요한 경우 sizeof (A [1])를 사용하고 필요한 요소 수를 곱하면됩니다.

e.g

char *pBuffer = new char[ sizeof(A[NUMELEMENTS]) ]; 
A *pA = (A*)pBuffer; 

for(int i = 0; i < NUMELEMENTS; ++i) 
{ 
    pA[i] = new (pA + i) A(); 
} 
+0

요점은 MSVC 'new [] '의 경우에는 sizeof (A [NUMELEMENTS])의 값을 넘는 추가 공간이 필요합니다. 'sizeof (A [N])'은 일반적으로'N * sizeof (N)'이 될 것이고,이 추가적인 공간을 반영하지 않을 것입니다. – BeeOnRope

1

답장을 보내 주셔서 감사합니다. 배열의 각 항목에 대해 새로운 배치를 사용하는 것은 내가이 문제에 부딪쳤을 때 사용했던 솔루션이었습니다 (미안합니다, 질문에서 언급 했어야합니다). 나는 방금 배치 된 새로운 []로 그것을하는 것에 대해 내가 빠뜨린 어떤 것이 있었음에 틀림 없다고 느꼈다. 즉, 컴파일러가 배열에 추가로 지정되지 않은 오버 헤드를 추가하는 것을 허용하는 표준 덕분에 배치 new []가 근본적으로 사용 불능 인 것처럼 보입니다. 나는 당신이 어떻게 그것을 안전하게 사용할 수 있는지 보지 못한다.

어쨌든 배열에 delete []를 호출하지 않으므로 추가 데이터가 필요한 이유가 명확하지 않기 때문에 항목의 수를 알아야하는 이유를 완전히 알지 못합니다. .

2

@ 제임스

I 아니에요 심지어는 [] 어쨌든 배열에서 삭제를 호출 할 것이다, 그래서 필요한 이유가 전혀 표시되지 않습니다대로, 추가 데이터를 필요로하는 이유 정말 선택 얼마나 많은 아이템이 있는지 알 수 있습니다.

이 의견을 제시 한 후 동의합니다. 게재 위치 삭제가 없기 때문에 게재 위치 new가 요소 수를 저장해야하는 이유는 없습니다. 배치 삭제가 없기 때문에 새로운 배치에서 요소 수를 저장할 이유가 없습니다.

또한 Mac에서 gcc를 사용하여 소멸자가있는 클래스를 사용하여이를 테스트했습니다. 내 시스템에서 새로 배치 한 포인터는 이 아니고입니다. 이것은 VC++의 문제인지, 이것이 표준을 위반할 수 있는지 궁금하게 만듭니다. (표준은이 부분을 특별히 언급하지 않습니다.

+0

나는 [Coliru] (http://coliru.stacked-crooked.com)에서'-std = C++ 14'와'-pedantic'을 모두 사용하여 clang 3.7.0과 GCC 5.3.0을 테스트했습니다. 특히 중대한 소멸자가없는 클래스와 함께 오버 헤드의 존재를 보여주지 못했습니다. 그래서, 저는 Visual C++가 그렇게 나쁜 컴파일러 인 방법의 또 다른 예라고 생각합니다. – bit2shift

4

@Derek

5.3.4, 배열 할당 오버 헤드, 내가 잘못 읽고있어 않는 한,이 컴파일러는 새로운 배치에 추가하는 것이 유효하다는 것을 나에게 제안하는 것에 대한 (12 개) 이야기

이 오버 헤드는 라이브러리 함수 연산자 new [] (std :: size_t, void *) 및 기타 배치 할당 함수를 참조하는 모든 배열 새 표현식에 적용될 수 있습니다. 오버 헤드 양은 new 호출마다 다를 수 있습니다.

그렇다고 VC가 나에게 GCC, Codewarrior 및 ProDG와 관련하여 문제를 일으키는 유일한 컴파일러라고 생각합니다. 그래도 다시 확인해야 겠어.

+0

VC가 여분의 공간을 추가하는 유일한 컴파일러라면 모든 컴파일러가 거기에 호출 할 소멸자의 수를 저장할 것이라고 생각합니다. 다른 논리적 인 장소는 없습니다. –

+1

@MooingDuck 앞서 언급 한 "배열 길이"를 사용하기위한 배치 삭제 연산자가 없습니다. 사실, ** new (포인터) 타입 [길이]로 배열 **에 대한 소멸자를 수동으로 호출해야합니다. VC는 나쁜 컴파일러이므로 모두가 알아야합니다. – bit2shift

3

새로운 배치 자체는 이식 가능하지만 지정된 메모리 블록에서 수행하는 작업에 대한 가정은 이식 가능하지 않습니다. 전에 말했던 것과 마찬가지로, 컴파일러 였고 메모리 덩어리가 있다면 배열을 할당하고 포인터가 모두 포인터라면 제대로 각 원소를 파괴하는 법을 어떻게 알 수 있습니까? 단지 그것만 [] 새로운 배치와 배열을 할당하면서 생성자에서 예외가 발생 될 때 호출된다

실제로 배치가 삭제된다

편집 (. 운영자 인터페이스가 삭제 [] 참조).

새로운 [] 실제로 요소 수를 추적해야하는지 여부는 어떻게 든 표준에 달려있어 컴파일러에게 남겨 둡니다. 불행히도,이 경우.

+1

임의의 많은 양의 저장소를 덮어 쓰는 경우 어떻게 "휴대용"이 될 수 있습니까? 얼마나 많은 양의 글을 쓸지 모르기 때문에 안전하게 호출 할 수 없습니다. – BeeOnRope

+0

'delete []'표현식은 알 필요가 있지만 힙 할당 (throw 또는'nothrow' 중 하나)을 가진'new []'표현식의 결과에만 사용될 수 있습니다. 배치'new []'에 대해서는 실제로 필요하지 않습니다.VC의 동작에 대한 유일한 설명은 힙'new []'이 배치 new {]의 관점에서 어떻게 든 구현되고 요소 자체의 수를 저장하지 않는다는 것입니다. –