ฉันจะแปลงระหว่างค่า big-endian และ little-endian ใน C ++ ได้อย่างไร


196

ฉันจะแปลงระหว่างค่า big-endian และ little-endian ใน C ++ ได้อย่างไร

แก้ไข: เพื่อความชัดเจนฉันต้องแปลข้อมูลไบนารี (ค่าทศนิยมที่มีความแม่นยำสองเท่าและจำนวนเต็ม 32- บิตและ 64- บิต) จากสถาปัตยกรรม CPU หนึ่งไปยังอีกสถาปัตยกรรมหนึ่ง สิ่งนี้ไม่เกี่ยวข้องกับเครือข่ายดังนั้น ntoh () และฟังก์ชั่นที่คล้ายกันจะไม่ทำงานที่นี่

แก้ไข # 2: คำตอบที่ฉันยอมรับนำไปใช้โดยตรงกับคอมไพเลอร์ที่ฉันกำหนดเป้าหมาย (ซึ่งเป็นเหตุผลที่ฉันเลือกมัน) อย่างไรก็ตามมีคำตอบที่ดีมากพกพาได้มากกว่าที่นี่


21
ntoh hton จะทำงานได้ดีแม้ว่าจะไม่มีอะไรเกี่ยวข้องกับระบบเครือข่าย
Ben Collins

2
วิธีที่ดีที่สุดในการจัดการกับ endianness โดยทั่วไปคือการตรวจสอบให้แน่ใจว่าโค้ดทำงานบนเครื่องโฮสต์ทั้งขนาดเล็กและใหญ่ หากใช้งานได้คุณอาจทำถูกต้อง สมมติว่าคุณอยู่ใน x86 / เป็นอันตรายในทางปฏิบัติ
jakobengblom2

10
hton ntoh จะไม่ทำงานหากเครื่องใหญ่ - endian เพราะผู้ถามคำถามต้องการทำการแปลงอย่างชัดเจน
fabspro

6
@ jakobengblom2 เป็นคนเดียวที่พูดถึงเรื่องนี้ ตัวอย่างเกือบทั้งหมดในหน้านี้ใช้แนวคิดเช่น "สลับ" แทนการไม่เชื่อเรื่อง endianness หากคุณกำลังจัดการกับรูปแบบไฟล์ภายนอก (ซึ่งมี endianness ที่กำหนดไว้อย่างดี) สิ่งที่พกพาได้มากที่สุดคือปฏิบัติต่อข้อมูลภายนอกเป็นสตรีมไบต์และแปลงไบต์สตรีมเป็นและจากจำนวนเต็มดั้งเดิม ฉันประจบประแจงทุกครั้งที่ฉันเห็นshort swap(short x)รหัสเพราะมันจะแตกถ้าคุณย้ายไปยังแพลตฟอร์มที่มีความแตกต่างกัน Matthieu M มีคำตอบที่ถูกต้องเท่านั้นด้านล่าง
Mark Lakata

3
คุณกำลังคิดเกี่ยวกับปัญหาที่ผิดอย่างสมบูรณ์ ภารกิจไม่ใช่ "ฉันจะแปลงระหว่างค่าใหญ่และค่าเล็ก ๆ น้อย ๆ " ได้อย่างไร งานคือ "ฉันจะแปลงค่าทศนิยมและจำนวนเต็มในรูปแบบเฉพาะเป็นรูปแบบดั้งเดิมของแพลตฟอร์ม" ได้อย่างไร หากคุณทำถูกต้องรูปแบบดั้งเดิมอาจเป็น endian ขนาดใหญ่, endian น้อย, endian ผสมหรือ ternary สำหรับรหัสทั้งหมดของคุณ
David Schwartz

คำตอบ:


166

หากคุณใช้Visual C ++ ให้ทำดังต่อไปนี้: คุณรวม 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 บิต (ตัวอักษร)

นอกจากนี้ยังมีการกำหนดไว้สำหรับค่าที่ไม่ได้ลงนามเท่านั้นซึ่งจะทำงานกับจำนวนเต็มที่ลงนามแล้ว

สำหรับการลอยตัวและทวีคูณมันยากกว่าเช่นเดียวกับจำนวนเต็มธรรมดาเนื่องจากสิ่งเหล่านี้อาจอยู่ในลำดับของเครื่องแม่ข่ายหรือไม่ก็ได้ คุณสามารถรับทุ่นเล็ก ๆ น้อย ๆ บนเครื่องจักรขนาดใหญ่และในทางกลับกัน

คอมไพเลอร์อื่น ๆ มีความเหมือนกันภายใน

ในGCCตัวอย่างเช่นคุณสามารถเรียกbuiltins บางตัวได้โดยตรงตามเอกสารที่นี่ :

uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)

(ไม่จำเป็นต้องมีบางสิ่ง) Afaik bits.h ประกาศฟังก์ชันเดียวกันในแบบ gcc-centric เช่นกัน

การสลับ 16 บิตเป็นเพียงการหมุน

การเรียกอินทรินสิกส์แทนการหมุนของคุณเองจะให้ประสิทธิภาพที่ดีที่สุดและความหนาแน่นของรหัส btw ..


11
ด้วย GCC ฉันอาจใช้: #include <byteswap.h> int32_t bswap_32 (int32_t x) int64_t bswap_64 (int64_t x)
jmanning2k

5
__builtin_bswapXมีให้เฉพาะจาก GCC-4.3 เป็นต้นไป
Matt Joiner

20
นอกจากนี้ยังเป็นที่น่าสังเกตว่า intrinsics เหล่านี้ / เสมอ / แลกเปลี่ยนไบต์พวกเขาจะไม่ชอบhtonl, htonsฯลฯ คุณจะต้องรู้จากบริบทของสถานการณ์ของคุณเมื่อจริงสลับไบต์
Brian Vandenberg

8
@ Jason เพราะตัวเลข 8 บิตเหมือนกันใน endian ใหญ่และน้อย :-)
Nils Pipenbrinck

2
@BrianVandenberg ขวา; การใช้htonlและntohlไม่ต้องกังวลเกี่ยวกับบริบทจะใช้งานได้เมื่อเขียนโค้ดพกพาเนื่องจากแพลตฟอร์มที่กำหนดฟังก์ชั่นเหล่านี้จะสลับกันถ้ามันเป็นตัวเล็ก / กลาง - กลางและบนใหญ่ ๆ แต่เมื่อการถอดรหัสไฟล์ประเภทมาตรฐานที่ถูกกำหนดให้เป็นน้อย endian (พูด BMP) หนึ่งยังคงมีการรู้บริบทและไม่สามารถเพียงแค่พึ่งพาและhtonl ntohl
ตำนาน 2k

86

ใส่เพียง:

#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).


3
มี upvote ฉันใช้ uchars และมอบหมาย 4 ถึง 1, 3 ถึง 2, 2 ถึง 3 และ 1 ถึง 4 แต่นี่ยืดหยุ่นกว่านี้ถ้าคุณมีขนาดต่างกัน 6 นาฬิกาในวันที่ 1 Gen Pentium IIRC BSWAP คือ 1 นาฬิกา แต่เป็นแพลตฟอร์มที่เฉพาะเจาะจง

2
@RocketRoy: ใช่และถ้าความเร็วกลายเป็นปัญหามันง่ายมากที่จะเขียนโอเวอร์โหลดด้วยแพลตฟอร์มและประเภทเฉพาะสำหรับ Intrisics
Alexandre C.

3
@MihaiTodor: การใช้สหภาพสำหรับการพิมพ์ผ่านอาร์เรย์ของตัวอักษรนี้ได้รับอนุญาตอย่างชัดเจนโดยมาตรฐาน ดูเช่น คำถามนี้
Alexandre C.

4
@AlexandreC ไม่อยู่ในมาตรฐาน C ++ - เฉพาะใน C ใน C ++ (ซึ่งรหัสนี้คืออะไร) รหัสนี้เป็นพฤติกรรมที่ไม่ได้กำหนด
Rapptz

4
@Rapptz: 3.10 ดูเหมือนชัดเจน: "หากโปรแกรมพยายามเข้าถึงค่าที่เก็บไว้ของวัตถุผ่าน glvalue นอกเหนือจากประเภทใดประเภทหนึ่งต่อไปนี้พฤติกรรมจะไม่ได้กำหนด: [... ] ประเภทถ่านหรือถ่านที่ไม่ได้ลงนาม " บางทีฉันอาจจะพลาดบางสิ่งบางอย่างที่นี่ แต่ฉันก็ค่อนข้างชัดเจนว่าการเข้าถึงประเภทใด ๆ ผ่านตัวชี้ถ่านได้รับอนุญาตอย่างชัดเจน
Alexandre C.

75

จากการเข้าใจคำสั่งของ Byteโดย Rob Pike:

สมมติว่าสตรีมข้อมูลของคุณมีเลขจำนวนเต็ม 32 บิตแบบ endian เข้ารหัส ต่อไปนี้เป็นวิธีแยกไฟล์ (สมมติว่ามีไบต์ที่ไม่ได้ลงชื่อ):

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

หากเป็นใหญ่ผู้นี่คือวิธีการแยก:

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

