2013-03-07 6 views
11

잘 훈련 된 땅에서 어리석은 첫 게시물 일지 미리 사과드립니다. 주제에 대해 많은 자료가 있지만, 그 중 거의 확실하지 않으며 나에게 알기 쉽습니다.C++ : 엄격한 앨리어싱 대 노조의 학대

임의로 정렬 된 힙에 동적으로 메모리를 할당하는 템플릿 클래스가 있습니다 (AVX 어셈블리 루틴의 경우 32 바이트 정렬 필요). 이렇게하려면 추한 포인터 조작이 필요합니다.

Agner Fog는 cppexamples.zip에서 union을 악용하여 (http://www.agner.org/optimize/optimization_manuals.zip) 샘플 클래스를 제공합니다. 그러나 저는 한 노조원에게 글을 쓰고 다른 한권제에서 읽으면 UB가된다는 것을 압니다.

AFAICT 모든 포인터 유형을 char *으로 별명을 지정하는 것이 안전하지만 한 방향으로 만 지정하는 것이 안전합니다. 이것은 나의 이해가 퍼지게되는 곳이다.

template <typename T, size_t alignment = 32> 
class AlignedArray 
{ 
    size_t m_size; 
    char * m_unaligned; 
    T * m_aligned; 

public: 
    AlignedArray (size_t const size) 
     : m_size(0) 
     , m_unaligned(0) 
     , m_aligned(0) 
    { 
     this->size(size); 
    } 

    ~AlignedArray() 
    { 
     this->size(0); 
    } 

    T const & operator [] (size_t const i) const { return m_aligned[i]; } 

    T & operator [] (size_t const i) { return m_aligned[i]; } 

    size_t const size() { return m_size; } 

    void size (size_t const size) 
    { 
     if (size > 0) 
     { 
      if (size != m_size) 
      { 
       char * unaligned = 0; 
       unaligned = new char [size * sizeof(T) + alignment - 1]; 
       if (unaligned) 
       { 
        // Agner: 
        /* 
        union { 
         char * c; 
         T * t; 
         size_t s; 
        } aligned; 
        aligned.c = unaligned + alignment - 1; 
        aligned.s &= ~(alignment - 1); 
        */ 

        // Me: 
        T * aligned = reinterpret_cast<T *>((reinterpret_cast<size_t>(unaligned) + alignment - 1) & ~(alignment - 1)); 

        if (m_unaligned) 
        { 
         // Agner: 
         //memcpy(aligned.c, m_aligned, std::min(size, m_size)); 

         // Me: 
         memcpy(aligned, m_aligned, std::min(size, m_size)); 

         delete [] m_unaligned; 
        } 
        m_size = size; 
        m_unaligned = unaligned; 

        // Agner: 
        //m_aligned = aligned.t; 

        // Me: 
        m_aligned = aligned; 
       } 
       return; 
      } 
      return; 
     } 
     if (m_unaligned) 
     { 
      delete [] m_unaligned; 
      m_size = 0; 
      m_unaligned = 0; 
      m_aligned = 0; 
     } 
    } 
}; 

그래서 방법은 안전 (R) : 여기 내 AlignedArray 클래스의 축소 된 버전 (Agner의 본질적 재 작성, 내 이해를 돕기 위해이)입니까?

+3

대신'운영자 new'에서 T로, 당신은 왜 (원시 메모리를 잡아하지 않는 것이 주조 후'char' 개체를 구성하고 다음은

은 고정 것을 제안하여 기능의 버전입니다 , 또는 심지어'malloc')을'void *'로 정의하고 실제로'T' 객체를 구성합니까? 기본적으로 T 객체를 원하면 T 객체를 만듭니다. 이 유스 케이스 (정렬 된 배열)는 앨리어스 트릭/유니온/memcpy/무엇이든 * 제로 * 필요합니다. –

+0

@ R.MartinhoFernandes : 예외적으로, 수학은'void *'에 허용되지 않습니다. 정렬 된'void * '를 어떻게 얻습니까? – Omnifarious

+0

@Omnifarious 마지막으로 확인했는데 수학은'char * '에도 허용되지 않습니다. (그렇다고해도 char 객체를 생성하고 T 객체를 생성 할 필요가 없다는 의미는 아닙니다.) 수학을 수행하려면 정수가 필요합니다. C++ 11의 이식 가능한 솔루션은 http://en.cppreference.com/w/cpp/memory/align입니다. 이론적으로는 이식 가능하지 않은 솔루션은 숫자 형식으로 reinterpret_cast를 수행하고 수학을 수행 한 다음 reinterpret_cast를 다시 수행하는 것입니다. (모든 구현에서 숫자 형에 대한 reinterpret_cast가 예상대로 작동하기 때문에 실제로는 꽤 휴대 가능합니다.) –

답변

3

SIMD (즉, SSE/AVX)에 적합한 (대체) newdelete 연산자를 구현하는 코드가 있습니다. 유용 할 수있는 다음 기능을 사용합니다.

static inline void *G0__SIMD_malloc (size_t size) 
{ 
    constexpr size_t align = G0_SIMD_ALIGN; 
    void *ptr, *uptr; 

    static_assert(G0_SIMD_ALIGN >= sizeof(void *), 
        "insufficient alignment for pointer storage"); 

    static_assert((G0_SIMD_ALIGN & (G0_SIMD_ALIGN - 1)) == 0, 
        "G0_SIMD_ALIGN value must be a power of (2)"); 

    size += align; // raw pointer storage with alignment padding. 

    if ((uptr = malloc(size)) == nullptr) 
     return nullptr; 

    // size_t addr = reinterpret_cast<size_t>(uptr); 
    uintptr_t addr = reinterpret_cast<uintptr_t>(uptr); 

    ptr = reinterpret_cast<void *> 
     ((addr + align) & ~(align - 1)); 

    *(reinterpret_cast<void **>(ptr) - 1) = uptr; // (raw ptr) 

    return ptr; 
} 


static inline void G0__SIMD_free (void *ptr) 
{ 
    if (ptr != nullptr) 
     free(*(reinterpret_cast<void **>(ptr) - 1)); // (raw ptr) 
} 

적응하기 쉽습니다. 분명히 원시 (문자) 저장에 newdelete을 사용하고 있으므로 mallocfree을 바꿀 것입니다. 주소 계산을 위해 size_t이 충분히 넓다고 가정합니다. 실제로는 uintptr_t에서 <cstdint>까지가 더 정확할 것입니다.

+0

POSIX 시스템에는 posix_memalign()이 있습니다. – BatchyX

+0

감사합니다. 가장 도움이됩니다. (둘 다 본질적으로 동일한 포인터를 사용하고 있습니다). 우리 예제 중 하나가 정의되지 않은 동작을 초래할 가능성이 있습니까? 여기에 엄격한 앨리어스 규칙을 위반하고 있는지 여부는 확실하지 않습니다 ... – linguamachina

+0

이것을 할당 자에게 넣으면 나머지 표준 라이브러리를 쉽게 사용할 수 있습니다 :'vector '; 재발견 할 필요가 없습니다. –

2

질문에 대답하기 위해 두 가지 방법 모두 안전합니다. 실제로 냄새 나는 유일한 두 가지 작업은 size_tnew char[stuff]으로 캐스트입니다. 적어도 uintptr_t부터 <cstdint>까지 사용해야합니다. 두 번째 작업은 기술적으로 char 구성 요소가 각 char 요소에서 실행되고 char 포인터를 통해 데이터에 액세스하는 것으로서 포인터 별칭 문제 만 발생시킵니다. 대신 malloc을 사용해야합니다.

다른 '포인터 앨리어싱'은 문제가되지 않습니다. new 연산 이외에 별칭이 지정된 포인터를 통해 데이터에 액세스하지 않기 때문입니다. 맞춤 후 T *을 통해서만 데이터에 액세스하고 있습니다.

물론 모든 배열 요소를 작성해야한다는 것을 기억해야합니다. 이것은 귀하의 버전에서도 마찬가지입니다. 어떤 종류의 사람들이 T 사람들이 거기에 넣을 지 알고 있습니다. 그리고 물론 그렇게한다면 소멸자를 호출하는 것을 잊지 말고 복사 할 때 예외를 처리해야한다는 것을 기억해야합니다 (memcpy 절단하지 않음).

특정 C++ 11 기능이있는 경우이 작업을 수행 할 필요가 없습니다. C++ 11에는 포인터를 임의의 경계에 맞추기위한 함수가 있습니다. 인터페이스는 약간 펑키하지만 작업을 수행해야합니다. 전화는 ::std::align이며 <memory>으로 정의되어 있습니다. 전화 번호는 R. Martinho Fernandes입니다.

#include <cstdint> // For uintptr_t 
#include <cstdlib> // For malloc 
#include <algorithm> 

template <typename T, size_t alignment = 32> 
class AlignedArray 
{ 
    size_t m_size; 
    void * m_unaligned; 
    T * m_aligned; 

public: 
    AlignedArray (size_t const size) 
     : m_size(0) 
     , m_unaligned(0) 
     , m_aligned(0) 
    { 
     this->size(size); 
    } 

    ~AlignedArray() 
    { 
     this->size(0); 
    } 

    T const & operator [] (size_t const i) const { return m_aligned[i]; } 

    T & operator [] (size_t const i) { return m_aligned[i]; } 

    size_t size() const { return m_size; } 

    void size (size_t const size) 
    { 
     using ::std::uintptr_t; 
     using ::std::malloc; 

     if (size > 0) 
     { 
      if (size != m_size) 
      { 
       void * unaligned = 0; 
       unaligned = malloc(size * sizeof(T) + alignment - 1); 
       if (unaligned) 
       { 
        T * aligned = reinterpret_cast<T *>((reinterpret_cast<uintptr_t>(unaligned) + alignment - 1) & ~(alignment - 1)); 

        if (m_unaligned) 
        { 
         ::std::size_t constructed = 0; 
         const ::std::size_t num_to_copy = ::std::min(size, m_size); 

         try { 
          for (constructed = 0; constructed < num_to_copy; ++constructed) { 
           new(aligned + constructed) T(m_aligned[constructed]); 
          } 
          for (; constructed < size; ++constructed) { 
           new(aligned + constructed) T; 
          } 
         } catch (...) { 
          for (::std::size_t i = 0; i < constructed; ++i) { 
           aligned[i].T::~T(); 
          } 
          ::std::free(unaligned); 
          throw; 
         } 

         for (size_t i = 0; i < m_size; ++i) { 
          m_aligned[i].T::~T(); 
         } 
         free(m_unaligned); 
        } 
        m_size = size; 
        m_unaligned = unaligned; 
        m_aligned = aligned; 
       } 
      } 
     } else if (m_unaligned) { // and size <= 0 
      for (::std::size_t i = 0; i < m_size; ++i) { 
       m_aligned[i].T::~T(); 
      } 
      ::std::free(m_unaligned); 
      m_size = 0; 
      m_unaligned = 0; 
      m_aligned = 0; 
     } 
    } 
}; 
+1

"포인터 별칭은 문제가 아니며 별칭이 지정된 포인터를 통해 데이터에 액세스하지 않기 때문에 발생합니다." 생성 된 char 배열을 볼 수 있으며, T *를 통해 액세스 할 수 있습니다. ... –

+0

@ R.MartinhoFernandes : 좋아요, 맞습니다. 그리고 내 대답을 고쳐 줄거야. – Omnifarious

+0

@ R.MartinhoFernandes :저기서 고쳤습니다. – Omnifarious

관련 문제