chuyển đổi endian lớn thành endian nhỏ trong C [mà không sử dụng func được cung cấp] [đã đóng]


91

Tôi cần viết một hàm để chuyển đổi endian lớn thành endian nhỏ trong C. Tôi không thể sử dụng bất kỳ hàm thư viện nào.


5
giá trị 16 bit? Giá trị 32 bit? Phao nổi? một mảng?
John Knoeller

19
thời gian để chọn một câu trả lời có lẽ?
Aniket Inge

7
Bỏ phiếu để mở lại. Tương tự như stackoverflow.com/questions/105252/… cho C ++. Chúng tôi chỉ có thể chỉnh sửa để làm cho điều đó rõ ràng hơn.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Câu trả lời:


168

Giả sử những gì bạn cần là một hoán đổi byte đơn giản, hãy thử một cái gì đó như

Chuyển đổi 16 bit không dấu:

swapped = (num>>8) | (num<<8);

Chuyển đổi 32-bit không dấu:

swapped = ((num>>24)&0xff) | // move byte 3 to byte 0
                    ((num<<8)&0xff0000) | // move byte 1 to byte 2
                    ((num>>8)&0xff00) | // move byte 2 to byte 1
                    ((num<<24)&0xff000000); // byte 0 to byte 3

Thao tác này hoán đổi thứ tự byte từ vị trí 1234 đến 4321. Nếu đầu vào của bạn là 0xdeadbeef, hoán đổi endian 32 bit có thể có đầu ra là 0xefbeadde.

Đoạn mã trên nên được làm sạch bằng macro hoặc ít nhất là hằng số thay vì số ma thuật, nhưng hy vọng nó sẽ hữu ích

CHỈNH SỬA: như một câu trả lời khác đã chỉ ra, có các lựa chọn thay thế cụ thể cho nền tảng, hệ điều hành và tập lệnh có thể nhanh hơn RẤT NHIỀU so với phần trên. Trong nhân Linux có các macro (ví dụ: cpu_to_be32) xử lý endianness khá tốt. Nhưng những lựa chọn thay thế này dành riêng cho môi trường của chúng. Trong thực tế, sự kết thúc tốt nhất được giải quyết bằng cách sử dụng kết hợp các phương pháp có sẵn


5
+1 để đề cập đến các phương pháp dành riêng cho nền tảng / phần cứng. Các chương trình luôn chạy trên một số phần cứng và các tính năng phần cứng luôn nhanh nhất.
eonil,

21
nếu quá trình chuyển đổi 16 bit được thực hiện ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 tạo ra một rollệnh duy nhất . Và nếu chuyển đổi 32 bit được viết dưới dạng ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), trình biên dịch tương tự sẽ tạo ra một bswaplệnh duy nhất .
user666412

Tôi không biết điều này hiệu quả như thế nào nhưng tôi đã hoán đổi thứ tự byte struct byte_t reverse(struct byte_t b) { struct byte_t rev; rev.ba = b.bh; rev.bb = b.bg; rev.bc = b.bf; rev.bd = b.be; rev.be = b.bd; rev.bf = b.bc; rev.bg = b.bb; rev.bh = b.ba; return rev;}với các trường bit như thế này: đây là một trường bit với 8 trường mỗi trường 1 bit. Nhưng tôi không chắc liệu điều đó có nhanh như những gợi ý khác hay không. Đối với int sử dụng union { int i; byte_t[sizeof(int)]; }để đảo ngược từng byte trong số nguyên.
Ilian Zapryanov

Tôi nghĩ rằng biểu thức phải là: (num >> 8) | (num << 8) để đảo ngược thứ tự byte và KHÔNG: ((num & 0xff) >> 8) | (num << 8), Ví dụ sai nhận số 0 trong byte thấp.
jscom

@IlianZapryanov Có thể +1 cho rõ ràng nhưng sử dụng các trường bit trong C như vậy có lẽ là cách kém hiệu quả nhất để làm điều đó.
sherrellbc

104

Bằng cách bao gồm:

#include <byteswap.h>

