大小端的理解以及宏定义实现的理解

本文详细解释了大端模式和小端模式在计算机存储中的区别,涉及16/32/64位数据类型,结构体,指针和网络字节流的处理。通过实例展示了如何根据大小端模式解析和转换数据,以及如何使用GCC内置函数处理字节序问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大小端定义

在计算机系统中,数据存储是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小 端存储模式。这里的关键就在于,数据存储以及地址单元对应的最基本单位只有一个字节,8bit。
对于数据16进制数据0x1234,包含两个字节,注意左边的0x12为高位数据(不是34),右边的0x34是低位数据,假设从那个地址0x4000开始存放数据,则在大端模式(Big-endian)中,保存方式为
在这里插入图片描述
所谓的大端模式,是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
对字符串高低位的理解:
char a[]= “12345”
这里1是最高位,5是最低位,也就是从左往右看,和数字的高低位一样,注意不是看数组下标。数组下标和高低位没关系。

从直观上来说,Big-endian的方式很符合日常对数据的解读。

Little-endian的方式为
内存地址
0x4000
0x4001
存放内容
0x34
0x12

所谓的小端模式,是指数据的低位保存在内存的低地址中,而数 据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。

大小端模式适用范围

大小端模式指的是16bit、32bit、64bit数据类型的保存方式,指的是多个字节的情况下哪个字节保存高位哪个字节保存低位的情况。
对于单字节,8bit,是没有大小端的说法的。至于对于0x1的二进制是00000001还是10000000,指的是位域的操作,不做过多研究。位域
对于2字节16bit数据,数据保存方式存在上述大小端两种情况,因此对于不同的编译器相互间的通讯时需要进行数据结构的转化。
32bit和64bit的情况类似。
对于结构体,例如

typedef struct template
{
    uint8_t a;
    uint16_t b;
}template_t;
template_t v_template;
v_template.a = 1;//8位16进制0x01
v_template.b=1;////16位16进制0x0001,高位是0x00,低位是0x01

假设template的起始地址为0x4000,那么大小端模式下结构体template的保存方式如:
大端:
在这里插入图片描述

小端:
在这里插入图片描述

也就是说对于结构体,大段模式并不是指所有的数据都从高位保存,仅仅在结构体含有的成员变量大于1个字节的情况下才会有大小端的问题。对于单个字节,不存在大小端问题。
指针、大小端、网络字节流发送
对于指针来说,不管是大端模式还是小端模式,指针总是指向低位地址,例如上述的template结构体,假设指针template_t指向该结构体,则他指向的地址是v_template.a的内存地址,而不是v_template.b的高位地址。即使地址+1,指向的也只是v_tempalte的低位地址。

typedef struct template
{
    uint8_t a;
    uint16_t b;
}template_t;
template_t v_template;
v_template.a = 1;//8位16进制0x01
v_template.b=1;////16位16进制0x0001,高位是0x00,低位是0x01

template_t * template_t = &v_template;//

当网络发送字节流时,是不会考虑大小端的问题,网络只是单纯的将字节打包发送,发送顺序为从低位地址开始到高位地址结束。例如上述的v_template变量,先不管大小端的问题,也就是不管其中的变量,先看地址,那么首先开始发送的字节地址是0x4000,接下来分别是0x40001、0x4002。网络接受方的数据组织形式也是从低位地址到高位地址依次存放,一一对应。
发送方地址:
在这里插入图片描述
接收方地址
在这里插入图片描述

**不考虑结构体类型,也不考虑大小端的问题,只看地址发送的内容,**首先发送的地址是0x4000,数据为0x1,接下来是0x4001,数据为0x01,0x4002,数据为0x00,那么接受方接受到的地址数据也是如此。随后接收方需要定义结构体解读接受到的内容,如果是小端模式,定义相同的结构体template_t,那么成员a(uint8_t)的地址为0x3000,值为0x01,成员b(uint16_t)的地址为0x3001,0x3002,地址中的值分别为0x01,0x00,按照,小端模式的解读,最后变量a=0x01,b=0x0001。反正,如果是大端模式,那么成员a(uint8_t),占8位,因此为地址为0x3000,成员b(uint16_t)的地址为0x3001,0x3002,地址中的值分别为0x01,0x00,按照大段模式解读,a=0x01,b=0x0100(低位地址保存高位数据,高位地址保存低位数据,左边的是高位数值,右边的是低位数值)。
总结来说,不管是大段还是小端,对于地址来说,每个地址单元保存的单字节数据都是相同的,不同点在于对地址数据的解读方式。

Linux中大小端宏定义以及实现,自定义实现

gcc中预定义宏__BYTE_ORDER__、ORDER_LITTLE_ENDIANORDER_BIG_ENDIAN。可以在代码中直接使用。
6.58 Other Built-in Functions Provided by GCC(点击打开链接)
这个页面最后面三个函数就是我们需要的:
— Built-in Function: uint16_t __builtin_bswap16 (uint16_t x)
Returns x with the order of the bytes reversed; for example, 0xaabb becomes 0xbbaa. Byte here always means exactly 8 bits.//返回x的反序字节,例如:0xaabb变成0xbbaa,下面类同。
— Built-in Function: uint32_t __builtin_bswap32 (uint32_t x)
Similar to __builtin_bswap16, except the argument and return types are 32 bit.
— Built-in Function: uint64_t __builtin_bswap64 (uint64_t x)
Similar to __builtin_bswap32, except the argument and return types are 64 bit.
参考:https://blog.youkuaiyun.com/10km/article/details/49021499

