2013-03-07 33 views
11

Xin lỗi trước cho những gì có thể là một bài đăng đầu tiên ngớ ngẩn trên mặt đất vững chắc. Trong khi có rất nhiều tài liệu về chủ đề, rất ít trong số đó là dứt khoát và/hoặc dễ hiểu đối với tôi.C++: Nghiêm ngặt răng cưa và lạm dụng công đoàn

Tôi có lớp mẫu AlignedArray để tự động cấp phát bộ nhớ trên heap với căn chỉnh tùy ý (tôi cần căn chỉnh 32 byte cho các thói quen lắp ráp AVX). Điều này đòi hỏi một số thao tác con trỏ xấu xí.

Agner Fog cung cấp một lớp mẫu trong cppexamples.zip lạm dụng liên minh để làm như vậy (http://www.agner.org/optimize/optimization_manuals.zip). Tuy nhiên, tôi biết rằng viết cho một thành viên của một công đoàn và sau đó đọc từ một kết quả khác trong UB.

AFAICT là an toàn để đặt biệt hiệu bất kỳ loại con trỏ nào thành char *, nhưng chỉ theo một hướng. Đây là nơi hiểu biết của tôi bị mờ. Dưới đây là một phiên bản rút gọn của AlignedArray lớp học của tôi (thực chất là một viết lại của Agner, để giúp hiểu biết của tôi):

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; 
     } 
    } 
}; 

Vì vậy, phương pháp nào là an toàn (r)?

+3

Thay vì xây dựng 'đối tượng char' và sau đó đúc kết đến T, tại sao bạn không lấy nguyên bộ nhớ (từ 'hành new' , hoặc thậm chí 'malloc'), như' void * ', và thực sự xây dựng các đối tượng' T' trong đó? Về cơ bản: nếu bạn muốn T đối tượng, xây dựng các đối tượng T. Trường hợp sử dụng này (mảng liên kết) có * zero * cần cho bí danh thủ thuật/công đoàn/memcpy/bất cứ điều gì. –

+0

@ R.MartinhoFernandes: Ngoại trừ, toán học không được phép trên 'void *' s. Làm thế nào để bạn có được một liên kết 'void *'? – Omnifarious

+0

@Omnifarious Cuối cùng tôi đã kiểm tra, toán học không được phép trên 'char *'. (Và ngay cả khi nó được, đó sẽ không có nghĩa là bạn cần phải xây dựng các đối tượng char và không xây dựng các đối tượng T) Bạn cần số nguyên để làm toán học. Giải pháp di động trong C++ 11 là http://en.cppreference.com/w/cpp/memory/align. Giải pháp lý thuyết không phải là di động là reinterpret_cast thành kiểu số, thực hiện phép toán và reinterpret_cast. (nó khá di động trong thực tế bởi vì trong tất cả các triển khai, tôi biết reinterpret_cast cho các kiểu số hoạt động như mong đợi) –

Trả lời

3

Tôi có mã thực hiện (thay thế) newdelete toán tử, phù hợp với SIMD (tức là, SSE/AVX). Tính năng này sử dụng các chức năng sau mà bạn có thể thấy hữu ích:

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) 
} 

Điều này phải dễ điều chỉnh. Rõ ràng là bạn sẽ thay thế mallocfree vì bạn đang sử dụng toàn bộ newdelete để lưu trữ thô (char). Nó giả định rằng size_t đủ rộng cho số học địa chỉ - đúng trong thực tế, nhưng uintptr_t từ <cstdint> sẽ chính xác hơn.

+0

Trên hệ thống POSIX, có posix_memalign() – BatchyX

+0

Cảm ơn vì điều này, nó hữu ích nhất (và cả hai chúng tôi đều thực hiện cùng một con trỏ nhả). Có khả năng một trong hai ví dụ của chúng tôi có thể dẫn đến hành vi không xác định không? Tôi không chắc liệu chúng tôi có vi phạm các quy tắc bí mật nghiêm ngặt ở đây hay không ... – linguamachina

+0

Nếu bạn đặt điều này vào một bộ cấp phát, bạn có thể sử dụng phần còn lại của thư viện chuẩn một cách dễ dàng: 'vector '; không cần phải tái tạo lại bất cứ điều gì khác. –

2

Để trả lời câu hỏi của bạn, cả hai phương pháp đó đều an toàn. Chỉ có hai hoạt động thực sự bốc mùi có các diễn viên để size_tnew char[stuff]. Bạn nên sử dụng ít nhất uintptr_t từ số <cstdint> cho người đầu tiên. Thao tác thứ hai tạo ra vấn đề bí danh con trỏ duy nhất của bạn về mặt kỹ thuật, hàm tạo char được chạy trên mỗi phần tử char và cấu thành truy cập dữ liệu qua con trỏ char. Bạn nên sử dụng malloc để thay thế.

Giả sử con trỏ giả mạo khác không phải là vấn đề. Và đó là bởi vì ngoài hoạt động new bạn không truy cập bất kỳ dữ liệu nào thông qua các con trỏ bí danh. Bạn chỉ truy cập dữ liệu qua số T * mà bạn nhận được sau khi căn chỉnh.

Tất nhiên, bạn phải nhớ xây dựng tất cả các phần tử mảng của mình. Điều này đúng ngay cả trong phiên bản của bạn. Ai biết loại T mọi người sẽ đặt ở đó. Và, tất nhiên, nếu bạn làm điều đó, bạn sẽ phải nhớ để gọi destructors của họ, và phải nhớ để xử lý các trường hợp ngoại lệ khi bạn sao chép chúng (memcpy không cắt nó).

Nếu bạn có một tính năng C++ 11 cụ thể, bạn không cần phải thực hiện việc này. C++ 11 có một chức năng đặc biệt để căn chỉnh con trỏ tới các ranh giới tùy ý. Giao diện là một chút sôi nổi, nhưng nó sẽ làm công việc. Cuộc gọi là ::std::align được xác định trong <memory>. Cảm ơn R. Martinho Fernandes vì đã chỉ ra.

Đây là một phiên bản của chức năng của bạn với các đề nghị cố định:

#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

"Bí danh con trỏ không phải là một vấn đề. Và đó là vì bạn không truy cập bất kỳ dữ liệu nào thông qua các con trỏ bí danh." Tôi có thể thấy một mảng của char được xây dựng, và sau đó nó được truy cập thông qua một T * ... –

+0

@ R.MartinhoFernandes: Được rồi, bạn đã chính xác. Và tôi sẽ sửa câu trả lời của tôi. – Omnifarious

+0

@ R.MartinhoFernandes: Ở đó, tôi đã sửa nó. – Omnifarious

Các vấn đề liên quan