TL; DR:ไม่ต้องกังวลเกี่ยวกับลำดับดั้งเดิมของแพลตฟอร์มของคุณทั้งหมดที่นับเป็นลำดับไบต์ของสตรีมที่คุณอ่านจากและคุณหวังว่ามันจะถูกกำหนดไว้อย่างดี

หมายเหตุ: มันถูกตั้งข้อสังเกตในความคิดเห็นที่ขาดแปลงชนิดอย่างชัดเจนมันเป็นสิ่งสำคัญที่dataจะอาร์เรย์ของหรือunsigned char uint8_tการใช้signed charหรือchar(ถ้าลงชื่อ) จะส่งผลให้data[x]ได้รับการเลื่อนตำแหน่งเป็นจำนวนเต็มและdata[x] << 24อาจเลื่อน 1 เป็นบิตเครื่องหมายซึ่งเป็น UB


6
นี่มันเจ๋ง แต่ฉันคิดว่ามันใช้ได้กับจำนวนเต็มและตัวแปรเท่านั้น จะทำอย่างไรกับลอย / คู่?
Brett

1
@ v.oddou: ใช่และไม่ใช่ไฟล์ที่แมปหน่วยความจำจะเหมือนกับเฟรมเครือข่ายทุกประการ หากคุณยอมรับที่จะไม่อ่านพวกเขาโดยตรงสิ่งที่สำคัญคือendianness ของพวกเขา : ถ้าเด็กน้อยใช้สูตรแรกถ้ามันเป็นเด็กใหญ่ใช้ที่สอง คอมไพเลอร์ใด ๆ ที่มีมูลค่าเกลือจะปรับการแปลงที่ไม่จำเป็นออกหากการจับคู่ endianness
Matthieu M.

2
@ meowsqueak: ใช่ฉันคาดหวังว่ามันจะทำงานได้เพราะลำดับของไบต์มีการเปลี่ยนแปลงเท่านั้นไม่ใช่ลำดับของบิตภายในแต่ละไบต์
Matthieu M.

3
ในบันทึกที่เกี่ยวข้องอย่างหลวม ๆ โพสต์ที่เชื่อมโยงคือการอ่านที่ไม่พึงประสงค์ ... คนที่แต่งตัวประหลาดดูเหมือนจะให้ความสำคัญกับช่วงเวลาสั้น ๆ แต่เขาก็ชอบที่จะเขียนคำพูดยาว ๆ เกี่ยวกับโปรแกรมเมอร์ที่ไม่ดีเหล่านั้นทั้งหมด อธิบายสถานการณ์และสาเหตุที่วิธีแก้ปัญหาของเขาใช้ได้ผลเสมอ
โฆษณา N

1
หากคุณกำลังใช้วิธีนี้ตรวจสอบให้แน่ใจว่าคุณได้นำข้อมูลของคุณไปที่ (ถ่านที่ไม่ได้ลงชื่อ *)
joseph

51

หากคุณกำลังทำสิ่งนี้เพื่อวัตถุประสงค์ในการรองรับเครือข่าย / โฮสต์คุณควรใช้:

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 ที่นำเสนอที่นี่จะใช้ได้ดี


2
การสั่งซื้อเครือข่ายไบต์เป็นเรื่องใหญ่ที่ฉันเชื่อ ฟังก์ชั่นเหล่านี้สามารถใช้กับสิ่งนั้นได้แม้ว่าคุณจะไม่ได้ใช้รหัสเครือข่าย อย่างไรก็ตามไม่มีรุ่นโฟลต ntohf หรือ htonf
Matt

2
Matt H. ที่ถูกต้องส่วนใหญ่เท่านั้น ระบบคอมพิวเตอร์บางระบบอาจมีลำดับไบต์น้อย หากคุณกำลังทำงานให้พูด motorolla 68k, PowerPC หรือสถาปัตยกรรมขนาดใหญ่อื่น ๆ ฟังก์ชั่นเหล่านี้จะไม่สลับไบต์เลยเพราะมันอยู่ใน 'ลำดับไบต์เครือข่าย
Frosty

2
น่าเสียดายhtonlและntohlไม่สามารถไปถึง endian น้อย ๆ บนแพลตฟอร์มขนาดใหญ่
Brian Vandenberg

2
@celtschk เข้าใจแล้ว; อย่างไรก็ตาม OP ต้องการวิธีสลับ endianness แม้ในสภาพแวดล้อมขนาดใหญ่ endian
Brian Vandenberg