bạn có thể nhận được phiên bản tối ưu hóa của các chức năng hoán đổi byte phụ thuộc vào máy. Sau đó, bạn có thể dễ dàng sử dụng các chức năng sau:

__bswap_32 (uint32_t input)

hoặc là

__bswap_16 (uint16_t input)

3
Cảm ơn câu trả lời của bạn, nhưng tôi không thể sử dụng bất kỳ chức năng thư viện nào
Mark Ransom

4
Nên đọc #include <byteswap.h>, xem bình luận trong chính tệp .h. Bài đăng này chứa thông tin hữu ích nên tôi đã bỏ phiếu mặc dù tác giả bỏ qua yêu cầu OP để không sử dụng hàm lib.
Eli Rosencruft

30
Trên thực tế, các hàm __bswap_32 / __ bswap_16 trên thực tế là macro chứ không phải hàm thư viện, một lý do khác để bỏ phiếu.
Eli Rosencruft

7
Tôi hiểu rằng tiêu đề này không được đảm bảo tồn tại cho tất cả các hệ điều hành trên tất cả các kiến ​​trúc. Tôi vẫn chưa tìm ra một cách di động để đối phó với các vấn đề liên quan đến nội tạng.
Edward Falk

2
không tồn tại trên cửa sổ - ít nhất là không phải khi biên dịch chéo từ linux với mingw 32 hoặc 64 bit
bph

61
#include <stdint.h>


//! Byte swap unsigned short
uint16_t swap_uint16( uint16_t val ) 
{
    return (val << 8) | (val >> 8 );
}

//! Byte swap short
int16_t swap_int16( int16_t val ) 
{
    return (val << 8) | ((val >> 8) & 0xFF);
}

//! Byte swap unsigned int
uint32_t swap_uint32( uint32_t val )
{
    val = ((val << 8) & 0xFF00FF00 ) | ((val >> 8) & 0xFF00FF ); 
    return (val << 16) | (val >> 16);
}

//! Byte swap int
int32_t swap_int32( int32_t val )
{
    val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF ); 
    return (val << 16) | ((val >> 16) & 0xFFFF);
}

Cập nhật : Đã thêm tính năng hoán đổi byte 64bit

int64_t swap_int64( int64_t val )
{
    val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
    val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
    return (val << 32) | ((val >> 32) & 0xFFFFFFFFULL);
}

uint64_t swap_uint64( uint64_t val )
{
    val = ((val << 8) & 0xFF00FF00FF00FF00ULL ) | ((val >> 8) & 0x00FF00FF00FF00FFULL );
    val = ((val << 16) & 0xFFFF0000FFFF0000ULL ) | ((val >> 16) & 0x0000FFFF0000FFFFULL );
    return (val << 32) | (val >> 32);
}

Đối với int32_tint64_tcác biến thể, lý do đằng sau việc che dấu ... & 0xFFFFvà là ... & 0xFFFFFFFFULLgì? Có điều gì đó đang xảy ra với tiện ích mở rộng đăng ký mà tôi không thấy ở đây? Ngoài ra, tại sao swap_int64lại quay trở lại uint64_t? Có nên không int64_t?
bgoodr

1
Swap_int64 trả về uint64 thực sự là một lỗi. Việc che dấu với các giá trị int có dấu thực sự là để loại bỏ dấu hiệu. Dịch chuyển sang phải sẽ tiêm bit dấu vào bên trái. Chúng ta có thể tránh điều này bằng cách gọi thao tác hoán đổi int không dấu.
chmike

Cảm ơn. Bạn có thể muốn thay đổi loại giá trị trả về swap_int64trong câu trả lời của mình. +1 cho câu trả lời hữu ích, BTW!
bgoodr

Bitwise và giá trị cuối có phụ thuộc không?
MarcusJ

1
Những cái LLkhông cần thiết (u)swap_uint64()giống như một cái Lkhông cần thiết trong (u)swap_uint32(). Các Ukhông cần thiết trong uswap_uint64()giống như Ulà không cần thiết tronguswap_uint32()
Chux - Khôi phục Monica

13

Đây là một phiên bản khá chung chung; Tôi chưa biên soạn nó, vì vậy có thể có lỗi chính tả, nhưng bạn nên hiểu,