大小端对移位运算有没有影响? 大小端是内存存储数据高位和数据低位的不同顺序;而移位移的是寄存器中的内存,与内存是无关的。

左移代表低位向高位移动,右移代表高位向地位移动。 宏定义实现:,先移位后保留,先保留后移位,两种方式不同。

#define sw16(A) ((((u16)(A) & 0xff00) >> 8) | (((u16)(A) & 0x00ff) << 8))
//这里为什么是0xff00而不是0x00ff呢?((u16)(A) & 0xff00)先保留高位,在做移到低位,
//另一个类似。也可以写成
//#define sw16(A) (((((u16)(A) >>8)& 0x00ff)) | (((u16)(A) <<8) & 0xff00))
#define sw32(A) ((((u32)(A) & 0xff000000) >> 24) | (((u32)(A) & 0x00ff0000) >> 8) | (((u32)(A) & 0x0000ff00) << 8) | (((u32)(A) & 0x000000ff) << 24))
#define sw64(A) ((uint64_t)(\
                (((uint64_t)(A)& (uint64_t)0x00000000000000ffULL) << 56) | \
                (((uint64_t)(A)& (uint64_t)0x000000000000ff00ULL) << 40) | \
                (((uint64_t)(A)& (uint64_t)0x0000000000ff0000ULL) << 24) | \
                (((uint64_t)(A)& (uint64_t)0x00000000ff000000ULL) << 8) | \
                (((uint64_t)(A)& (uint64_t)0x000000ff00000000ULL) >> 8) | \
                (((uint64_t)(A)& (uint64_t)0x0000ff0000000000ULL) >> 24) | \
                (((uint64_t)(A)& (uint64_t)0x00ff000000000000ULL) >> 40) | \
                (((uint64_t)(A)& (uint64_t)0xff00000000000000ULL) >> 56) ))

自定义实现:根据地址连续的条件,可以利用union或者指针强转的方式实现。

static void endian_check()
{
    union
    {
        uint32_t val_int;
        uint8_t  val_char;
    } val_union;

    val_union.val_int = 1U;
    if (val_union.val_char != 1U)
    {
        g_endian_check = TIME_BIG_ENDIAN;
    }
    else
    {
        // do nothing
    }
}
<think>好的,用户现在需要查找关于大小端转换的宏定义代码或实现方法,特别是在C/C++中的示例。首先,我需要回忆一下大小端的概念,以及如何在代码中进行转换。大小端涉及数据在内存中的存储顺序,大端模式将高位字节存储在低地址,小端模式则相反。接下来,用户提到宏定义,所以需要找到用实现的方法。常见的转换方法包括使用位操作或字节交换。比如,16位、32位和64位数值的转换可能需要不同的。例如,对于16位的转换,可以通过位移操作来交换高低字节。同样,32位的转换可能需要更复杂的位移或使用内置函数。然后,我需要考虑如何将这些操作封装成。例如,使用位移运算符将高位和低位字节交换位置。同时,需要注意的正确性,比如括号的使用以避免运算符优先级问题。此外,还可以利用联合体(union)来通过类型双关直接访问字节,这可能更高效。另外,用户提供的引用中提到了联合体的使用,比如引用3中的InputReg_t结构,这可能对实现大小端转换有帮助。通过联合体,可以将一个多字节变量作为整体访问,也可以逐个字节访问,方便进行转换。 还需要考虑不同平台的兼容性。例如,GCC的内置函数__builtin_bswap16/32/64可以用于字节交换,但可能不是所有编译器都支持。因此,宏定义可能需要条件编译来适配不同环境。最后,生成相关问题时要围绕大小端转换、宏定义的应用、检测方法以及实际应用场景展开,帮助用户深入理解。</think>在C/C++中,大小端转换可以通过宏定义实现高效处理。以下是常用的实现方法: ### 一、基于位操作的宏定义 ```c // 16位大小端转换 #define SWAP16(x) ( (((x) & 0xFF00) >> 8) | (((x) & 0x00FF) << 8) ) // 32位大小端转换 #define SWAP32(x) ( (((x) & 0xFF000000) >> 24) | \ (((x) & 0x00FF0000) >> 8) | \ (((x) & 0x0000FF00) << 8) | \ (((x) & 0x000000FF) << 24) ) // 64位大小端转换 #define SWAP64(x) ( (((x) & 0xFF00000000000000ULL) >> 56) | \ (((x) & 0x00FF000000000000ULL) >> 40) | \ (((x) & 0x0000FF0000000000ULL) >> 24) | \ (((x) & 0x000000FF00000000ULL) >> 8) | \ (((x) & 0x00000000FF000000ULL) << 8) | \ (((x) & 0x0000000000FF0000ULL) << 24) | \ (((x) & 0x000000000000FF00ULL) << 40) | \ (((x) & 0x00000000000000FFULL) << 56) ) ``` ### 二、基于联合体的实现 ```c typedef union { uint32_t value; uint8_t bytes[4]; } Converter32; #define SWAP32_UNION(x) ({ \ Converter32 c; \ c.value = (x); \ (c.bytes[0] << 24) | (c.bytes[1] << 16) | (c.bytes[2] << 8) | c.bytes[3]; \ }) ``` 此方法通过直接访问内存字节实现转换,适用于需要避免位操作符的场景[^3]。 ### 三、编译器内置函数 ```c #if defined(__GNUC__) #define BSWAP16(x) __builtin_bswap16(x) #define BSWAP32(x) __builtin_bswap32(x) #define BSWAP64(x) __builtin_bswap64(x) #endif ``` GCC/Clang编译器提供的内置函数能生成最优化的汇编指令[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值