Мені потрібно написати функцію для перетворення великого ендіана в маленький ендіан у мові C. Я не можу використовувати жодну функцію бібліотеки.
Мені потрібно написати функцію для перетворення великого ендіана в маленький ендіан у мові 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
, 32-розрядний ендіанний своп міг мати результат 0xefbeadde
.
Наведений вище код слід очистити за допомогою макросів або принаймні констант замість магічних чисел, але, сподіваємось, це допоможе як є
РЕДАГУВАТИ: як зазначено в іншій відповіді, існують конкретні альтернативи платформи, ОС та набору інструкцій, які можуть бути НАБАГАТО швидшими, ніж зазначені вище. У ядрі Linux є макроси (наприклад, cpu_to_be32), які досить добре обробляють ендіанс. Але ці альтернативи специфічні для їх середовища. На практиці з ендіанством найкраще боротися, використовуючи поєднання доступних підходів
((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>
, див. Коментар у самому файлі .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
and int64_t
, якими аргументами є маскування ... & 0xFFFF
та ... & 0xFFFFFFFFULL
? Тут щось не відбувається із розширенням знака, якого я не бачу? Крім того, чому swap_int64
повертається uint64_t
? Це не повинно бути int64_t
?
swap_int64
у вашій відповіді. +1 за корисну відповідь, до речі!
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));
NB: Це не оптимізовано для швидкості або простору. Він призначений бути зрозумілим (легким для налагодження) та портативним.
Оновлення 2018-04-04 Додано assert (), щоб зафіксувати неприпустимий регістр 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, припускаючи, що у вас кратно 4 int
с:
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.
EDIT: Ця функція міняє місцями лише вирівняні 16-бітові слова. Функція, часто необхідна для кодування UTF-16 / UCS-2. РЕДАКТУВАТИ КІНЕЦЬ.
Якщо ви хочете змінити зовнішній вигляд блоку пам'яті, ви можете скористатися моїм надзвичайно швидким підходом. Ваш масив пам'яті повинен мати розмір, кратний 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, це більше анотація, ніж будь-що інше. Це також звичка, яку я давно зрозумів із компіляторами баггі (попередньо стандартними), які не робили б правильно.
Цей фрагмент коду може перетворити 32-бітне маленьке число Ендіана у велике Ендіан.
#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, великий ендіан є рідним. так
для 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);
Це не найефективніше рішення, якщо компілятор не визнає, що це маніпуляція на рівні байтів, і не генерує код заміни байтів. Але це не залежить від будь-яких хитрощів щодо розміщення пам'яті, і його можна досить легко перетворити на макрос.