void SwapBytes(void *pv, size_t n)
{
    assert(n > 0);

    char *p = pv;
    size_t lo, hi;
    for(lo=0, hi=n-1; hi>lo; lo++, hi--)
    {
        char tmp=p[lo];
        p[lo] = p[hi];
        p[hi] = tmp;
    }
}
#define SWAP(x) SwapBytes(&x, sizeof(x));

NB: Điều này không được tối ưu hóa cho tốc độ hoặc không gian. Nó nhằm mục đích rõ ràng (dễ gỡ lỗi) và di động.

Cập nhật 2018-04-04 Đã thêm khẳng định () để bẫy trường hợp không hợp lệ của n == 0, như được phát hiện bởi người bình luận @chux.


1
bạn có thể sử dụng xorSwap để có hiệu suất tốt hơn. Thích phiên bản generic này trên tất cả những kích thước cụ thể ...

Tôi đã thử nghiệm nó, hóa ra nó nhanh hơn xorSwap ... trên x86. stackoverflow.com/questions/3128095/…

1
@nus - Một trong những ưu điểm của mã rất đơn giản là trình tối ưu hóa trình biên dịch đôi khi có thể làm cho nó rất nhanh.
Michael J

@MichaelJ OTOH, phiên bản 32 bit ở trên trong câu trả lời của chmike được bswaptrình biên dịch X86 tốt biên dịch thành một hướng dẫn duy nhất với tính năng tối ưu hóa được bật. Phiên bản này với một tham số cho kích thước không thể làm được điều đó.
Alnitak

@Alnitak - Như tôi đã nói, tôi đã không cố gắng tối ưu hóa mã của mình. Khi người dùng nhận thấy rằng mã chạy rất nhanh (trong một trường hợp), tôi chỉ đề cập đến ý tưởng chung rằng mã đơn giản thường có thể được tối ưu hóa cao bởi trình biên dịch. Mã của tôi hoạt động cho nhiều trường hợp và nó khá dễ hiểu và do đó dễ gỡ lỗi. Điều đó đáp ứng mục tiêu của tôi.
Michael J

9

Nếu bạn cần macro (ví dụ: hệ thống nhúng):

#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))

Các macro này ổn, nhưng ((x) >> 24) sẽ không thành công khi số nguyên có dấu nằm trong khoảng từ 0x80000000 đến 0xffffffff. Bạn nên sử dụng bitwise AND ở đây. Lưu ý: ((x) << 24) là hoàn toàn an toàn. (x) >> 8) cũng sẽ không thành công nếu 16 bit cao là khác không (hoặc giá trị 16 bit có dấu được cung cấp).

2
@ PacMan-- Các macro này chỉ được sử dụng để hoán đổi các số nguyên không có dấu . Đó là lý do tại sao có UINTtên của họ.
kol

Vâng, đúng, xin lỗi vì tiếng ồn. Không phải là tốt nhất để nhúng một typecast?

5

Chỉnh sửa: Đây là các chức năng thư viện. Sau đây là cách làm thủ công.

Tôi thực sự choáng váng trước số lượng người không biết về __byteswap_ushort, __byteswap_ulong và __byteswap_uint64 . Chắc chắn chúng là Visual C ++ cụ thể, nhưng chúng biên dịch thành một số mã ngon lành trên kiến ​​trúc x86 / IA-64. :)

Đây là cách sử dụng rõ ràng của bswaphướng dẫn, được lấy từ trang này . Lưu ý rằng dạng nội tại ở trên sẽ luôn nhanh hơn dạng này , tôi chỉ thêm nó để đưa ra câu trả lời mà không có quy trình thư viện.

uint32 cq_ntohl(uint32 a) {
    __asm{
        mov eax, a;
        bswap eax; 
    }
}

21
Đối với một câu hỏi C, bạn đang đề xuất một cái gì đó cụ thể cho Visual C ++?
Alok Singhal

3
@Alok: Visual C ++ là một sản phẩm của Microsoft. Nó hoạt động tốt để biên dịch mã C. :)
Sam Harwell

20
Tại sao nó làm bạn ngạc nhiên khi nhiều người không biết về việc triển khai byteswapping dành riêng cho Microsoft?
dreamlax

