كيف يمكنني التحويل بين قيم endian الكبيرة وقيم endian الصغيرة في C++؟

StackOverflow https://stackoverflow.com/questions/105252

  •  01-07-2019
  •  | 
  •  

سؤال

كيف يمكنني التحويل بين قيم endian الكبيرة وقيم endian الصغيرة في C++؟

يحرر:من أجل الوضوح، لا بد لي من ترجمة البيانات الثنائية (قيم الفاصلة العائمة مزدوجة الدقة والأعداد الصحيحة 32 بت و64 بت) من بنية وحدة المعالجة المركزية إلى أخرى.هذا لا يتضمن التواصل، لذا فإن ntoh() والوظائف المشابهة لن تعمل هنا.

التعديل رقم 2:تنطبق الإجابة التي قبلتها مباشرة على المترجمين الذين أستهدفهم (وهذا هو سبب اختياري لها).ومع ذلك، هناك إجابات أخرى جيدة جدًا وأكثر قابلية للنقل هنا.

هل كانت مفيدة؟

المحلول

إذا كنت تستخدم فيجوال سي++ قم بما يلي:يمكنك تضمين intrin.h واستدعاء الوظائف التالية:

لأرقام 16 بت:

unsigned short _byteswap_ushort(unsigned short value);

لأرقام 32 بت:

unsigned long _byteswap_ulong(unsigned long value);

لأرقام 64 بت:

unsigned __int64 _byteswap_uint64(unsigned __int64 value);

لا يلزم تحويل أرقام 8 بت (الأحرف).

كما يتم تعريفها فقط للقيم غير الموقعة التي تعمل مع الأعداد الصحيحة الموقعة أيضًا.

بالنسبة للأعداد العشرية والمضاعفة، يكون الأمر أكثر صعوبة كما هو الحال مع الأعداد الصحيحة البسيطة، حيث قد تكون أو لا تكون بترتيب البايت في الأجهزة المضيفة.يمكنك الحصول على العوامات ذات النهاية الصغيرة على الأجهزة ذات النهاية الكبيرة والعكس صحيح.

المترجمون الآخرون لديهم جوهريات مماثلة أيضًا.

في مجلس التعاون الخليجي على سبيل المثال يمكنك الاتصال مباشرة:

int32_t __builtin_bswap32 (int32_t x)
int64_t __builtin_bswap64 (int64_t x)

(لا حاجة لتضمين شيء ما).تعلن Afaik bits.h عن نفس الوظيفة بطريقة غير مركزية في دول مجلس التعاون الخليجي أيضًا.

مبادلة 16 بت هي مجرد تدوير قليلاً.

إن استدعاء العناصر الجوهرية بدلاً من التدوير بنفسك يمنحك أفضل أداء وكثافة للتعليمات البرمجية.

نصائح أخرى

ببساطة:

#include <climits>

template <typename T>
T swap_endian(T u)
{
    static_assert (CHAR_BIT == 8, "CHAR_BIT != 8");

    union
    {
        T u;
        unsigned char u8[sizeof(T)];
    } source, dest;

    source.u = u;

    for (size_t k = 0; k < sizeof(T); k++)
        dest.u8[k] = source.u8[sizeof(T) - k - 1];

    return dest.u;
}

الاستخدام: swap_endian<uint32_t>(42).

من مغالطة ترتيب البايت بواسطة روب بايك:

لنفترض أن دفق البيانات الخاص بك يحتوي على عدد صحيح 32 بت مشفر بالنهاية.إليك كيفية استخراجها (بافتراض البايتات غير الموقعة):

i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);

إذا كان Big-Endian، فإليك كيفية استخراجه:

i = (data[3]<<0) | (data[2]<<8) | (data[1]<<16) | (data[0]<<24);

ليرة تركية؛دكتور: لا تقلق بشأن الترتيب الأصلي لنظامك الأساسي، فكل ما يهم هو ترتيب البايت للدفق الذي تقرأ منه، ومن الأفضل أن يكون محددًا جيدًا.

ملحوظة:تمت الإشارة في التعليق إلى أنه في غياب تحويل النوع الصريح، فمن المهم ذلك data تكون مجموعة من unsigned char أو uint8_t.استخدام signed char أو char (في حالة التوقيع) سوف يؤدي إلى data[x] يتم ترقيتها إلى عدد صحيح و data[x] << 24 من المحتمل أن يتم تحويل 1 إلى بت الإشارة وهو UB.

إذا كنت تفعل ذلك لأغراض التوافق مع الشبكة/المضيف، فيجب عليك استخدام:

ntohl() //Network to Host byte order (Long)
htonl() //Host to Network byte order (Long)

ntohs() //Network to Host byte order (Short)
htons() //Host to Network byte order (Short)

إذا كنت تفعل ذلك لسبب آخر، فإن أحد حلول byte_swap المقدمة هنا سيعمل بشكل جيد.

أخذت بعض الاقتراحات من هذا المنشور وجمعتها معًا لتشكيل هذا:

#include <boost/type_traits.hpp>
#include <boost/static_assert.hpp>
#include <boost/detail/endian.hpp>
#include <stdexcept>

enum endianness
{
    little_endian,
    big_endian,
    network_endian = big_endian,