4
เพื่อหลีกเลี่ยงคำถามที่หลีกเลี่ยงไม่ได้: มีเหตุผลหลายประการที่ต้องใช้ LE สำหรับแพลตฟอร์ม BE รูปแบบไฟล์จำนวนมาก (bmp, fli, pcx, qtm, rtf, tga เพื่อตั้งชื่อไม่กี่) ใช้ค่า endian น้อย ... หรืออย่างน้อยรูปแบบบางรูปแบบก็ทำได้ในคราวเดียว
Brian Vandenberg

26

ฉันได้รับคำแนะนำเล็กน้อยจากโพสต์นี้และรวบรวมเข้าด้วยกันเพื่อสร้างสิ่งนี้:

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

คุณต้องรวม <cstdint> หรือ <stdint.h> เช่นสำหรับ uint32_t
ady

17

ขั้นตอนสำหรับการเปลี่ยนจากผู้มีรายได้สูงไปเป็นผู้มีรายได้น้อยนั้นเป็นแบบเดียวกันกับการย้ายจาก บริษัท ผู้มีรายได้น้อยไปเป็นผู้รับจ้างรายใหญ่

นี่คือตัวอย่างรหัส:

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

2
ฟังก์ชั่นสุดท้ายที่โพสต์ที่นี่ไม่ถูกต้องและควรแก้ไขเป็น: void swapByteOrder (ไม่ได้ลงนาม & long ull ยาวที่ไม่ได้ลงชื่อ) {ull = (ull >> 56) | ... (ull << 56); }
Eric Burnett

14
ฉันคิดว่าไม่ถูกต้องที่จะใช้ตรรกะ - และ (&&) เมื่อเทียบกับระดับบิต - และ (&) ตามสเป็ค C ++ ทั้งตัวถูกดำเนินการจะถูกแปลงเป็นบูลโดยปริยายซึ่งไม่ใช่สิ่งที่คุณต้องการ
Trevor Robinson

15

มีคำสั่งแอสเซมบลีที่เรียกว่า BSWAP ที่จะทำการสลับให้คุณเร็วมากๆ คุณสามารถอ่านเกี่ยวกับเรื่องนี้ที่นี่

Visual Studio หรือมากกว่าแม่นยำ C ++ Visual ห้องสมุดรันไทม์มี intrinsics _byteswap_ushort(), _byteswap_ulong(), and _byteswap_int64()แพลตฟอร์มสำหรับการนี้เรียกว่า ควรมีลักษณะคล้ายกันสำหรับแพลตฟอร์มอื่น ๆ แต่ฉันไม่ทราบว่าจะเรียกใช้อะไร


นั่นเป็นลิงค์ที่ยอดเยี่ยม ฉันสนใจในแอสเซมเบลอร์ x86 อีกครั้ง
PP

1
ผลลัพธ์การกำหนดเวลาสำหรับ BSWAP แสดงไว้ที่นี่ gmplib.org/~tege/x86-timing.pdf ... และที่นี่ ... agner.org/optimize/instruction_tables.pdf

12

เราได้ทำสิ่งนี้กับเทมเพลต คุณสามารถทำสิ่งนี้:

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

8

หากคุณทำเช่นนี้เพื่อถ่ายโอนข้อมูลระหว่างแพลตฟอร์มที่แตกต่างกันดูที่ฟังก์ชั่น ntoh และ hton


7

แบบเดียวกับที่คุณทำใน C:

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

คุณสามารถประกาศเวกเตอร์ของตัวอักษรที่ไม่ได้ลงนาม memcpy ค่าอินพุตลงในนั้นย้อนกลับไบต์เป็นเวกเตอร์อื่นและ memcpy ไบต์ที่ออก แต่ที่จะสั่งขนาดที่ยาวกว่าบิต twiddling โดยเฉพาะอย่างยิ่งกับ 64 บิตบิต


7

ในระบบ POSIX ส่วนใหญ่ (ผ่านมันไม่ได้อยู่ในมาตรฐาน POSIX) มี endian.h ซึ่งสามารถใช้เพื่อกำหนดว่าการเข้ารหัสระบบของคุณใช้อะไร จากตรงนี้มันจะเป็นแบบนี้:

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

วิธีนี้จะเปลี่ยนคำสั่งซื้อ (จาก endian ใหญ่เป็น endian น้อย):

หากคุณมีหมายเลข 0xDEADBEEF (บนระบบ endian เล็ก ๆ ที่เก็บเป็น 0xEFBEADDE) ptr [0] จะเป็น 0xEF, ptr [1] คือ 0xBE เป็นต้น

แต่ถ้าคุณต้องการใช้สำหรับการเชื่อมต่อเครือข่าย htons, htonl และ htonll (และผู้บุกรุก ntohs, ntohl และ ntohll) จะเป็นประโยชน์สำหรับการแปลงจากลำดับโฮสต์เป็นคำสั่งเครือข่าย