36
Tuyệt vời, đó là thông tin tốt cho bất kỳ ai đang phát triển một sản phẩm mã nguồn đóng không cần phải di động hoặc tuân thủ các tiêu chuẩn.
Sam Post

6
@Alok, OP không đề cập đến trình biên dịch | OS. Một người được phép đưa ra câu trả lời theo kinh nghiệm của mình với một bộ công cụ cụ thể.
Aniket Inge

5

Như một trò đùa:


#include <stdio.h>

int main (int argc, char *argv[])
{
    size_t sizeofInt = sizeof (int);
    int i;

    union
    {
        int x;
        char c[sizeof (int)];
    } original, swapped;

    original.x = 0x12345678;

    for (i = 0; i < sizeofInt; i++)
        swapped.c[sizeofInt - i - 1] = original.c[i];

    fprintf (stderr, "%x\n", swapped.x);

    return 0;
}

7
HAHAHAHAHA. Hahaha. Ha. Ha? (Trò đùa gì vậy?)

3
bạn đã lấy nó từ một số kho lưu trữ nguồn Windows? :)
hochl

Nodejs sử dụng kỹ thuật này! github.com/nodejs/node/blob/…
Justin Moser

Tò mò sử dụng int i, size_t sizeofIntvà không cùng một loại cho cả hai.
chux - Phục hồi Monica

5

đây là một cách sử dụng lệnh SSSE3 pshufb sử dụng nội tại Intel, giả sử bạn có bội số của 4 ints:

unsigned int *bswap(unsigned int *destination, unsigned int *source, int length) {
    int i;
    __m128i mask = _mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
    for (i = 0; i < length; i += 4) {
        _mm_storeu_si128((__m128i *)&destination[i],
        _mm_shuffle_epi8(_mm_loadu_si128((__m128i *)&source[i]), mask));
    }
    return destination;
}

3

Điều này sẽ hoạt động / nhanh hơn?

 uint32_t swapped, result;

((byte*)&swapped)[0] = ((byte*)&result)[3];
((byte*)&swapped)[1] = ((byte*)&result)[2];
((byte*)&swapped)[2] = ((byte*)&result)[1];
((byte*)&swapped)[3] = ((byte*)&result)[0];

2
Tôi nghĩ bạn có nghĩa là char, không phải byte.
dreamlax

Sử dụng chiến lược này, giải pháp có nhiều phiếu bầu nhất so với giải pháp của bạn là tương đương và hiệu quả nhất và dễ di chuyển. Tuy nhiên, giải pháp tôi đề xuất (nhiều phiếu thứ hai) cần ít thao tác hơn và phải hiệu quả hơn.
chmike

1

Đây là một chức năng tôi đã và đang sử dụng - đã thử nghiệm và hoạt động trên bất kỳ kiểu dữ liệu cơ bản nào:

//  SwapBytes.h
//
//  Function to perform in-place endian conversion of basic types
//
//  Usage:
//
//    double d;
//    SwapBytes(&d, sizeof(d));
//

