преобразовать прямой порядок байтов в обратный порядок байтов в 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);
}
chmike
источник
Для 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;
}
jcomeau_ictx
источник
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