    #if defined(BOOST_LITTLE_ENDIAN)
        host_endian = little_endian
    #elif defined(BOOST_BIG_ENDIAN)
        host_endian = big_endian
    #else
        #error "unable to determine system endianness"
    #endif
};

namespace detail {

template<typename T, size_t sz>
struct swap_bytes
{
    inline T operator()(T val)
    {
        throw std::out_of_range("data size");
    }
};

template<typename T>
struct swap_bytes<T, 1>
{
    inline T operator()(T val)
    {
        return val;
    }
};

template<typename T>
struct swap_bytes<T, 2>
{
    inline T operator()(T val)
    {
        return ((((val) >> 8) & 0xff) | (((val) & 0xff) << 8));
    }
};

template<typename T>
struct swap_bytes<T, 4>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff000000) >> 24) |
                (((val) & 0x00ff0000) >>  8) |
                (((val) & 0x0000ff00) <<  8) |
                (((val) & 0x000000ff) << 24));
    }
};

template<>
struct swap_bytes<float, 4>
{
    inline float operator()(float val)
    {
        uint32_t mem =swap_bytes<uint32_t, sizeof(uint32_t)>()(*(uint32_t*)&val);
        return *(float*)&mem;
    }
};

template<typename T>
struct swap_bytes<T, 8>
{
    inline T operator()(T val)
    {
        return ((((val) & 0xff00000000000000ull) >> 56) |
                (((val) & 0x00ff000000000000ull) >> 40) |
                (((val) & 0x0000ff0000000000ull) >> 24) |
                (((val) & 0x000000ff00000000ull) >> 8 ) |
                (((val) & 0x00000000ff000000ull) << 8 ) |
                (((val) & 0x0000000000ff0000ull) << 24) |
                (((val) & 0x000000000000ff00ull) << 40) |
                (((val) & 0x00000000000000ffull) << 56));
    }
};

template<>
struct swap_bytes<double, 8>
{
    inline double operator()(double val)
    {
        uint64_t mem =swap_bytes<uint64_t, sizeof(uint64_t)>()(*(uint64_t*)&val);
        return *(double*)&mem;
    }
};

template<endianness from, endianness to, class T>
struct do_byte_swap
{
    inline T operator()(T value)
    {
        return swap_bytes<T, sizeof(T)>()(value);
    }
};
// specialisations when attempting to swap to the same endianess
template<class T> struct do_byte_swap<little_endian, little_endian, T> { inline T operator()(T value) { return value; } };
template<class T> struct do_byte_swap<big_endian,    big_endian,    T> { inline T operator()(T value) { return value; } };

} // namespace detail

template<endianness from, endianness to, class T>
inline T byte_swap(T value)
{
    // ensure the data is only 1, 2, 4 or 8 bytes
    BOOST_STATIC_ASSERT(sizeof(T) == 1 || sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);
    // ensure we're only swapping arithmetic types
    BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

    return detail::do_byte_swap<from, to, T>()(value);
}

هناك تعليمات تجميع تسمى BSWAP والتي ستقوم بالتبديل نيابةً عنك، سريع جدا.يمكنك أن تقرأ عنها هنا.

يحتوي Visual Studio، أو بشكل أكثر دقة مكتبة وقت تشغيل Visual C++، على جوهر النظام الأساسي لهذا الغرض، والذي يسمى _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64().يجب أن يكون هناك شيء مشابه للمنصات الأخرى، لكني لست على علم بما سيتم تسميتها.

إن إجراء الانتقال من النهاية الكبيرة إلى النهاية الصغيرة هو نفس إجراء الانتقال من النهاية الصغيرة إلى النهاية الكبيرة.

إليك بعض الأمثلة على التعليمات البرمجية:

void swapByteOrder(unsigned short& us)
{
    us = (us >> 8) |
         (us << 8);
}

void swapByteOrder(unsigned int& ui)
{
    ui = (ui >> 24) |
         ((ui<<8) & 0x00FF0000) |
         ((ui>>8) & 0x0000FF00) |
         (ui << 24);
}

void swapByteOrder(unsigned long long& ull)
{
    ull = (ull >> 56) |
          ((ull<<40) & 0x00FF000000000000) |
          ((ull<<24) & 0x0000FF0000000000) |
          ((ull<<8) & 0x000000FF00000000) |
          ((ull>>8) & 0x00000000FF000000) |
          ((ull>>24) & 0x0000000000FF0000) |
          ((ull>>40) & 0x000000000000FF00) |
          (ull << 56);
}

لقد فعلنا ذلك باستخدام القوالب.يمكنك ذلك شيء من هذا القبيل:

// Specialization for 2-byte types.
template<>
inline void endian_byte_swapper< 2 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    ushort* p_dest = reinterpret_cast< ushort* >(dest);
    ushort const* const p_src = reinterpret_cast< ushort const* >(src);
    *p_dest = (*p_src >> 8) | (*p_src << 8);
}

// Specialization for 4-byte types.
template<>
inline void endian_byte_swapper< 4 >(char* dest, char const* src)
{
    // Use bit manipulations instead of accessing individual bytes from memory, much faster.
    uint* p_dest = reinterpret_cast< uint* >(dest);
    uint const* const p_src = reinterpret_cast< uint const* >(src);
    *p_dest = (*p_src >> 24) | ((*p_src & 0x00ff0000) >> 8) | ((*p_src & 0x0000ff00) << 8) | (*p_src << 24);
}

