перетворити великий ендіан в маленький ендіан у мові C [без використання наданої функції] [закрито]


91

Мені потрібно написати функцію для перетворення великого ендіана в маленький ендіан у мові C. Я не можу використовувати жодну функцію бібліотеки.


5
16-бітове значення? 32-бітове значення? плавати? масив?
John Knoeller 02

19
час вибрати відповідь, можливо?
Aniket Inge

7
Голосування за відновлення. Те саме, що stackoverflow.com/questions/105252/… для C ++. Ми могли б просто редагувати, щоб зробити це зрозумілішим.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Відповіді:


168

Припустивши, що вам потрібен простий обмін байтами, спробуйте щось на зразок

Непідписане 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), які досить добре обробляють ендіанс. Але ці альтернативи специфічні для їх середовища. На практиці з ендіанством найкраще боротися, використовуючи поєднання доступних підходів


5
+1 за згадування методів, що стосуються певної платформи / обладнання Програми завжди працюють на певному обладнанні, а апаратні функції завжди є найшвидшими.
eonil

21
якщо 16-бітове перетворення виконується як ((num & 0xff) >> 8) | (num << 8), gcc 4.8.3 генерує одну rolінструкцію. І якщо 32-бітове перетворення записано як ((num & 0xff000000) >> 24) | ((num & 0x00ff0000) >> 8) | ((num & 0x0000ff00) << 8) | (num << 24), той самий компілятор генерує одну bswapінструкцію.
user666412

Я не знаю, наскільки це ефективно, але я поміняв порядок байтів на 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)]; }зворотний байт за байтом у цілому числу.
Іліан Запрянов

Думаю, вираз повинен бути: (число >> 8) | (num << 8), щоб змінити порядок байтів, а NOT: ((num & 0xff) >> 8) | (число << 8), Неправильний приклад отримує нуль у малому байті.
jscom

@IlianZapryanov Можливо, +1 для ясності, але використання польових полів в C, як це, мабуть, найменш ефективний спосіб це зробити.
sherrellbc

104

Включаючи:

#include <byteswap.h>

Ви можете отримати оптимізовану версію машинно-залежних функцій заміни байтів. Тоді ви можете легко використовувати такі функції:

__bswap_32 (uint32_t input)

або

__bswap_16 (uint16_t input)

3
Дякую за вашу відповідь, але я не можу використовувати жодну функцію бібліотеки
Марк Ренсом

4
Якщо прочитати #include <byteswap.h>, див. Коментар у самому файлі .h. Ця публікація містить корисну інформацію, тому я проголосував, незважаючи на те, що автор ігнорував вимогу OP не використовувати функцію lib.
Елі Розенкруфт,

30
Насправді функції __bswap_32 / __ bswap_16 насправді є макросами, а не функціями бібліотеки, ще однією причиною для голосування.
Елі Розенкруфт,

7
Я розумію, що цей заголовок не гарантується для всіх операційних систем на всіх архітектурах. Я ще не знайшов портативного способу вирішення ендіанських проблем.
Едвард Фальк

2
не існує у вікнах - принаймні не при перехресній компіляції з linux за допомогою mingw 32 або 64 біт
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);
}

Оновлення : Додано обмін 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_tand int64_t, якими аргументами є маскування ... & 0xFFFFта ... & 0xFFFFFFFFULL? Тут щось не відбувається із розширенням знака, якого я не бачу? Крім того, чому swap_int64повертається uint64_t? Це не повинно бути int64_t?
bgoodr

1
Swap_int64, що повертає uint64, насправді є помилкою. Маскування із підписаними значеннями int справді полягає у видаленні знаку. Переміщення вправо вводить знаковий біт зліва. Ми могли б уникнути цього, просто зателефонувавши операції заміни int без підпису.
chmike

Дякую. Можливо, ви захочете змінити тип значення, що повертається, swap_int64у вашій відповіді. +1 за корисну відповідь, до речі!
bgoodr

Чи побітове та значення ендіан залежить?
MarcusJ

1
LLЗайві в (u)swap_uint64()дуже як Lне потрібно (u)swap_uint32(). Це Uне потрібно так uswap_uint64()само, як Uне потрібно уuswap_uint32()
chux - Відновити Моніку

13

Ось досить загальна версія; Я його не скомпілював, тому, ймовірно, є помилки, але ви повинні зрозуміти ідею,

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.


1
Ви можете використовувати xorSwap для кращої роботи. Віддавайте перевагу цій загальній версії над усіма, що стосуються розміру ...

Я протестував, виявляється, це швидше, ніж xorSwap ... на x86. stackoverflow.com/questions/3128095 / ...

1
@nus - Однією з переваг дуже простого коду є те, що оптимізатор компілятора іноді може зробити це дуже швидко.
Michael J

@MichaelJ OTOH, 32-розрядна версія вище у відповіді chmike компілюється в одну bswapінструкцію гідним компілятором X86 із увімкненою оптимізацією. Ця версія з параметром розміру не могла цього зробити.
Альнітак

