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


93

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


5
16-битное значение? 32-битное значение? плавать? массив?
— Джон Кнеллер

20
пора ли выбрать ответ?
— Аникет Инге

7
Голосование для открытия. То же, что и stackoverflow.com/questions/105252/… для C ++. Мы могли бы просто отредактировать, чтобы было понятнее.
— Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

Ответы:


173

Предполагая, что вам нужен простой обмен байтами, попробуйте что-то вроде

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 бит каждое. Но я не уверен, что это так быстро, как другие предложения. Для целых чисел используйте union { int i; byte_t[sizeof(int)]; }для обратного байта байта в целое число.
— Илиан Запрянов

Я думаю, что выражение должно быть: (num >> 8) | (num << 8) для изменения порядка байтов и НЕ: ((num & 0xff) >> 8) | (num << 8), неправильный пример получает ноль в младшем байте.
— jscom

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

105

Включая:

#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
не существует в Windows - по крайней мере, при кросс-компиляции из 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_tи int64_tвариантов, что рассуждения за маскировку ... & 0xFFFFи ... & 0xFFFFFFFFULL? Что-то происходит со знаком-расширением, которого я не вижу? Кроме того, почему swap_int64возвращается uint64_t? Разве этого не должно быть int64_t?
— bgoodr 02

1
Swap_int64, возвращающий uint64, действительно является ошибкой. Маскировка с помощью подписанных значений int действительно предназначена для удаления знака. Сдвиг вправо вводит бит знака слева. Мы могли бы избежать этого, просто вызвав операцию замены unsigned int.
— chmike 03

Спасибо. Возможно, вы захотите изменить тип возвращаемого значения swap_int64в своем ответе. +1 за полезный ответ, BTW!
— bgoodr 04

Зависит ли побитовое и обратное значение?
— MarcusJ

1
Они LLне нужны, (u)swap_uint64()как и Lне нужны (u)swap_uint32(). Это Uне нужно во uswap_uint64()многом, как Uне нужно вuswap_uint32()
— chux - Reinstate Monica

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

@Alnitak - Как я уже сказал, я не приложил никаких усилий для оптимизации своего кода. Когда пользователь обнаружил, что код выполняется очень быстро (в одном случае), я просто упомянул общую идею, что простой код часто может быть сильно оптимизирован компилятором. Мой код работает в самых разных случаях, его довольно легко понять и, следовательно, легко отлаживать. Это соответствовало моим целям.
— 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в их имени стоит .
— коль

Да, правда, извините за шум. Не лучше ли встроить приведение типов?

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
На вопрос C вы предлагаете что-то специфическое для Visual C ++?
— Алок Сингхал

3
@Alok: Visual C ++ - продукт Microsoft. Он отлично работает для компиляции кода C. :)
— Сэм Харвелл

20
Почему вас удивляет то, что многие люди не знают о специфичных для Microsoft реализациях перестановки байтов?
— dreamlax

36
Круто, это хорошая информация для всех, кто разрабатывает продукт с закрытым исходным кодом, который не должен быть переносимым или соответствовать стандартам.
— Sam Post

6
@Alok, OP не упомянул компилятор | OS. Человек может давать ответы в соответствии с его опытом работы с определенным набором инструментов.
— Аникет Инге

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 05

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

Ты прав, 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);может быть быстрее на некоторых платформах (например, повторное использование констант маски AND). Однако большинство компиляторов сделали бы это, но некоторые простые компиляторы не могут оптимизировать это за вас.

-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.