إذا كنت تفعل ذلك لنقل البيانات بين الأنظمة الأساسية المختلفة، فانظر إلى وظائف ntoh وhton.

بنفس الطريقة التي تفعلها في C:

short big = 0xdead;
short little = (((big & 0xff)<<8) | ((big & 0xff00)>>8));

يمكنك أيضًا الإعلان عن متجه للأحرف غير الموقعة، و memcpy قيمة الإدخال فيه، وعكس البايتات إلى متجه آخر و memcpy البايتات الخارجة، ولكن هذا سيستغرق أوامر ذات حجم أطول من التلاعب بالبت، خاصة مع قيم 64 بت.

في معظم أنظمة POSIX (حيث أنه ليس في معيار POSIX) يوجد endian.h، والذي يمكن استخدامه لتحديد التشفير الذي يستخدمه نظامك.من هناك شيء مثل هذا:

unsigned int change_endian(unsinged int x)
{
    unsigned char *ptr = (unsigned char *)&x;
    return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
}

يؤدي هذا إلى تبديل الترتيب (من النهاية الكبيرة إلى النهاية الصغيرة):

إذا كان لديك الرقم 0xDEADBEEF (على نظام endian صغير مخزّن كـ 0xEFBEADDE)، فإن ptr[0] سيكون 0xEF، وptr[1] هو 0xBE، وما إلى ذلك.

ولكن إذا كنت تريد استخدامه للتواصل، فإن htons وhtonl وhtonll (وntohs المعكوسة وntohl وntohll) ستكون مفيدة للتحويل من ترتيب المضيف إلى ترتيب الشبكة.

لاحظ أن htonl()، على الأقل بالنسبة لنظام التشغيل Windows، أبطأ بكثير من نظيرتها الجوهرية _byteswap_ulong().الأول عبارة عن استدعاء مكتبة DLL إلى ws2_32.dll، والأخير عبارة عن تعليمات تجميع BSWAP واحدة.لذلك، إذا كنت تكتب بعض التعليمات البرمجية المعتمدة على النظام الأساسي، ففضل استخدام العناصر الجوهرية للسرعة:

#define htonl(x) _byteswap_ulong(x)

قد يكون هذا مهمًا بشكل خاص لمعالجة صور .PNG حيث يتم حفظ كافة الأعداد الصحيحة في Big Endian مع شرح "يمكن للمرء استخدام htonl()..." {لإبطاء برامج Windows النموذجية، إذا لم تكن مستعدًا}.

تحتوي معظم الأنظمة الأساسية على ملف رأس النظام الذي يوفر وظائف بايت مبادلة فعالة.على نظام Linux هو موجود <endian.h>.يمكنك لفه بشكل جيد في C++:

#include <iostream>

#include <endian.h>

template<size_t N> struct SizeT {};

