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.
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.
Câu trả lời:
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
((num & 0xff) >> 8) | (num << 8)
, gcc 4.8.3 tạo ra một rol
lệ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 bswap
lệnh duy nhất .
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.
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)
#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.
#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);
}
int32_t
và int64_t
các biến thể, lý do đằng sau việc che dấu ... & 0xFFFF
và là ... & 0xFFFFFFFFULL
gì? 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_int64
lại quay trở lại uint64_t
? Có nên không int64_t
?
swap_int64
trong câu trả lời của mình. +1 cho câu trả lời hữu ích, BTW!
LL
không cần thiết (u)swap_uint64()
giống như một cái L
không cần thiết trong (u)swap_uint32()
. Các U
không cần thiết trong uswap_uint64()
giống như U
là không cần thiết tronguswap_uint32()
Đâ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.
bswap
trì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 đó.
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))
UINT
tên của họ.
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 bswap
hướ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;
}
}
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;
}
int i, size_t sizeofInt
và không cùng một loại cho cả hai.
đâ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 int
s:
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;
}
Đ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];
char
, không phải byte
.
Đâ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;
}
}
source
được căn chỉnh khi cần thiết - nhưng nếu giả định đó không đúng, thì mã là UB.
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.
t know if it
s nhanh như những gợi ý nhưng nó wokrs: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
thay vì 8
tò mò như 0xFF00FF00FF00FF00ULL
là phụ thuộc vào CHAR_BIT == 8
. Lưu ý rằng LL
không cần thiết trong hằng số.
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.
Đ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);
}
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.