7
นั่นเป็นเรื่องตลก - มาตรฐาน POSIX ที่opengroup.org/onlinepubs/9699919799/toc.htmไม่ได้พูดถึงส่วนหัว '<endian.h> `
Jonathan Leffler

1
คุณสามารถใช้htonlและเพื่อน ๆ โดยไม่คำนึงว่ากรณีใช้งานมีส่วนเกี่ยวข้องกับเครือข่ายหรือไม่ คำสั่งไบต์เครือข่ายนั้นใหญ่มากดังนั้นให้ปฏิบัติต่อฟังก์ชั่นเหล่านั้นเป็น host_to_be และ be_to_host (ไม่ช่วยหากคุณต้องการ host_to_le)
Peter Cordes

5

โปรดทราบว่าอย่างน้อยสำหรับ Windows นั้น htonl () จะช้ากว่าของที่อยู่ภายใน _byteswap_ulong () ที่แท้จริง อดีตคือการเรียกไลบรารี DLL เข้า ws2_32.dll ส่วนหลังคือคำสั่งแอสเซมบลีหนึ่ง BSWAP ดังนั้นหากคุณกำลังเขียนโค้ดที่ขึ้นอยู่กับแพลตฟอร์มให้เลือกใช้ Intrinsics เพื่อความเร็ว:

#define htonl(x) _byteswap_ulong(x)

นี่อาจเป็นสิ่งสำคัญอย่างยิ่งสำหรับการประมวลผลภาพ. PNG ที่บันทึกจำนวนเต็มทั้งหมดใน Big Endian พร้อมคำอธิบาย "สามารถใช้ htonl () ... " {เพื่อทำให้โปรแกรม Windows ทั่วไปช้าลงหากคุณไม่ได้เตรียม}


4

แพลตฟอร์มส่วนใหญ่มีไฟล์ส่วนหัวของระบบที่มีฟังก์ชั่นไบต์ที่มีประสิทธิภาพ บน 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

เปลี่ยน: #define BYTESWAPS (bits) \ template <class T> อินไลน์ T htobe (T t, SizeT <bits / 8>) {return htobe ## บิต (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 เป็น ## bits ## toh (t); } \ template <class T> inline T letoh (T t, SizeT <bits / 8>) {return le ## bits ## toh (t); }
ldav1s

ขอบคุณที่ลืมทดสอบ betoh () และ letoh ()
Maxim Egorushkin

4

ฉันชอบอันนี้สำหรับสไตล์ :-)

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

ฉันได้รับข้อผิดพลาดในการchar[]พูดว่า 'ข้อผิดพลาด: ประเภทที่ไม่สมบูรณ์ไม่ได้รับอนุญาต'
Portland Runner

4

อย่างจริงจัง ... ฉันไม่เข้าใจว่าทำไมการแก้ปัญหาทั้งหมดจึงซับซ้อน ! ฟังก์ชั่นเทมเพลตทั่วไปที่ง่ายที่สุดและธรรมดาที่สุดที่สามารถสลับขนาดทุกประเภทภายใต้สถานการณ์ใด ๆ ในระบบปฏิบัติการใด ๆ ????

template <typename T>
void SwapEnd(T& var)
{
    static_assert(std::is_pod<T>::value, "Type must be POD type for safety");
    std::array<char, sizeof(T)> varArray;
    std::memcpy(varArray.data(), &var, sizeof(T));
    for(int i = 0; i < static_cast<int>(sizeof(var)/2); i++)
        std::swap(varArray[sizeof(var) - 1 - i],varArray[i]);
    std::memcpy(&var, varArray.data(), sizeof(T));
}

มันคือพลังเวทย์มนตร์ของ C และ C ++ ด้วยกัน! เพียงสลับอักขระตัวแปรดั้งเดิมเป็นตัวละคร

จุดที่ 1 : ไม่มีตัวดำเนินการ: จำไว้ว่าฉันไม่ได้ใช้ตัวดำเนินการกำหนดค่าอย่างง่าย "=" เนื่องจากวัตถุบางอย่างจะเกิดความสับสนเมื่อความล้มเหลวถูกพลิกและตัวสร้างสำเนา (หรือตัวดำเนินการกำหนดค่า) ไม่ทำงาน ดังนั้นจึงมีความน่าเชื่อถือมากกว่าที่จะคัดลอกถ่านด้วยถ่าน