#define BYTESWAPS(bits) \
template<class T> inline T htobe(T t, SizeT<bits / 8>) { return htobe ## bits(t); } \
template<class T> inline T htole(T t, SizeT<bits / 8>) { return htole ## bits(t); } \
template<class T> inline T betoh(T t, SizeT<bits / 8>) { return be ## bits ## toh(t); } \
template<class T> inline T letoh(T t, SizeT<bits / 8>) { return le ## bits ## toh(t); }

BYTESWAPS(16)
BYTESWAPS(32)
BYTESWAPS(64)

#undef BYTESWAPS

template<class T> inline T htobe(T t) { return htobe(t, SizeT<sizeof t>()); }
template<class T> inline T htole(T t) { return htole(t, SizeT<sizeof t>()); }
template<class T> inline T betoh(T t) { return betoh(t, SizeT<sizeof t>()); }
template<class T> inline T letoh(T t) { return letoh(t, SizeT<sizeof t>()); }

int main()
{
    std::cout << std::hex;
    std::cout << htobe(static_cast<unsigned short>(0xfeca)) << '\n';
    std::cout << htobe(0xafbeadde) << '\n';

    // Use ULL suffix to specify integer constant as unsigned long long 
    std::cout << htobe(0xfecaefbeafdeedfeULL) << '\n';
}

انتاج:

cafe
deadbeaf
feeddeafbeefcafe

أنا أحب هذا فقط من أجل الأسلوب :-)

long swap(long i) {
    char *c = (char *) &i;
    return * (long *) (char[]) {c[3], c[2], c[1], c[0] };
}

بجد...لا أفهم لماذا كل الحلول هكذا معقد! ماذا عن وظيفة القالب الأبسط والأكثر عمومية التي تقوم بتبديل أي نوع بأي حجم تحت أي ظرف من الظروف في أي نظام تشغيل ؟؟؟؟

template <typename T>
void SwapEnd(T& var)
{
    char* varArray = reinterpret_cast<char*>(&var);
    for(long i = 0; i < static_cast<long>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
}

إنها القوة السحرية لـ C وC++ معًا!ما عليك سوى تبديل حرف المتغير الأصلي بحرف.

تذكر أنني لم أستخدم عامل التعيين البسيط "=" لأنه سيتم إفساد بعض الكائنات عند قلب endianness ولن يعمل مُنشئ النسخ (أو عامل التعيين).لذلك، من الأكثر موثوقية نسخها حرفًا تلو الآخر.

لنسميها، مجرد استخدام

double x = 5;
SwapEnd(x);

و الأن x يختلف في endianness.

لدي هذا الرمز الذي يسمح لي بالتحويل من HOST_ENDIAN_ORDER (مهما كان) إلى LITTLE_ENDIAN_ORDER أو BIG_ENDIAN_ORDER.أستخدم قالبًا، لذا إذا حاولت التحويل من HOST_ENDIAN_ORDER إلى LITTLE_ENDIAN_ORDER وصادف أنهما متماثلان بالنسبة للجهاز الذي أقوم بتجميعه، فلن يتم إنشاء أي تعليمات برمجية.

إليك الكود مع بعض التعليقات:

// We define some constant for little, big and host endianess. Here I use 
// BOOST_LITTLE_ENDIAN/BOOST_BIG_ENDIAN to check the host indianess. If you
// don't want to use boost you will have to modify this part a bit.
enum EEndian
{
  LITTLE_ENDIAN_ORDER,
  BIG_ENDIAN_ORDER,
#if defined(BOOST_LITTLE_ENDIAN)
  HOST_ENDIAN_ORDER = LITTLE_ENDIAN_ORDER
#elif defined(BOOST_BIG_ENDIAN)
  HOST_ENDIAN_ORDER = BIG_ENDIAN_ORDER
#else
#error "Impossible de determiner l'indianness du systeme cible."
#endif
};

// this function swap the bytes of values given it's size as a template
// parameter (could sizeof be used?).
template <class T, unsigned int size>
inline T SwapBytes(T value)
{
  union
  {
     T value;
     char bytes[size];
  } in, out;

  in.value = value;

  for (unsigned int i = 0; i < size / 2; ++i)
  {
     out.bytes[i] = in.bytes[size - 1 - i];
     out.bytes[size - 1 - i] = in.bytes[i];
  }

  return out.value;
}

// Here is the function you will use. Again there is two compile-time assertion
// that use the boost librarie. You could probably comment them out, but if you
// do be cautious not to use this function for anything else than integers
// types. This function need to be calles like this :
//
//     int x = someValue;
//     int i = EndianSwapBytes<HOST_ENDIAN_ORDER, BIG_ENDIAN_ORDER>(x);
//
template<EEndian from, EEndian to, class T>
inline T EndianSwapBytes(T value)
{
  // A : La donnée à swapper à une taille de 2, 4 ou 8 octets
  BOOST_STATIC_ASSERT(sizeof(T) == 2 || sizeof(T) == 4 || sizeof(T) == 8);

  // A : La donnée à swapper est d'un type arithmetic
  BOOST_STATIC_ASSERT(boost::is_arithmetic<T>::value);

  // Si from et to sont du même type on ne swap pas.
  if (from == to)
     return value;

  return SwapBytes<T, sizeof(T)>(value);
}

إذا كان العدد الصحيح غير الموقع ذو 32 بت ذو النهاية الكبيرة يبدو مثل 0xAABBCCDD والذي يساوي 2864434397، فإن نفس العدد الصحيح غير الموقع ذو 32 بت يبدو مثل 0xDDCCBBAA على معالج ذو نهاية صغيرة والذي يساوي أيضًا 2864434397.

إذا كان الاختصار الكبير غير الموقع ذو 16 بت يشبه 0xAABB الذي يساوي 43707، فإن نفس الاختصار غير الموقع ذو 16 بت يبدو مثل 0xBBAA على معالج ذو النهاية الصغيرة والذي يساوي أيضًا 43707.

فيما يلي بعض الوظائف المفيدة #define لمبادلة البايتات من النهاية الصغيرة إلى النهاية الكبيرة والعكس -->

// can be used for short, unsigned short, word, unsigned word (2-byte types)
#define BYTESWAP16(n) (((n&0xFF00)>>8)|((n&0x00FF)<<8))

// can be used for int or unsigned int or float (4-byte types)
#define BYTESWAP32(n) ((BYTESWAP16((n&0xFFFF0000)>>16))|((BYTESWAP16(n&0x0000FFFF))<<16))

// can be used for unsigned long long or double (8-byte types)
#define BYTESWAP64(n) ((BYTESWAP32((n&0xFFFFFFFF00000000)>>32))|((BYTESWAP32(n&0x00000000FFFFFFFF))<<32))

إليك نسخة عامة توصلت إليها من أعلى رأسي، لتبديل قيمة في مكانها.ستكون الاقتراحات الأخرى أفضل إذا كان الأداء يمثل مشكلة.

 template<typename T>
    void ByteSwap(T * p)
    {
        for (int i = 0;  i < sizeof(T)/2;  ++i)
            std::swap(((char *)p)[i], ((char *)p)[sizeof(T)-1-i]);
    }

تنصل: لم أحاول تجميع هذا أو اختباره بعد.

إذا اتبعت النمط الشائع لعكس ترتيب البتات في الكلمة، وقمت بإعدام الجزء الذي يعكس البتات داخل كل بايت، فسيتبقى لديك شيء يعكس البايتات فقط داخل الكلمة.لأنظمة 64 بت:

x = ((x & 0x00000000ffffffff) << 32) ^ ((x >> 32) & 0x00000000ffffffff);
x = ((x & 0x0000ffff0000ffff) << 16) ^ ((x >> 16) & 0x0000ffff0000ffff);
x = ((x & 0x00ff00ff00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff00ff00ff);

المترجم يجب قم بتنظيف عمليات إخفاء البتات غير الضرورية (تركتها لتسليط الضوء على النمط)، ولكن إذا لم يحدث ذلك، فيمكنك إعادة كتابة السطر الأول بهذه الطريقة:

x = ( x                       << 32) ^  (x >> 32);

يجب أن يتم تبسيط ذلك عادةً إلى تعليمات تدوير واحدة في معظم البنيات (مع تجاهل أن العملية بأكملها ربما تكون تعليمة واحدة).

في معالج RISC، قد تتسبب الثوابت الكبيرة والمعقدة في حدوث صعوبات في برنامج التحويل البرمجي.ومع ذلك، يمكنك حساب كل من الثوابت من الثوابت السابقة بشكل تافه.مثل ذلك:

uint64_t k = 0x00000000ffffffff; /* compiler should know a trick for this */
x = ((x & k) << 32) ^ ((x >> 32) & k);
k ^= k << 16;
x = ((x & k) << 16) ^ ((x >> 16) & k);
k ^= k << 8;
x = ((x & k) <<  8) ^ ((x >>  8) & k);

إذا أردت، يمكنك كتابة ذلك كحلقة.لن تكون فعالة، ولكن للمتعة فقط:

int i = sizeof(x) * CHAR_BIT / 2;
uintmax_t k = (1 << i) - 1;
while (i >= 8)
{
    x = ((x & k) << i) ^ ((x >> i) & k);
    i >>= 1;
    k ^= k << i;
}

وللاكتمال، إليك النسخة المبسطة 32 بت من النموذج الأول:

x = ( x               << 16) ^  (x >> 16);
x = ((x & 0x00ff00ff) <<  8) ^ ((x >>  8) & 0x00ff00ff);

اعتقدت أنني أضفت الحل الخاص بي هنا لأنني لم أره في أي مكان.إنها وظيفة صغيرة ومحمولة منبثقة من لغة C++ ومحمولة تستخدم عمليات البت فقط.

template<typename T> inline static T swapByteOrder(const T& val) {
    int totalBytes = sizeof(val);
    T swapped = (T) 0;
    for (int i = 0; i < totalBytes; ++i) {
        swapped |= (val >> (8*(totalBytes-i-1)) & 0xFF) << (8*i);
    }
    return swapped;
}

باستخدام الرموز الواردة أدناه، يمكنك التبديل بين BigEndian وLittleEndian بسهولة

#define uint32_t unsigned 
#define uint16_t unsigned short

#define swap16(x) ((((uint16_t)(x) & 0x00ff)<<8)| \
(((uint16_t)(x) & 0xff00)>>8))

#define swap32(x) ((((uint32_t)(x) & 0x000000ff)<<24)| \
(((uint32_t)(x) & 0x0000ff00)<<8)| \
(((uint32_t)(x) & 0x00ff0000)>>8)| \
(((uint32_t)(x) & 0xff000000)>>24))

أنا مندهش حقًا لأنه لم يذكر أحد وظائف htobeXX وbetohXX.تم تعريفها في endian.h وهي مشابهة جدًا لوظائف الشبكة htonXX.

واو، لم أستطع أن أصدق بعض الإجابات التي قرأتها هنا.هناك في الواقع تعليمات في التجميع تقوم بذلك بشكل أسرع من أي شيء آخر.com.bswap.يمكنك ببساطة كتابة وظيفة مثل هذا ...

__declspec(naked) uint32_t EndianSwap(uint32 value)
{
    __asm
    {
        mov eax, dword ptr[esp + 4]
        bswap eax
        ret
    }
}

إنها كثيراً أسرع من الجوهرية التي تم اقتراحها.لقد تفكيكها ونظرت.لا تحتوي الوظيفة المذكورة أعلاه على مقدمة/خاتمة، لذا لا يوجد بها فعليًا أي حمل على الإطلاق.

unsigned long _byteswap_ulong(unsigned long value);

يعد إجراء 16 بت أمرًا سهلاً تمامًا، باستثناء أنك ستستخدم xchg al، ah.يعمل bswap فقط على سجلات 32 بت.

يعد الإصدار 64 بت أكثر صعوبة بعض الشيء، ولكن ليس بشكل مفرط.أفضل بكثير من جميع الأمثلة المذكورة أعلاه مع الحلقات والقوالب وما إلى ذلك.

هناك بعض التحذيرات هنا...أولاً، يتوفر bswap فقط على وحدات المعالجة المركزية (CPU) 80x486 وما فوق.هل هناك من يخطط لتشغيله على 386؟!؟إذا كان الأمر كذلك، فلا يزال بإمكانك استبدال bswap بـ...

mov ebx, eax
shr ebx, 16
xchg bl, bh
xchg al, ah
shl eax, 16
or eax, ebx

كما يتوفر التجميع المضمّن فقط في كود x86 في Visual Studio.لا يمكن سطر الدالة المجردة كما أنها غير متوفرة في إصدارات x64.في هذه الحالة، سيتعين عليك استخدام جوهر المترجم.

تقنية محمولة لتنفيذ أدوات الوصول endian غير المحاذية وغير المضمنة والصديقة للمُحسِّن.إنهم يعملون على كل مترجم، وكل محاذاة حدودية، وكل ترتيب بايت.يتم استكمال هذه الإجراءات غير المحاذاة أو طرحها اعتمادًا على النهاية الأصلية والمحاذاة.قائمة جزئية ولكنك حصلت على الفكرة.BO* هي قيم ثابتة تعتمد على ترتيب البايت الأصلي.

uint32_t sw_get_uint32_1234(pu32)
uint32_1234 *pu32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32_1234[0] = (*pu32)[BO32_0];
  bou32.u32_1234[1] = (*pu32)[BO32_1];
  bou32.u32_1234[2] = (*pu32)[BO32_2];
  bou32.u32_1234[3] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_1234(pu32, u32)
uint32_1234 *pu32;
uint32_t u32;
{
  union {
    uint32_1234 u32_1234;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_1234[0];
  (*pu32)[BO32_1] = bou32.u32_1234[1];
  (*pu32)[BO32_2] = bou32.u32_1234[2];
  (*pu32)[BO32_3] = bou32.u32_1234[3];
}

#if HAS_SW_INT64
int64 sw_get_int64_12345678(pi64)
int64_12345678 *pi64;
{
  union {
    int64_12345678 i64_12345678;
    int64 i64;
  } boi64;
  boi64.i64_12345678[0] = (*pi64)[BO64_0];
  boi64.i64_12345678[1] = (*pi64)[BO64_1];
  boi64.i64_12345678[2] = (*pi64)[BO64_2];
  boi64.i64_12345678[3] = (*pi64)[BO64_3];
  boi64.i64_12345678[4] = (*pi64)[BO64_4];
  boi64.i64_12345678[5] = (*pi64)[BO64_5];
  boi64.i64_12345678[6] = (*pi64)[BO64_6];
  boi64.i64_12345678[7] = (*pi64)[BO64_7];
  return(boi64.i64);
}
#endif

int32_t sw_get_int32_3412(pi32)
int32_3412 *pi32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32_3412[2] = (*pi32)[BO32_0];
  boi32.i32_3412[3] = (*pi32)[BO32_1];
  boi32.i32_3412[0] = (*pi32)[BO32_2];
  boi32.i32_3412[1] = (*pi32)[BO32_3];
  return(boi32.i32);
}

void sw_set_int32_3412(pi32, i32)
int32_3412 *pi32;
int32_t i32;
{
  union {
    int32_3412 i32_3412;
    int32_t i32;
  } boi32;
  boi32.i32 = i32;
  (*pi32)[BO32_0] = boi32.i32_3412[2];
  (*pi32)[BO32_1] = boi32.i32_3412[3];
  (*pi32)[BO32_2] = boi32.i32_3412[0];
  (*pi32)[BO32_3] = boi32.i32_3412[1];
}

uint32_t sw_get_uint32_3412(pu32)
uint32_3412 *pu32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32_3412[2] = (*pu32)[BO32_0];
  bou32.u32_3412[3] = (*pu32)[BO32_1];
  bou32.u32_3412[0] = (*pu32)[BO32_2];
  bou32.u32_3412[1] = (*pu32)[BO32_3];
  return(bou32.u32);
}

void sw_set_uint32_3412(pu32, u32)
uint32_3412 *pu32;
uint32_t u32;
{
  union {
    uint32_3412 u32_3412;
    uint32_t u32;
  } bou32;
  bou32.u32 = u32;
  (*pu32)[BO32_0] = bou32.u32_3412[2];
  (*pu32)[BO32_1] = bou32.u32_3412[3];
  (*pu32)[BO32_2] = bou32.u32_3412[0];
  (*pu32)[BO32_3] = bou32.u32_3412[1];
}

float sw_get_float_1234(pf)
float_1234 *pf;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f_1234[0] = (*pf)[BO32_0];
  bof.f_1234[1] = (*pf)[BO32_1];
  bof.f_1234[2] = (*pf)[BO32_2];
  bof.f_1234[3] = (*pf)[BO32_3];
  return(bof.f);
}

void sw_set_float_1234(pf, f)
float_1234 *pf;
float f;
{
  union {
    float_1234 f_1234;
    float f;
  } bof;
  bof.f = (float)f;
  (*pf)[BO32_0] = bof.f_1234[0];
  (*pf)[BO32_1] = bof.f_1234[1];
  (*pf)[BO32_2] = bof.f_1234[2];
  (*pf)[BO32_3] = bof.f_1234[3];
}

double sw_get_double_12345678(pd)
double_12345678 *pd;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d_12345678[0] = (*pd)[BO64_0];
  bod.d_12345678[1] = (*pd)[BO64_1];
  bod.d_12345678[2] = (*pd)[BO64_2];
  bod.d_12345678[3] = (*pd)[BO64_3];
  bod.d_12345678[4] = (*pd)[BO64_4];
  bod.d_12345678[5] = (*pd)[BO64_5];
  bod.d_12345678[6] = (*pd)[BO64_6];
  bod.d_12345678[7] = (*pd)[BO64_7];
  return(bod.d);
}

