ฉันต้องเขียนฟังก์ชันเพื่อแปลง endian ตัวใหญ่เป็น endian ตัวเล็กใน C. ฉันไม่สามารถใช้ฟังก์ชันไลบรารีใด ๆ
ฉันต้องเขียนฟังก์ชันเพื่อแปลง endian ตัวใหญ่เป็น endian ตัวเล็กใน C. ฉันไม่สามารถใช้ฟังก์ชันไลบรารีใด ๆ
คำตอบ:
สมมติว่าสิ่งที่คุณต้องการคือการแลกเปลี่ยนไบต์ง่ายๆลองสิ่งที่ชอบ
การแปลง 16 บิตที่ไม่ได้ลงนาม:
swapped = (num>>8) | (num<<8);
การแปลง 32 บิตที่ไม่ได้ลงชื่อ:
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
สิ่งนี้จะสลับคำสั่งไบต์จากตำแหน่ง 1234 เป็น 4321 หากอินพุตของคุณคือ0xdeadbeef
endian swap 32 บิตอาจมีเอาต์พุตเป็น0xefbeadde
.
โค้ดด้านบนควรล้างด้วยมาโครหรืออย่างน้อยค่าคงที่แทนที่จะเป็นตัวเลขวิเศษ แต่หวังว่ามันจะช่วยได้เหมือนเดิม
แก้ไข: ในฐานะที่เป็นคำตอบอื่นที่ชี้ให้เห็นมีแพลตฟอร์ม OS และชุดคำสั่งทางเลือกเฉพาะซึ่งสามารถเร็วกว่าข้างต้นได้มาก ในเคอร์เนลลินุกซ์มีมาโคร (เช่น cpu_to_be32) ซึ่งจัดการกับ endianness ได้ค่อนข้างดี แต่ทางเลือกเหล่านี้เฉพาะสำหรับสภาพแวดล้อมของพวกเขา ในทางปฏิบัติ endianness จะจัดการได้ดีที่สุดโดยใช้แนวทางที่มีอยู่ผสมผสานกัน
((num & 0xff) >> 8) | (num << 8)
gcc 4.8.3 จะสร้างrol
คำสั่งเดียว และถ้าการแปลง 32 บิตถูกเขียนเป็น((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24)
คอมไพเลอร์เดียวกันจะสร้างbswap
คำสั่งเดียว
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;}
โดยที่นี่คือบิตฟิลด์ที่มี 8 ฟิลด์ 1 บิต แต่ฉันไม่แน่ใจว่าเร็วเท่าคำแนะนำอื่น ๆ หรือไม่ สำหรับ ints ใช้union { int i; byte_t[sizeof(int)]; }
เพื่อย้อนกลับไบต์โดยไบต์ในจำนวนเต็ม
โดยรวม:
#include <byteswap.h>
คุณสามารถรับฟังก์ชั่นการแลกเปลี่ยนไบต์ที่ขึ้นอยู่กับเครื่องได้ จากนั้นคุณสามารถใช้ฟังก์ชันต่อไปนี้ได้อย่างง่ายดาย:
__bswap_32 (uint32_t input)
หรือ
__bswap_16 (uint16_t input)
#include <byteswap.h>
ดู comment ในไฟล์. h เอง โพสต์นี้มีข้อมูลที่เป็นประโยชน์ดังนั้นฉันจึงโหวตขึ้นแม้ว่าผู้เขียนจะเพิกเฉยต่อข้อกำหนด OP ที่จะไม่ใช้ฟังก์ชัน 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);
}
อัปเดต : เพิ่มการแลกเปลี่ยนไบต์ 64 บิต
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
และint64_t
ตัวแปรอะไรคือเหตุผลที่อยู่เบื้องหลังการปิดบัง... & 0xFFFF
และ... & 0xFFFFFFFFULL
? มีบางอย่างเกิดขึ้นกับส่วนขยายเครื่องหมายที่นี่ฉันไม่เห็นหรือไม่ แล้วทำไมถึงswap_int64
กลับมาuint64_t
? ไม่ควรอย่างนั้นint64_t
หรือ?
swap_int64
ในคำตอบของคุณ +1 สำหรับคำตอบที่เป็นประโยชน์ BTW!
LL
ที่ไม่จำเป็น(u)swap_uint64()
มากเช่นL
ไม่จำเป็นใน(u)swap_uint32()
. U
ไม่จำเป็นต้องใช้ในuswap_uint64()
มากเช่นU
ไม่จำเป็นต้องใช้ในuswap_uint32()
นี่เป็นเวอร์ชันที่ค่อนข้างทั่วไป ฉันยังไม่ได้รวบรวมมันจึงอาจมีการพิมพ์ผิด แต่คุณควรเข้าใจ
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));
หมายเหตุ:สิ่งนี้ไม่ได้ปรับให้เหมาะกับความเร็วหรือพื้นที่ มีวัตถุประสงค์เพื่อให้ชัดเจน (ง่ายต่อการแก้ไขข้อบกพร่อง) และพกพาได้
อัปเดต 2018-04-04 เพิ่มการยืนยัน () เพื่อดักจับกรณีที่ไม่ถูกต้องของ n == 0 ตามที่ผู้แสดงความคิดเห็น @chux เห็น
bswap
คำสั่งเดียวโดยคอมไพเลอร์ X86 ที่เหมาะสมพร้อมเปิดใช้งานการเพิ่มประสิทธิภาพ เวอร์ชันที่มีพารามิเตอร์สำหรับขนาดนี้ไม่สามารถทำได้
หากคุณต้องการมาโคร (เช่นระบบฝังตัว):
#define SWAP_UINT16(x) (((x) >> 8) | ((x) << 8))
#define SWAP_UINT32(x) (((x) >> 24) | (((x) & 0x00FF0000) >> 8) | (((x) & 0x0000FF00) << 8) | ((x) << 24))
UINT
ชื่อของพวกเขา
แก้ไข:นี่คือฟังก์ชันไลบรารี ทำตามขั้นตอนเหล่านี้ด้วยตนเอง
ผมตะลึงอย่างแน่นอนโดยจำนวนของคนที่ไม่รู้__byteswap_ushort, __byteswap_ulong และ __byteswap_uint64 แน่นอนว่าเป็น Visual C ++ ที่เฉพาะเจาะจง แต่พวกเขารวบรวมโค้ดแสนอร่อยบนสถาปัตยกรรม x86 / IA-64 :)
นี่คือการใช้bswap
คำสั่งอย่างชัดเจนซึ่งดึงมาจากหน้านี้ หมายเหตุว่ารูปแบบที่แท้จริงข้างต้นจะเสมอจะเร็วกว่านี้ฉันเพิ่มเพียง แต่มันจะให้คำตอบโดยไม่ต้องประจำห้องสมุด
uint32 cq_ntohl(uint32 a) {
__asm{
mov eax, a;
bswap eax;
}
}
เป็นเรื่องตลก:
#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
และไม่ใช่ประเภทเดียวกันสำหรับทั้งสองอย่าง
นี่คือวิธีการใช้คำสั่ง SSSE3 pshufb โดยใช้ Intel intrinsic โดยสมมติว่าคุณมีผลคูณของ 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;
}
จะทำงาน / เร็วกว่านี้ไหม
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
byte
นี่คือฟังก์ชั่นที่ฉันใช้ - ทดสอบและใช้งานได้กับข้อมูลพื้นฐานทุกประเภท:
// 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
มีการจัดตำแหน่งตามความจำเป็น - แต่ถ้าไม่ถือสมมติฐานนั้นรหัสจะเป็น UB
แก้ไข: ฟังก์ชั่นนี้จะแลกเปลี่ยนเฉพาะความสมบูรณ์ของคำ 16 บิตที่จัดชิดกันเท่านั้น ฟังก์ชันมักจำเป็นสำหรับการเข้ารหัส UTF-16 / UCS-2 แก้ไข END
หากคุณต้องการเปลี่ยนจุดสิ้นสุดของบล็อกความทรงจำคุณสามารถใช้วิธีการที่รวดเร็วอย่างเห็นได้ชัด อาร์เรย์หน่วยความจำของคุณควรมีขนาดเท่ากับ 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);
}
ฟังก์ชันประเภทนี้มีประโยชน์สำหรับการเปลี่ยนจุดสิ้นสุดของไฟล์ Unicode UCS-2 / UTF-16
t know if it
เร็วเท่าที่คำแนะนำ แต่มันจะเปลี่ยนไป: github.com/heatblazer/helpers/blob/master/utils.h
CHAR_BIT
แทนที่จะ8
เป็นอยากรู้ว่าจะขึ้นอยู่กับ0xFF00FF00FF00FF00ULL
CHAR_BIT == 8
โปรดทราบว่าLL
ไม่จำเป็นต้องใช้ค่าคงที่
CHAR_BIT
เพื่อเพิ่มการเปิดรับแสงของมาโครนั้นเท่านั้น สำหรับ LL มันเป็นคำอธิบายประกอบมากกว่าสิ่งอื่นใด นอกจากนี้ยังเป็นนิสัยที่ฉันจับได้เมื่อนานมาแล้วด้วยคอมไพเลอร์ buggy (มาตรฐานก่อน) ซึ่งจะไม่ทำสิ่งที่ถูกต้อง
ข้อมูลโค้ดนี้สามารถแปลงหมายเลข Endian ขนาดเล็ก 32 บิตเป็นหมายเลข Big Endian
#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);
}
หากคุณรันบนโปรเซสเซอร์ x86 หรือ x86_64 big endian จะเป็นแบบเนทีฟ ดังนั้น
สำหรับค่า 16 บิต
unsigned short wBigE = value;
unsigned short wLittleE = ((wBigE & 0xFF) << 8) | (wBigE >> 8);
สำหรับค่า 32 บิต
unsigned int iBigE = value;
unsigned int iLittleE = ((iBigE & 0xFF) << 24)
| ((iBigE & 0xFF00) << 8)
| ((iBigE >> 8) & 0xFF00)
| (iBigE >> 24);
นี่ไม่ใช่วิธีแก้ปัญหาที่มีประสิทธิภาพสูงสุดเว้นแต่คอมไพเลอร์จะรับรู้ว่านี่เป็นการจัดการระดับไบต์และสร้างรหัสแลกเปลี่ยนไบต์ แต่มันไม่ได้ขึ้นอยู่กับเทคนิคการจัดวางหน่วยความจำใด ๆ และสามารถเปลี่ยนเป็นมาโครได้อย่างง่ายดาย