จุดที่ 2 : ระวังปัญหาการจัดตำแหน่ง: โปรดสังเกตว่าเรากำลังคัดลอกไปยังและจากอาเรย์ซึ่งเป็นสิ่งที่ถูกต้องเนื่องจากคอมไพเลอร์ C ++ ไม่รับประกันว่าเราสามารถเข้าถึงหน่วยความจำที่ไม่ได้จัดแนว (คำตอบนี้ได้รับการปรับปรุงจากต้นฉบับ แบบฟอร์มนี้) ตัวอย่างเช่นถ้าคุณจัดสรรuint64_tคอมไพเลอร์ของคุณไม่สามารถรับประกันได้ว่าคุณจะสามารถเข้าถึงไบต์ที่ 3 uint8_tที่เป็น ดังนั้นสิ่งที่ถูกต้องคือการคัดลอกสิ่งนี้ไปยังอาเรย์ถ่านเปลี่ยนมันจากนั้นก็คัดลอกมันกลับมา (ไม่เลยreinterpret_cast) โปรดสังเกตว่าคอมไพเลอร์ส่วนใหญ่ฉลาดพอที่จะแปลงสิ่งที่คุณทำกลับเป็น a reinterpret_castหากพวกเขาสามารถเข้าถึงแต่ละไบต์โดยไม่คำนึงถึงการจัดตำแหน่ง

วิธีใช้ฟังก์ชั่นนี้ :

double x = 5;
SwapEnd(x);

และตอนนี้xมีความแตกต่างใน endianness


2
สิ่งนี้จะทำงานได้ทุกที่ แต่แอสเซมบลี ocde ที่ผลิตมักจะไม่ดี: ดูคำถามของฉันstackoverflow.com/questions/36657895//
j_kubik

คุณใช้new/ deleteเพื่อจัดสรรบัฟเฟอร์สำหรับสิ่งนี้?!? เป็นค่าคงที่รวบรวมเวลาเพื่อให้คุณสามารถทำsizeof(var) char varSwapped[sizeof(var)]หรือคุณสามารถทำchar *p = reinterpret_cast<char*>(&var)และแลกเปลี่ยนในสถานที่
Peter Cordes

@ ปีเตอร์คำตอบนี้รวดเร็วและสกปรกทำเพื่อพิสูจน์จุด ฉันจะใช้คำแนะนำของคุณ อย่างไรก็ตามคุณไม่จำเป็นต้องเป็น SO AH ขนาดใหญ่และลงคะแนนโซลูชัน 5 บรรทัดเมื่อเปรียบเทียบกับโซลูชัน 50 บรรทัดที่ให้ไว้ที่นั่น ฉันจะไม่พูดอะไรอีก
นักฟิสิกส์ควอนตัม

คำตอบนี้ทำให้ประเด็นที่มีประโยชน์เกี่ยวกับการระมัดระวังกับ constructors และตัวดำเนินการ overloaded ในข้อมูลผิด endian ดังนั้นฉันยินดีที่จะลบ downvote ของฉันเมื่อรหัสไม่น่ากลัวและเป็นสิ่งที่คอมไพเลอร์ที่ดีสามารถรวบรวมเป็น bswap คำแนะนำ. นอกจากนี้ผมขอแนะนำให้ใช้แทนfor(size_t i = 0 ; i < sizeof(var) ; i++) static_cast<long>(หรือจริงๆแล้วการแลกเปลี่ยนแบบแทนที่จะใช้การขึ้นและลงchar*เพื่อที่จะหายไป)
Peter Cordes

เช่นดูคำตอบของ Mark Ransomโดยใช้ std :: swap เพื่อกลับเข้าที่เดิม
Peter Cordes

3

ฉันมีรหัสนี้ที่อนุญาตให้ฉันแปลงจาก 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);
}

3

หากจำนวนเต็มที่ไม่ได้ลงนามแบบ 32 บิตขนาดใหญ่ดูเหมือน 0xAABBCCDD ซึ่งเท่ากับ 2864434397 ดังนั้นจำนวนเต็มแบบ 32 บิตที่ไม่ได้ลงชื่อนั้นจะเป็น 0xDDCCBBAA ในตัวประมวลผลแบบ end-little ซึ่งเท่ากับ 2864434397

หากสั้นที่ไม่มีเครื่องหมายขนาดใหญ่ 16 บิตดูเหมือน 0xAABB ซึ่งเท่ากับ 43707 ดังนั้นรูปแบบที่ไม่มีการลงนามแบบ 16 บิตเดียวกันนั้นจะดูเหมือน 0xBBAA บนตัวประมวลผลขนาดเล็กที่สิ้นสุดซึ่งเท่ากับ 43707

นี่คือฟังก์ชั่น #define ที่มีประโยชน์สองสามอย่างที่จะสลับไบต์จาก little-endian เป็น big-endian และในทางกลับกัน ->

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

2

