2013-03-07 12 views
11

अच्छी तरह से जमीन पर एक मूर्खतापूर्ण पहली पोस्ट क्या हो सकता है के लिए अग्रिम क्षमा करें। हालांकि इस विषय पर बहुत सारी सामग्री है, लेकिन इसमें से बहुत कम मेरे लिए निश्चित और/या समझदार है।सी ++: सख्त एलियासिंग बनाम यूनियन दुरुपयोग

मेरे पास AlignedArray टेम्पलेट क्लास है जो मनमाने ढंग से संरेखण के साथ ढेर पर स्मृति आवंटित करने के लिए है (मुझे AVX असेंबली दिनचर्या के लिए 32-बाइट संरेखण की आवश्यकता है)। इसके लिए कुछ बदसूरत सूचक हेरफेर की आवश्यकता है।

एग्नेर फोग cppexamples.zip में एक नमूना वर्ग प्रदान करता है जो ऐसा करने के लिए एक संघ का दुरुपयोग करता है (http://www.agner.org/optimize/optimization_manuals.zip)। हालांकि, मुझे पता है कि एक संघ के एक सदस्य को लिखना और फिर यूबी में दूसरे परिणामों से पढ़ना।

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

तो जो विधि सुरक्षित है (आर): यहाँ मेरी AlignedArray वर्ग के एक संक्षिप्त संस्करण (अनिवार्य रूप से Agner के एक फिर से लिखने, मेरी समझ में मदद करने के लिए) है?

+3

के बजाय 'निर्माण char' वस्तुओं और फिर कास्ट' ऑपरेटर new' से कि टी करने के लिए, तुम क्यों कच्चे स्मृति हड़पने नहीं है (:

यहाँ के साथ सुझाव दिया तय अपने कार्य का एक संस्करण है , या यहां तक ​​कि 'malloc'),' void * 'के रूप में, और वास्तव में इसमें' टी 'ऑब्जेक्ट्स का निर्माण करते हैं? असल में: यदि आप टी वस्तुओं चाहते हैं, टी वस्तुओं का निर्माण। इस उपयोग के मामले (गठबंधन सरणी) * शून्य * एलियासिंग चाल/यूनियन/memcpy/जो कुछ भी के लिए की जरूरत है। –

+0

@ आर। मार्टिनिन्हो फर्नांडीस: इसके अलावा, 'शून्य * 'पर गणित की अनुमति नहीं है। आप एक गठबंधन 'शून्य * 'कैसे प्राप्त करते हैं? – Omnifarious

+0

@ ओम्निफायरस आखिरी मैंने चेक किया, गणित को 'char *' पर भी अनुमति नहीं है। (और यदि यह भी था, तो इसका मतलब यह नहीं होगा कि आपको चार वस्तुओं का निर्माण करने और टी वस्तुओं का निर्माण करने की आवश्यकता नहीं है) आपको गणित करने के लिए पूर्णांक की आवश्यकता है। सी ++ 11 में पोर्टेबल समाधान http://en.cppreference.com/w/cpp/memory/align है। सैद्धांतिक रूप से पोर्टेबल समाधान एक संख्यात्मक प्रकार को reinterpret_cast करना है, गणित करें, और reinterpret_cast वापस करें। (यह अभ्यास में काफी पोर्टेबल है क्योंकि सभी कार्यान्वयनों में मुझे पता है कि संख्यात्मक प्रकारों के लिए reinterpret_cast अपेक्षित व्यवहार करता है) –

उत्तर

3

मेरे पास कोड है जो सिमड (यानी, एसएसई/एवीएक्स) के लिए उपयुक्त (प्रतिस्थापन) new और delete ऑपरेटरों को लागू करता है। यह निम्न कार्यों का उपयोग करता है जो आपको उपयोगी मिल सकती हैं:

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

यह अनुकूलित करना आसान होना चाहिए। स्पष्ट रूप से आप malloc और free को प्रतिस्थापित करेंगे, क्योंकि आप कच्चे (char) संग्रहण के लिए वैश्विक new और delete का उपयोग कर रहे हैं। यह मानता है कि size_t अंक अंकगणित के लिए पर्याप्त रूप से विस्तृत है - अभ्यास में सच है, लेकिन <cstdint> से अधिक सही होगा।

+0

पॉज़िक्स सिस्टम पर, posix_memalign() – BatchyX

+0

इसके लिए धन्यवाद, यह सबसे उपयोगी है (और हम दोनों अनिवार्य रूप से एक ही सूचक मंगिंग कर रहे हैं)। क्या कोई मौका है कि हमारे उदाहरणों में से कोई भी अपरिभाषित व्यवहार का परिणाम हो सकता है? मुझे यकीन नहीं है कि हम यहां सख्त एलियासिंग नियमों का उल्लंघन कर रहे हैं या नहीं ... – linguamachina

+0

यदि आप इसे आवंटक में डालते हैं, तो आप आसानी से बाकी मानक लाइब्रेरी का उपयोग कर सकते हैं: 'vector '; किसी और चीज को फिर से शुरू करने की जरूरत नहीं है। –

2

अपने प्रश्न का उत्तर देने के लिए, दोनों विधियां उतनी ही सुरक्षित हैं। वास्तव में बदबूदार केवल दो ऑपरेशन size_t और new char[stuff] पर कलाकार हैं। आपको कम से कम <cstdint> से पहले uintptr_t का उपयोग करना चाहिए। दूसरा ऑपरेशन आपके केवल पॉइंटर एलियासिंग मुद्दे को तकनीकी रूप से char कन्स्ट्रक्टर प्रत्येक char तत्व पर चलाया जाता है और यह char पॉइंटर के माध्यम से डेटा तक पहुंचने का गठन करता है। आपको इसके बजाय malloc का उपयोग करना चाहिए।

अन्य माना जाता है कि 'पॉइंटर एलियासिंग' कोई मुद्दा नहीं है। और ऐसा इसलिए है क्योंकि new ऑपरेशन के अलावा आप एलियाड पॉइंटर्स के माध्यम से किसी भी डेटा तक नहीं पहुंच रहे हैं। आप संरेखण के बाद प्राप्त होने वाले T * के माध्यम से केवल डेटा तक पहुंच रहे हैं।

बेशक, आपको अपने सभी सरणी तत्वों को बनाने के लिए याद रखना होगा। यह आपके संस्करण में भी सच है। कौन जानता है कि T लोग किस प्रकार रखेंगे। और, ज़ाहिर है, अगर आप ऐसा करते हैं, तो आपको अपने विनाशकों को फोन करना याद रखना होगा, और जब आप उन्हें कॉपी करते हैं तो अपवादों को संभालना याद रखना होगा (memcpy इसे काट नहीं देता है)।

यदि आपके पास कोई विशेष सी ++ 11 सुविधा है, तो आपको ऐसा करने की आवश्यकता नहीं है। सी ++ 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

"पॉइंटर एलियासिंग कोई मुद्दा नहीं है। और ऐसा इसलिए है क्योंकि आप एलियाड पॉइंटर्स के माध्यम से किसी भी डेटा तक नहीं पहुंच रहे हैं।" मैं चारों का निर्माण करने की एक सरणी देख सकता हूं, और फिर इसे टी * के माध्यम से पहुंचा जा सकता है ... –

+0

@ आर। मार्टिन्होफर्नैंड्स: ठीक है, आप सही हैं। और मैं अपना जवाब ठीक कर दूंगा। – Omnifarious

+0

@ आर। मार्टिन्होफर्नैंड्स: वहां, मैंने इसे ठीक किया। – Omnifarious

संबंधित मुद्दे