void sw_set_double_12345678(pd, d)
double_12345678 *pd;
double d;
{
  union {
    double_12345678 d_12345678;
    double d;
  } bod;
  bod.d = d;
  (*pd)[BO64_0] = bod.d_12345678[0];
  (*pd)[BO64_1] = bod.d_12345678[1];
  (*pd)[BO64_2] = bod.d_12345678[2];
  (*pd)[BO64_3] = bod.d_12345678[3];
  (*pd)[BO64_4] = bod.d_12345678[4];
  (*pd)[BO64_5] = bod.d_12345678[5];
  (*pd)[BO64_6] = bod.d_12345678[6];
  (*pd)[BO64_7] = bod.d_12345678[7];
}

تتمتع هذه الأنواع بميزة زيادة أخطاء المترجم إذا لم يتم استخدامها مع أدوات الوصول، وبالتالي التخفيف من أخطاء أدوات الوصول المنسية.

typedef char int8_1[1], uint8_1[1];

typedef char int16_12[2], uint16_12[2]; /* little endian */
typedef char int16_21[2], uint16_21[2]; /* big endian */

typedef char int24_321[3], uint24_321[3]; /* Alpha Micro, PDP-11 */

typedef char int32_1234[4], uint32_1234[4]; /* little endian */
typedef char int32_3412[4], uint32_3412[4]; /* Alpha Micro, PDP-11 */
typedef char int32_4321[4], uint32_4321[4]; /* big endian */