ต่อไปนี้เป็นรุ่นทั่วไปที่ฉันสร้างขึ้นโดยถอดส่วนหัวของฉันออกเพื่อแลกค่า ข้อเสนอแนะอื่น ๆ จะดีกว่าถ้าประสิทธิภาพเป็นปัญหา

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

คำเตือน:ฉันยังไม่ได้พยายามรวบรวมหรือทดสอบ


2

หากคุณใช้รูปแบบทั่วไปในการกลับลำดับของบิตในคำหนึ่งและคัดแยกชิ้นส่วนที่สลับบิตภายในแต่ละไบต์คุณจะเหลือบางสิ่งที่สลับไบต์ภายในคำเพียงคำเดียว สำหรับ 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);

2

แค่คิดว่าฉันเพิ่มโซลูชันของตัวเองที่นี่เพราะฉันไม่เห็นมันทุกที่ มันเป็นฟังก์ชั่นเทมเพลต 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;
}

2

ฉันแปลกใจจริงๆที่ไม่มีใครพูดถึงฟังก์ชั่น htobeXX และ betohXX พวกเขาถูกกำหนดใน endian.h และคล้ายกับฟังก์ชั่นเครือข่าย htonXX


2

ด้วยการใช้รหัสด้านล่างคุณสามารถสลับระหว่าง 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))

1

ฉันเพิ่งเขียนแมโครเพื่อทำสิ่งนี้ใน 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 ++


พบที่นี่ในบางรหัส สับสน heck ออกจากฉัน ขอบคุณสำหรับคำอธิบาย อย่างไรก็ตามทำไมต้องใช้__VA_ARGS__?
asr9

0

ว้าวฉันไม่อยากเชื่อเลยคำตอบที่ฉันได้อ่านที่นี่ จริงๆแล้วมีคำแนะนำในการชุมนุมซึ่งทำได้เร็วกว่าสิ่งอื่นใด bswap คุณสามารถเขียนฟังก์ชันแบบนี้ ...

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

มันเป็นมากเร็วกว่า intrinsics ที่ได้รับการแนะนำ ฉันถอดชิ้นส่วนแล้วมอง ฟังก์ชั่นด้านบนไม่มีคำนำ / บทส่งท้ายดังนั้นแทบไม่มีค่าใช้จ่ายเลย

unsigned long _byteswap_ulong(unsigned long value);

การทำ 16 บิตนั้นง่ายเหมือนกันยกเว้นว่าคุณจะใช้ xchg al, ah bswap ใช้งานได้กับการลงทะเบียนแบบ 32 บิตเท่านั้น

64- บิตเป็นเรื่องยุ่งยากเล็กน้อย แต่ไม่มากเกินไป ดีกว่าตัวอย่างข้างต้นทั้งหมดด้วยลูปและแม่แบบเป็นต้น

มีข้อแม้บางอย่างอยู่ที่นี่ ... ประการแรก bswap นั้นใช้ได้กับซีพียู 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 บิลด์ ผมว่าคุณจะต้องใช้คอมไพเลอร์อินทริน


1
_byteswap_ulongและ_uint64(เช่นในคำตอบที่ยอมรับ) ทั้งคู่รวบรวมเพื่อใช้bswapคำสั่ง ฉันจะแปลกใจ แต่ก็สนใจที่จะรู้ว่า ASM นี้เร็วกว่านี้เพียงเพราะไม่ใช้คำนำ / บทส่งท้าย - คุณเปรียบเทียบหรือไม่?
ZachB

@stdcall คำถามไม่ได้ขอโซลูชันแบบพกพาหรือพูดถึงอะไรเกี่ยวกับแพลตฟอร์ม ดังที่คำตอบของฉันบอกไว้ข้างต้นเป็นวิธีที่เร็วที่สุดในการแลกเปลี่ยน endian แน่นอนถ้าคุณกำลังเขียนสิ่งนี้บนแพลตฟอร์มที่ไม่ใช่ X86 สิ่งนี้จะไม่สามารถใช้งานได้ แต่อย่างที่ฉันได้กล่าวไปแล้วคุณก็ถูก จำกัด อยู่ที่คอมไพเลอร์อินทรินซิลิกถ้าคอมไพเลอร์ของคุณสนับสนุนพวกเขา
ช่างเชื่อม

