在C中将大字节序转换为小字节序[不使用提供的功能] [关闭]


91

我需要编写一个将C中的大端转换为小端的函数。我不能使用任何库函数。


5
16位值?32位值?浮动?数组?
约翰·诺勒

19
是时候选择答案了吗?
Aniket Inge

7
投票重新开放。与C ++的stackoverflow.com/questions/105252/…相同。我们可以进行编辑以使其更清晰。
西罗Santilli郝海东冠状病六四事件法轮功

Answers:


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

0xdeadbeef会将字节顺序从位置1234交换到4321。如果您输入的是32位字节的字节序交换,其输出可能为0xefbeadde

上面的代码应使用宏或至少常量(而不是幻数)进行清理,但希望它能照搬

编辑:正如另一个答案指出的那样,有特定于平台,操作系统和指令集的替代方案,它们可能比上述方法快得多。在Linux内核中,有一些宏(例如cpu_to_be32)可以很好地处理字节序。但是这些替代方案特定于其环境。在实践中,最好使用多种可用方法来处理字节序


5
+1代表特定于平台/硬件的方法。程序始终在某些硬件上运行,并且硬件功能始终是最快的。
尼尔2012年

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)]; }来逐字节逆转整数。
Ilian Zapryanov '16

我认为表达式必须是:(num >> 8)| (num << 8)来反转字节顺序,而不是:((num&0xff)>> 8)| (num << 8),错误的示例在低字节中获取零。
jscom

@IlianZapryanov也许为了清晰起见+1,但是像这样在C中使用位域可能是最不高效的方法。
sherrellbc

104

通过包括:

#include <byteswap.h>

您可以获得与机器有关的字节交换功能的优化版本。然后,您可以轻松使用以下功能:

__bswap_32 (uint32_t input)

要么

__bswap_16 (uint16_t input)

3
感谢您的回答,但我无法使用任何库函数
Mark Ransom

4
应该阅读#include <byteswap.h>,请参阅.h文件本身中的注释。这篇文章包含有用的信息,因此尽管作者忽略了不使用lib函数的OP要求,但我还是投票赞成。
伊莱·罗森克鲁夫特

30
实际上,__bswap_32 / __ bswap_16函数实际上是宏,而不是库函数,这是要投票的另一个原因。
伊莱·罗森克鲁夫特

7
我的理解是,不能保证所有架构上的所有操作系统都存在该标头。我还没有找到一种可移植的方式来处理字节序问题。
爱德华·福尔克

2
在Windows上不存在-至少从mingw 32或64位从linux进行交叉编译时至少不存在
bph

62
#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_tint64_t变体,对... & 0xFFFF和进行屏蔽的原因是什么... & 0xFFFFFFFFULL?我看不到这里是否发生了与符号扩展有关的事情?另外,为什么要swap_int64返回uint64_t?那不是int64_t吗?
bgoodr 2012年

1
swap_int64返回uint64确实是一个错误。具有带符号的int值的掩蔽确实是要删除符号。向右移动会在左侧注入符号位。我们可以通过简单地调用unsigned int交换操作来避免这种情况。
chmike 2012年

谢谢。您可能要更改swap_int64答案中返回值的类型。+1有用的答案,顺便说一句!
bgoodr 2012年

是按位和值的字节序依赖吗?
MarcusJ 2015年

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));

注意:不是针对速度或空间进行优化的。它旨在保持清晰(易于调试)和便携式。

更新2018-04-04 添加了assert()来捕获n == 0的无效情况,正如评论者@chux所发现的那样。


1
您可以使用xorSwap以获得更好的性能。在所有特定于大小的版本之前,均

我测试了它,结果证明它比x86上的xorSwap ...更快。stackoverflow.com/questions/3128095/...

1
@nus-非常简单的代码的优点之一是编译器优化程序有时可以使其变得非常快。
迈克尔·J

@MichaelJ OTOH,上面chmike的答案中的32位版本,bswap由运行良好的X86编译器编译成一条指令。带有尺寸参数的版本无法执行此操作。
Alnitak

@Alnitak-正如我所说,我不花力气优化代码。当nus用户发现代码运行非常快时(在一种情况下),我只是提到了通常的想法,即简单代码通常可以由编译器进行高度优化。我的代码适用于各种情况,很容易理解,因此很容易调试。达到了我的目标。
Michael J

8

如果需要宏(例如嵌入式系统):

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

这些宏很好,但是当有符号整数在0x80000000和0xffffffff之间时((x)>> 24)将失败。在这里使用按位与是一个好主意。注意:((x)<< 24)是绝对安全的。如果高16位非零(或提供带符号的16位值),则(x)>> 8)也将失败。

2
@PacMan-这些宏仅用于交换无符号整数。这就是为什么UINT他们的名字中有。
kol 2014年

是的,是的,抱歉。嵌入类型转换不是最好的方法吗?

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 ++的内容吗?
Alok Singhal'2

3
@Alok:Visual C ++是Microsoft的产品。对于编译C代码来说,它工作得很好。:)
山姆·哈威尔

20
为什么让很多人不了解Microsoft特定的字节交换实现使您惊讶?
dreamlax

36
太好了,对于开发不需要便携式或符合标准的封闭源产品的任何人来说,这都是好信息。
山姆邮政,2010年

6
@ Alok,OP没有提到编译器| OS。允许一个人根据自己的经验使用一组特定的工具给出答案。
Aniket Inge 2012年

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

编辑:此函数仅交换对齐的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以使代码完整。
托纳·塞缪尔

好的,我添加了缺少的包含项。
PatrickSchlüter,2013年

这是C ++中交换的链接,我t know if it的建议速度不如建议快,但它确实行得通:github.com/heatblazer/helpers/blob/master/utils.h
Ilian Zapryanov

CHAR_BIT而不是8好奇,因为0xFF00FF00FF00FF00ULL依赖CHAR_BIT == 8。注意LL在常量中不需要。
chux-恢复莫妮卡

你说得对。仅写CHAR_BIT以增加该宏的显示范围。至于LL,它更是一种注解。这也是我很久以前使用错误的编译器(标准版)捕获的习惯,该编译器无法正确执行操作。
PatrickSchlüter18年

1

此代码段可以将32位Little Endian数转换为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);    
}

谢谢@YuHao我是新来的,不知道如何格式化文本。
Kaushal Billore

2
((i>>24)&0xff) | ((i>>8)&0xff00) | ((i&0xff00)<<8) | (i<<24);在某些平台上使用可能会更快(例如,循环使用AND掩码常量)。尽管大多数编译器都会这样做,但是一些简单的编译器无法为您优化它。

-7

如果您在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);

除非编译器认识到这是字节级操作并生成字节交换代码,否则这不是最有效的解决方案。但这不依赖于任何内存布局技巧,可以很容易地转换为宏。


25
在x86和x86_64体系结构上,little endian方案是本机方案。
MK又名Grisu,2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.