typedef char int64_12345678[8], uint64_12345678[8]; /* little endian */
typedef char int64_34128756[8], uint64_34128756[8]; /* Alpha Micro, PDP-11 */
typedef char int64_87654321[8], uint64_87654321[8]; /* big endian */

typedef char float_1234[4]; /* little endian */
typedef char float_3412[4]; /* Alpha Micro, PDP-11 */
typedef char float_4321[4]; /* big endian */

typedef char double_12345678[8]; /* little endian */
typedef char double_78563412[8]; /* Alpha Micro? */
typedef char double_87654321[8]; /* big endian */

لقد قمت مؤخرًا بكتابة ماكرو للقيام بذلك في لغة C، ولكنه صالح بنفس القدر في لغة C++:

#define REVERSE_BYTES(...) do for(size_t REVERSE_BYTES=0; REVERSE_BYTES<sizeof(__VA_ARGS__)>>1; ++REVERSE_BYTES)\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES],\
    ((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES] ^= ((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES];\
while(0)

يقبل أي نوع ويعكس البايتات في الوسيطة التي تم تمريرها.أمثلة على الاستخدامات:

int main(){
    unsigned long long x = 0xABCDEF0123456789;
    printf("Before: %llX\n",x);
    REVERSE_BYTES(x);
    printf("After : %llX\n",x);

    char c[7]="nametag";
    printf("Before: %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
    REVERSE_BYTES(c);
    printf("After : %c%c%c%c%c%c%c\n",c[0],c[1],c[2],c[3],c[4],c[5],c[6]);
}

الذي يطبع:

Before: ABCDEF0123456789
After : 8967452301EFCDAB
Before: nametag
After : gateman

ما ورد أعلاه قابل للنسخ/اللصق تمامًا، ولكن هناك الكثير مما يحدث هنا، لذا سأشرح كيفية عمله قطعة قطعة:

أول شيء ملحوظ هو أن الماكرو بأكمله مغلف في ملف do while(0) حاجز.هذا ال لغة مشتركة للسماح باستخدام الفاصلة المنقوطة العادية بعد الماكرو.

التالي هو استخدام متغير اسمه REVERSE_BYTES كما for عداد الحلقة.يتم استخدام اسم الماكرو نفسه كاسم متغير لضمان عدم تعارضه مع أي رموز أخرى قد تكون في النطاق أينما يتم استخدام الماكرو.نظرًا لأنه يتم استخدام الاسم ضمن توسيع الماكرو، فلن يتم توسيعه مرة أخرى عند استخدامه كاسم متغير هنا.

في حدود for حلقة، هناك وحدتان من البايتات يتم الرجوع إليها و تم تبديل XOR (لذا فإن اسم المتغير المؤقت غير مطلوب):

((unsigned char*)&(__VA_ARGS__))[REVERSE_BYTES]
((unsigned char*)&(__VA_ARGS__))[sizeof(__VA_ARGS__)-1-REVERSE_BYTES]

__VA_ARGS__ يمثل كل ما تم إعطاؤه للماكرو، ويستخدم لزيادة مرونة ما يمكن تمريره (وإن لم يكن كثيرًا).يتم بعد ذلك أخذ عنوان هذه الوسيطة وإحالته إلى ملف unsigned char المؤشر للسماح بتبديل وحدات البايت الخاصة به عبر المصفوفة [] الاشتراك.

النقطة الغريبة الأخيرة هي عدم وجود {} الأقواس.إنها ليست ضرورية لأن جميع الخطوات في كل مبادلة مرتبطة بـ مشغل الفاصلة, ، وجعلهم بيانا واحدا.

أخيرًا، تجدر الإشارة إلى أن هذا ليس هو النهج المثالي إذا كانت السرعة هي الأولوية القصوى.إذا كان هذا عاملاً مهمًا، فمن المحتمل أن تكون بعض وحدات الماكرو الخاصة بالنوع أو التوجيهات الخاصة بالنظام الأساسي المشار إليها في الإجابات الأخرى خيارًا أفضل.ومع ذلك، فإن هذا الأسلوب قابل للنقل لجميع الأنواع، وجميع الأنظمة الأساسية الرئيسية، وكل من لغات C وC++.

يحاول Boost::endian, ولا تنفذه بنفسك!

وهنا أ وصلة

فيما يلي كيفية قراءة البيانات المزدوجة المخزنة بتنسيق IEEE 754 64 بت، حتى لو كان الكمبيوتر المضيف لديك يستخدم نظامًا مختلفًا.

/*
* read a double from a stream in ieee754 format regardless of host
*  encoding.
*  fp - the stream
*  bigendian - set to if big bytes first, clear for little bytes
*              first
*
*/
double freadieee754(FILE *fp, int bigendian)
{
    unsigned char buff[8];
    int i;
    double fnorm = 0.0;
    unsigned char temp;
    int sign;
    int exponent;
    double bitval;
    int maski, mask;
    int expbits = 11;
    int significandbits = 52;
    int shift;
    double answer;

    /* read the data */
    for (i = 0; i < 8; i++)
        buff[i] = fgetc(fp);
    /* just reverse if not big-endian*/
    if (!bigendian)
    {
        for (i = 0; i < 4; i++)
        {
            temp = buff[i];
            buff[i] = buff[8 - i - 1];
            buff[8 - i - 1] = temp;
        }
    }
    sign = buff[0] & 0x80 ? -1 : 1;
    /* exponet in raw format*/
    exponent = ((buff[0] & 0x7F) << 4) | ((buff[1] & 0xF0) >> 4);

    /* read inthe mantissa. Top bit is 0.5, the successive bits half*/
    bitval = 0.5;
    maski = 1;
    mask = 0x08;
    for (i = 0; i < significandbits; i++)
    {
        if (buff[maski] & mask)
            fnorm += bitval;

        bitval /= 2.0;
        mask >>= 1;
        if (mask == 0)
        {
            mask = 0x80;
            maski++;
        }
    }
    /* handle zero specially */
    if (exponent == 0 && fnorm == 0)
        return 0.0;

    shift = exponent - ((1 << (expbits - 1)) - 1); /* exponent = shift + bias */
    /* nans have exp 1024 and non-zero mantissa */
    if (shift == 1024 && fnorm != 0)
        return sqrt(-1.0);
    /*infinity*/
    if (shift == 1024 && fnorm == 0)
    {

#ifdef INFINITY
        return sign == 1 ? INFINITY : -INFINITY;
#endif
        return  (sign * 1.0) / 0.0;
    }
    if (shift > -1023)
    {
        answer = ldexp(fnorm + 1.0, shift);
        return answer * sign;
    }
    else
    {
        /* denormalised numbers */
        if (fnorm == 0.0)
            return 0.0;
        shift = -1022;
        while (fnorm < 1.0)
        {
            fnorm *= 2;
            shift--;
        }
        answer = ldexp(fnorm, shift);
        return answer * sign;
    }
}

بالنسبة لبقية مجموعة الوظائف، بما في ذلك إجراءات الكتابة والأعداد الصحيحة، راجع مشروع جيثب الخاص بي

https://github.com/MalcolmMcLean/ieee754

يوفر تبديل البايت باستخدام خدعة 3-step-xor القديمة حول المحور في وظيفة القالب حلاً مرنًا وسريعًا لـ O(ln2) لا يتطلب مكتبة، ويرفض النمط هنا أيضًا أنواع بايت واحدة:

template<typename T>void swap(T &t){
    for(uint8_t pivot = 0; pivot < sizeof(t)/2; pivot ++){
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
        *((uint8_t *)&t+sizeof(t)-1- pivot) ^= *((uint8_t *)&t + pivot);
        *((uint8_t *)&t + pivot) ^= *((uint8_t *)&t+sizeof(t)-1- pivot);
    }
}

يبدو أن الطريقة الآمنة هي استخدام htons في كل كلمة.لذا، إذا كان لديك...

std::vector<uint16_t> storage(n);  // where n is the number to be converted

// the following would do the trick
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });

ما ورد أعلاه سيكون محظورًا إذا كنت تستخدم نظامًا ذو نهاية كبيرة، لذلك سأبحث عن أي شيء يستخدمه نظامك الأساسي كشرط وقت الترجمة لتحديد ما إذا كان htons محظورًا أم لا.إنه O(n) بعد كل شيء.على جهاز Mac، سيكون الأمر مثل ...

#if (__DARWIN_BYTE_ORDER != __DARWIN_BIG_ENDIAN)
std::transform(word_storage.cbegin(), word_storage.cend()
  , word_storage.begin(), [](const uint16_t input)->uint16_t {
  return htons(input); });
#endif

ابحث عن تغيير البت، حيث أن هذا هو كل ما عليك فعله للتبديل من endian الصغير -> الكبير.وبعد ذلك، اعتمادًا على حجم البت، يمكنك تغيير كيفية إجراء إزاحة البت.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top