@ZachB ในกรณีนี้โดยเฉพาะฉันคิดว่าการละหมาดอารัมภบทและบทส่งท้ายจะช่วยให้คุณประหยัดได้ดีเพราะคุณเพียงแค่ดำเนินการ 1 คำสั่งเท่านั้น อารัมภบทจะต้องกดลงบนสแต็กทำการลบตั้งค่าตัวชี้ฐานแล้วคล้ายที่ส่วนท้าย ฉันไม่ได้ทำการเปรียบเทียบ แต่สิ่งที่กล่าวมาข้างต้นมีห่วงโซ่การพึ่งพา 0 ซึ่งคุณจะไม่ได้รับหากไม่มีการเปลือยกาย บางทีคอมไพเลอร์ที่ดีก็อาจจะใส่เข้าไป แต่คุณก็อยู่ในสวนบอลที่แตกต่างออกไป
ช่างเชื่อม

2
บางที แต่โปรดทราบว่าในกรณีทั่วไปของการสลับอาเรย์ของตัวเลขคอมไพเลอร์อินทรินที่กล่าวถึงในคำตอบอื่น ๆ จะใช้ส่วนขยาย SSE / AVX และปล่อย PSHUFB ซึ่งมีประสิทธิภาพเหนือกว่า BSWAP ดูwm.ite.pl/articles/reverse-array-of-bytes.html
ZachB

IMHO เป็นรูปแบบที่ไม่ถูกต้องในการโพสต์โซลูชันเฉพาะแพลตฟอร์มเมื่อ OP ไม่ได้ระบุว่าพวกเขาต้องการโซลูชันสำหรับ x86 เท่านั้น และดูถูกการแก้ปัญหาอื่น ๆ เมื่อคุณจะใช้ไม่ได้ในจำนวนมากที่ใช้เช่น iOS และ Android OS มากอย่างกว้างขวาง (ซึ่งใช้ ARM หรือ MIPS ซีพียู.)
Jens Alfke

0

เทคนิคแบบพกพาสำหรับการใช้อุปกรณ์เสริมเอนกประสงค์ที่ไม่ได้อยู่ในตำแหน่งที่ไม่เหมาะกับเครื่องมือเพิ่มประสิทธิภาพ พวกมันทำงานได้กับคอมไพเลอร์ทุกตัวการจัดตำแหน่งขอบเขตและการเรียงลำดับไบต์ รูทีนที่ไม่ได้จัดแนวเหล่านี้จะเสริมหรือ mooted ขึ้นอยู่กับ 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];
}

typedefs เหล่านี้มีประโยชน์ในการเพิ่มข้อผิดพลาดของคอมไพเลอร์หากไม่ได้ใช้กับ accessors จึงช่วยลดข้อบกพร่อง accessor ที่ถูกลืม

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 */

2
สำหรับคำถามนี้แท็ก C ++ สร้างความแตกต่าง มีพฤติกรรมที่ไม่ได้กำหนดจำนวนมากเนื่องจาก C ++ และสหภาพ
jww

0

ต่อไปนี้เป็นวิธีอ่านคู่ที่จัดเก็บในรูปแบบ 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;
    }
}

สำหรับชุดที่เหลือของฟังก์ชั่นรวมถึงรูทีนการเขียนและจำนวนเต็มดูโปรเจ็กต์ github ของฉัน

https://github.com/MalcolmMcLean/ieee754


0

การแลกเปลี่ยนไบต์กับเคล็ดลับ 3-x-xor รอบเดือยในฟังก์ชั่นเทมเพลตให้โซลูชัน O (ln2) ที่ยืดหยุ่นและรวดเร็วซึ่งไม่ต้องการไลบรารีสไตล์ที่นี่ยังปฏิเสธ 1 ไบต์ประเภท:

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

0

ดูเหมือนว่าวิธีที่ปลอดภัยจะใช้ 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); });

ข้างต้นจะเป็นแบบไม่มี op ถ้าคุณอยู่ในระบบใหญ่ดังนั้นฉันจะมองหาสิ่งที่แพลตฟอร์มของคุณใช้เป็นเงื่อนไขการรวบรวมเวลาในการตัดสินใจว่า 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

0

หากคุณมี C ++ 17 ให้เพิ่มส่วนหัวนี้

#include <algorithm>

ใช้ฟังก์ชันเทมเพลตนี้เพื่อสลับไบต์:

template <typename T>
void swapEndian(T& buffer)
{
    static_assert(std::is_pod<T>::value, "swapEndian support POD type only");
    char* startIndex = static_cast<char*>((void*)buffer.data());
    char* endIndex = startIndex + sizeof(buffer);
    std::reverse(startIndex, endIndex);
}

เรียกว่าชอบ:

swapEndian (stlContainer);

-4

เงยหน้าขึ้นมองการขยับเล็กน้อยเพราะนี่เป็นสิ่งที่คุณต้องทำเพื่อเปลี่ยนจาก -> endian ใหญ่ จากนั้นขึ้นอยู่กับขนาดของบิตคุณเปลี่ยนวิธีการเปลี่ยนบิต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.