@Alnitak - Як я вже говорив, я не докладав зусиль, щоб оптимізувати свій код. Коли користувацький nus виявив, що код працює дуже швидко (в одному випадку), я щойно згадав загальну ідею, що простий код часто може бути високо оптимізований компілятором. Мій код працює для найрізноманітніших випадків, і його досить легко зрозуміти і, таким чином, легко налагодити. Це відповідало моїм цілям.
Michael J

9

Якщо вам потрібні макроси (наприклад, вбудована система):

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

Ці макроси нормальні, але ((x) >> 24) не вдасться, коли підписане ціле число знаходиться між 0x80000000 та 0xffffffff. Добре тут використовувати побітове І. Примітка: ((x) << 24) є абсолютно безпечним. (x) >> 8) також не вдасться, якщо високі 16 бітів ненульові (або надано підписане 16-бітове значення).

2
@ PacMan - Ці макроси призначені для обміну лише беззнаковими цілими числами. Ось чому UINTв їх назві є.
kol

Так, правда, вибачте за шум. Чи не найкраще було б вбудувати набір шрифтів?

5

Редагувати: це функції бібліотеки. Слідуючи їм, це зробити вручну.

Я абсолютно вражений кількістю людей, які не знають про __byteswap_ushort, __byteswap_ulong та __byteswap_uint64 . Звичайно, вони специфічні для Visual C ++, але вони компілюються до смачного коду на архітектурах x86 / IA-64. :)

Ось явне використання bswapінструкції, витягнутої з цієї сторінки . Зауважте, що внутрішня форма вище завжди буде швидшою за цю , я додав її лише для того, щоб дати відповідь без бібліотечної процедури.

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

21
Для запитання на С ви пропонуєте щось, що є специфічним для Visual C ++?
Alok Singhal

3
@Alok: Visual C ++ - це продукт Microsoft. Це чудово працює для компіляції коду C. :)
Сем Гарвелл

20
Чому вас приголомшує те, що багато людей не знають про реалізацію байт-свопінгу, що стосується Microsoft?
dreamlax 02

36
Класно, це гарна інформація для тих, хто розробляє продукт із закритим кодом, який не повинен бути портативним або відповідати стандартам.
Sam Post

6
@Alok, OP не згадав про компілятор | ОС. Людині дозволяється давати відповіді відповідно до свого досвіду роботи з певним набором інструментів.
Aniket Inge

5

На жарт:


#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
ХАХАХАХА. Ха-ха-ха. Ха. Га? (Який жарт?)

3
ти дістав це з якогось сховища джерел Windows? :)
hochl

Nodejs використовує цю техніку! github.com/nodejs/node/blob/…
Джастін Мозер

Цікаво використовувати int i, size_t sizeofIntі не однаковий тип для обох.
chux

5

ось спосіб використання інструкції 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;
}

3

Це буде працювати / буде швидше?

 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
Я думаю, ви маєте на увазі char, ні byte.
dreamlax

Використовуючи цю стратегію, рішення, яке набрало найбільше голосів порівняно з вашим, є рівноцінним та найбільш ефективним та портативним. Однак рішення, яке я пропоную (друге за більшістю голосів), потребує менше операцій і повинно бути більш ефективним.
chmike

1

Ось функція, яку я використовував - перевірена і працює на будь-якому основному типі даних:

//  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
Код покладається на цілком обгрунтоване припущення: sourceвирівнюється за необхідності - проте, якщо це припущення не виконується, код - UB.
chux

1

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.


CHAR_BIT #define відсутній, щоб зробити код повним.
Тину Самуель

Гаразд, я додав відсутні елементи.
Патрік Шлютер

ось посилання на обмін у C ++, я не t know if itтак швидко, як і пропозиції, але це працює: github.com/heatblazer/helpers/blob/master/utils.h
Іліан Запрянов

CHAR_BITзамість 8цікаво, оскільки 0xFF00FF00FF00FF00ULLзалежить від CHAR_BIT == 8. Зверніть увагу, що це LLне потрібно в константі.
chux

Ви маєте рацію чукс. Лише написав з, CHAR_BITщоб збільшити експозицію цього макросу. Що стосується LL, це більше анотація, ніж будь-що інше. Це також звичка, яку я давно зрозумів із компіляторами баггі (попередньо стандартними), які не робили б правильно.
Патрік Шлютер,

1

Цей фрагмент коду може перетворити 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);    
}

Дякую @YuHao Я тут новачок, не знаю, як форматувати текст.
Kaushal Billore

2
Використання ((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);може бути швидшим на деяких платформах (наприклад, переробка констант маски І). Хоча більшість компіляторів зробили б це, але деякі прості компілятори не можуть оптимізувати це для вас.

-7

Якщо ви працюєте на процесорі 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);

Це не найефективніше рішення, якщо компілятор не визнає, що це маніпуляція на рівні байтів, і не генерує код заміни байтів. Але це не залежить від будь-яких хитрощів щодо розміщення пам'яті, і його можна досить легко перетворити на макрос.


25
У архітектурах x86 та x86_64 мала ендіанська схема є рідною.
MK aka Grisu
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.