inline void SwapBytes(void *source, int size)
{
    typedef unsigned char TwoBytes[2];
    typedef unsigned char FourBytes[4];
    typedef unsigned char EightBytes[8];

    unsigned char temp;

    if(size == 2)
    {
        TwoBytes *src = (TwoBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[1];
        (*src)[1] = temp;

        return;
    }

    if(size == 4)
    {
        FourBytes *src = (FourBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[3];
        (*src)[3] = temp;

        temp = (*src)[1];
        (*src)[1] = (*src)[2];
        (*src)[2] = temp;

        return;
    }

    if(size == 8)
    {
        EightBytes *src = (EightBytes *)source;
        temp = (*src)[0];
        (*src)[0] = (*src)[7];
        (*src)[7] = temp;

        temp = (*src)[1];
        (*src)[1] = (*src)[6];
        (*src)[6] = temp;

        temp = (*src)[2];
        (*src)[2] = (*src)[5];
        (*src)[5] = temp;

        temp = (*src)[3];
        (*src)[3] = (*src)[4];
        (*src)[4] = temp;

        return;
    }

}

2
Mã dựa trên một giả định rất hợp lý: sourceđược căn chỉnh khi cần thiết - nhưng nếu giả định đó không đúng, thì mã là UB.
chux - Phục hồi Monica

1

CHỈNH SỬA: Chức năng này chỉ hoán đổi độ cuối của các từ 16 bit được căn chỉnh. Một hàm thường cần thiết cho các mã hóa UTF-16 / UCS-2. CHỈNH SỬA KẾT THÚC.

Nếu bạn muốn thay đổi nội dung của khối bộ nhớ, bạn có thể sử dụng cách tiếp cận cực nhanh của tôi. Mảng bộ nhớ của bạn phải có kích thước là bội số của 8.

#include <stddef.h>
#include <limits.h>
#include <stdint.h>

void ChangeMemEndianness(uint64_t *mem, size_t size) 
{
uint64_t m1 = 0xFF00FF00FF00FF00ULL, m2 = m1 >> CHAR_BIT;

size = (size + (sizeof (uint64_t) - 1)) / sizeof (uint64_t);
for(; size; size--, mem++)
  *mem = ((*mem & m1) >> CHAR_BIT) | ((*mem & m2) << CHAR_BIT);
}

Loại chức năng này rất hữu ích để thay đổi nội dung của tệp Unicode UCS-2 / UTF-16.


CHAR_BIT #define bị thiếu để hoàn thành mã.
Tõnu Samuel

Ok, tôi đã thêm phần còn thiếu.
Patrick Schlüter

đây là một liên kết đến một swap trong C ++, tôi don t know if its nhanh như những gợi ý nhưng nó wokrs: github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov

CHAR_BITthay vì 8tò mò như 0xFF00FF00FF00FF00ULLlà phụ thuộc vào CHAR_BIT == 8. Lưu ý rằng LLkhông cần thiết trong hằng số.
chux - Phục hồi Monica

Bạn nói đúng chux. Chỉ được viết với CHAR_BITđể tăng cường độ phơi sáng của macro đó. Đối với LL, nó là một chú thích hơn bất cứ thứ gì khác. Đó cũng là một thói quen mà tôi đã mắc phải từ rất lâu trước đây với các trình biên dịch lỗi (chuẩn trước) sẽ không hoạt động đúng.
Patrick Schlüter

1

Đoạn mã này có thể chuyển đổi số Endian nhỏ 32 bit thành số Endian lớn.

#include <stdio.h>
main(){    
    unsigned int i = 0xfafbfcfd;
    unsigned int j;    
    j= ((i&0xff000000)>>24)| ((i&0xff0000)>>8) | ((i&0xff00)<<8) | ((i&0xff)<<24);    
    printf("unsigned int j = %x\n ", j);    
}

Thanks @YuHao Mình mới vào đây, không biết định dạng Text.
Kaushal Billore

2
Việc sử dụng ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);có thể nhanh hơn trên một số nền tảng (ví dụ: tái chế các hằng số mặt nạ AND). Tuy nhiên, hầu hết các trình biên dịch sẽ làm điều này, nhưng một số trình biên dịch đơn giản không thể tối ưu hóa nó cho bạn.

-7

Nếu bạn đang chạy trên bộ xử lý x86 hoặc x86_64, thì big endian là bản địa. vì thế

cho các giá trị 16 bit

unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);

cho các giá trị 32 bit

unsigned int   iBigE = value;
unsigned int   iLittleE = ((iBigE & 0xFF) << 24)
                        | ((iBigE & 0xFF00) << 8)
                        | ((iBigE >> 8) & 0xFF00)
                        | (iBigE >> 24);

Đây không phải là giải pháp hiệu quả nhất trừ khi trình biên dịch nhận ra rằng đây là thao tác cấp byte và tạo mã hoán đổi byte. Nhưng nó không phụ thuộc vào bất kỳ thủ thuật bố trí bộ nhớ nào và có thể được chuyển thành macro khá dễ dàng.


25
Trên kiến ​​trúc x86 và x86_64, lược đồ endian nhỏ là sơ đồ gốc.
MK aka Grisu
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.