字节序:大端法和小端法⭐

什么是大端法和小端法?

在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中的最小地址。

例如,假设一个类型为int的变量x的地址为0x100,即&x的值为0x100。那么x的4个字节将被存储在
存储器的0x100,0x101,0x102和0x103的位置。

字节序即为多字节对象存储在内存中的字节顺序,有两种不同的存储方案:大端法和小端法。现代的处理器大多为双端法,大小端都支持,可以配置称大端法或者小端法。

大端法  高位放前

最高有效字节在最前面的方式称为大端法,例如假设变量x类型为int型,位于地址0x100的地方,其16进制值为0x12345678,地址范围为0x100到0x103字节。

对于大端法的机器来说:

0x1000x1010x1020x103
12345678

由上图可见,地址从左向右增长,x的最高有效字节12在最前面存储。这正好和我们平时书写习惯一致,先书写最高有效字节,再依次写其余字节。

小端法 低位放前

最低有效字节在最前面的方式成为小端法,这正好和大端法相反,仍然用大端法中举的例子说明:

0x1000x1010x1020x103
78563412

由上图可见,地址依然从左向右增长,x的最低有效字节在最前面存储,与大端法相反。

如何判断我的机器是大端法还是小端法?

在《UNIX网络编程》上有一个程序可以判断一个机器是大端法还是小端法,我稍加改造了一下:典 ⭐

#include<stdio.h>
#include<stdlib.h>

int main(int argc, char **argv)
{
    union {
        short  s;
        char   c[sizeof(short)];
    } un;

    un.s = 0x0102;
    if (sizeof(short) == 2) {
    if (un.c[0] == 1 && un.c[1] == 2)
        printf("大端法\n");
    else if (un.c[0] == 2 && un.c[1] == 1)
        printf("小端法\n");
    else
        printf("不能判断\n");
    } else
    printf("sizeof(short) = %d\n", sizeof(short));

    exit(0);
}

 

大端法和小端法对程序员有什么影响?

多数程序员不必关系所使用的机器是大端法还是小端法,在大多数情况下都不会出问题,但在某些特殊情况下这有可能成为问题:

1.编写网络程序时,主机之间通过网络相互通信,不同主机之间可能采用不同的方法,而且网络字节序主机字节序也可能不同。

当小端法机器产生的数据被发送到大端法机器或者反方向发送时会发现接受程序子里面的字节成了反序的。为了避免这种情况的发

生,规定网络应用程序在将数据发送之前现将数据转换称网络字节序,在接收主机那边,主机再将网络字节序的数据转换成适合本

主机的主机字节序,从而避免了字节序异常。(网络字节序为大端法

网络编程中常用的转换函数有如下几个:

uing16_t htons(uint16_t host16bitvalue);    // 参数为16位主机字节序的值,返回值是16位网络字节序的值

uint32_t htonl(uint32_t host32bitvalue);    // 参数为32位主机字节序的值,返回值是32位网络字节序的值

uint16_t ntohs(uint16_t net16bitvalue);     // 参数为16位网络字节序的值,返回值是16位主机字节序的值

uint32_t ntohl(uint32_t net32bitvalue);     // 参数为16位网络字节序的值,返回值是16位主机字节序的值

2.当调试程序时常常需要将程序编译成汇编形式,当阅读汇编代码时数据的字节序很重要,需要根据自己的机器是大端法还是小

端法来不同对待,以免搞错字节顺序。

3.当编写规避正常类型系统的程序时,在C语言中可以使用强制类型转换来允许以一中类型引用一个对象,而这种数据类型与创建

这个对象时定义的数据类型不同,大多数应用编程都不推荐这种编码技巧,但是它们对于系统级编程来说十分有用。

参考资料:

《深入理解计算机系统》2nd

《UNIX网络编程 卷一》3th

转载于:https://www.cnblogs.com/Ph-one/p/8544884.html

<think> 我们分析用户的问题:用户希望在小端字节序环境下,通过代码 `CHAR vp_code[] = "CG"; memcpy(&ont2g.VPCode, &vp_code, strlen(vp_code));` 得到 0x4347(即大端序表示)。 但是,在小端机器上,直接使用memcpy将字符串"CG"(内存布局:'C'(0x43)在低地址,'G'(0x47)在高地址)复制到一个16位整数(uint16_t)中,得到的结果是0x4743(因为小端序是低位字节在前,高位字节在后),而不是0x4347。 用户希望得到0x4347,这实际上是大端序的表示(即高位字节0x43在前,低位字节0x47在后)。 因此,我们需要在小端机器上对字节顺序进行调整,使得复制后的结果为0x4347。 方: 1. 不能直接使用memcpy,因为memcpy是按字节顺序复制的,而小端机器会按照低地址存低位字节的方式存储。 2. 我们需要将字符串中的两个字节交换顺序,然后再复制到目标变量中。 具体步骤: 方案一:手动交换字节 - 创建一个临时数组,将原字符串的两个字符交换位置,即变成"GC" - 然后使用memcpy将交换后的字符串复制到目标变量,这样在小端机器上,目标变量就会是0x4347(因为此时低地址是'G'(0x47),高地址是'C'(0x43)?不对,这样在小端机器上会变成0x4347?我们需要重新分析) 重新分析: - 我们想要的目标值0x4347(十六进制)在内存中(小端机器)的表示应该是:低字节为0x47,高字节为0x43,所以内存布局应该是:地址0(低地址)存0x47,地址1存0x43。 - 但是原始字符串"CG"在内存中是:地址0存'C'(0x43),地址1存'G'(0x47)。如果我们直接复制,得到的内存布局是0x43,0x47,那么在小端机器上,这个16位整数就是0x4743。 - 如果我们交换两个字符,得到"GC",那么内存布局是:地址0存'G'(0x47),地址1存'C'(0x43)。这样复制到目标变量后,内存布局是0x47,0x43,那么在小端机器上,这个16位整数就是0x4347(因为低字节0x47,高字节0x43,组合起来就是0x4347)。 所以步骤: 1. 定义字符串为"CG",然后创建一个临时数组,将第一个字符第二个字符交换。 2. 将临时数组的内容复制到目标变量。 代码示例: CHAR vp_code[] = "CG"; // 创建临时数组,并交换字节 CHAR temp[3] = { vp_code[1], vp_code[0], '\0' }; // 注意:这里我们不需要字符串结尾,因为我们只复制2个字节,但为了安全可以加上 memcpy(&ont2g.VPCode, temp, 2); // 复制2个字节 这样,在小端机器上,ont2g.VPCode的值就是0x4347。 方案二:直接赋值(使用移位操作) ont2g.VPCode = (vp_code[0] << 8) | vp_code[1]; // 解释:将第一个字符左移8位作为高8位,第二个字符作为低8位,这样组合就是0x4347,无论机器是大端还是小端,这个赋值的结果都是0x4347(因为这是逻辑操作,不是内存布局)。 注意:这里假设ont2g.VPCode是uint16_t类型(2字节无符号整数),并且vp_code[0]vp_code[1]都是unsigned char类型(或者至少是非负的,不会符号扩展)。 但是,如果使用移位操作,我们需要注意数据类型的转换。因为char可能有符号,所以最好转换为unsigned char。 改进: ont2g.VPCode = ((unsigned char)vp_code[0] << 8) | (unsigned char)vp_code[1]; 这样,在小端机器上,我们直接得到了0x4347。 方案三:使用联合体(union)进行类型转换 union { uint16_t value; char bytes[2]; } converter; // 我们想要高字节是'C',低字节是'G',但是在联合体中,如果按小端序,bytes[0]对应低字节,bytes[1]对应高字节。 // 所以,如果我们这样赋值: converter.bytes[0] = vp_code[1]; // 低字节 converter.bytes[1] = vp_code[0]; // 高字节 ont2g.VPCode = converter.value; 这样,在小端机器上,converter.value的低字节是vp_code[1](即'G',0x47),高字节是vp_code[0](即'C',0x43),所以整个值就是0x4347。 但是,这种方依赖于联合体的内存布局(即小端序),所以它只在小端机器上有效。如果代码需要在大端机器上运行,则不能这样写。 总结:用户要求在小端机器上得到0x4347,我们可以选择: 推荐方案二:因为它不依赖机器的内存布局(是逻辑上的组合),而且简单直接。 因此,我们可以这样写: CHAR vp_code[] = "CG"; ont2g.VPCode = ((uint8_t)vp_code[0] << 8) | (uint8_t)vp_code[1]; 注意:这里使用uint8_t(或unsigned char)来避免符号扩展问题。 如果用户坚持使用memcpy,则用方案一(交换字节后memcpy)也可以,但不如方案二直接。 所以,我们给用户的解决方案: 在小端字节序环境下,将原始代码修改为: CHAR vp_code[] = "CG"; // 使用移位操作,避免字节序问题 ont2g.VPCode = ( (unsigned char)vp_code[0] << 8 ) | (unsigned char)vp_code[1]; 这样就能得到0x4347。 注意:如果目标变量ont2g.VPCode是16位整数,那么这个方可行。如果它是字符数组,则不能这样赋值。 但是,根据原始代码中的`memcpy(&ont2g.VPCode, ...)`,我们推断ont2g.VPCode是一个16位的整数(因为memcpy的目标地址是它的地址,并且复制2个字节)。 因此,我们按照方案二给出代码。 同时,为了确保代码的清晰,我们可以加上注释。 最终代码: CHAR vp_code[] = "CG"; // 将两个字节组合成一个16位整数,第一个字节作为高8位,第二个字节作为低8位,得到0x4347 ont2g.VPCode = ( (uint8_t)vp_code[0] << 8 ) | (uint8_t)vp_code[1]; 这样,无论在小端还是大端机器上,这个赋值都会得到0x4347(因为这是显式的位操作,不依赖于机器的内存布局)。 验证: 在小端机器上:存储到内存时,低地址是低8位(0x47),高地址是高8位(0x43),所以内存布局是0x47, 0x43。当作为16位整数读取时,按照小端序解释就是0x4347。 在大端机器上:存储时,低地址是高8位(0x43),高地址是低8位(0x47),所以内存布局是0x43,0x47,按照大端序解释也是0x4347。 所以,这个赋值操作实际上得到了一个数值0x4347,与机器的字节序无关。 因此,我们推荐使用这种方。 但是注意:如果这个16位整数需要以特定的字节序存储在内存中(例如网络传输要求大端序),那么我们需要使用htonl等函数转换,但这里用户只是希望变量ont2g.VPCode的值为0x4347(即整数17159),所以这样做是没问题的。 因此,我们给出最终答案。 </think>### 在小端字节序环境下获取0x4347的方 #### 问题分析 在小端系统中,原始代码`memcpy(&ont2g.VPCode, &vp_code, strlen(vp_code))`会导致错误的内存布局: - 字符串`"CG"`在内存中:`0x43 ('C')` → 低地址, `0x47 ('G')` → 高地址 - 小端序存储:低地址存低位字节 → `VPCode = 0x4743` - 但目标值应为`0x4347`(即大端序表示) #### 正确实现方案 ##### 方案1:显式字节交换(推荐) ```c CHAR vp_code[] = "CG"; uint16_t temp = 0; // 手动构造大端序值 temp = (vp_code[0] << 8) | vp_code[1]; // 0x43<<8 | 0x47 = 0x4347 memcpy(&ont2g.VPCode, &temp, sizeof(temp)); ``` - **工作原理**: - `vp_code[0] << 8`:将`'C'`(0x43)左移8位 → `0x4300` - `| vp_code[1]`:与`'G'`(0x47)按位或 → `0x4300 | 0x0047 = 0x4347` - **优势**:字节序明确,跨平台兼容 ##### 方案2:内存布局修正 ```c CHAR vp_code[] = "CG"; CHAR swapped[3] = {vp_code[1], vp_code[0], '\0'}; // 创建"GC" memcpy(&ont2g.VPCode, swapped, 2); // 复制交换后的字节 ``` - **小端内存布局**: ``` 低地址 高地址 +----+----+ | 47 | 43 | // 小端解释为0x4347 +----+----+ ``` #### 关键验证代码 ```c // 验证结果是否为0x4347 assert(ont2g.VPCode == 0x4347 && "Byte order correction failed"); // 检查内存布局 printf("Memory dump: %02X %02X\n", ((uint8_t*)&ont2g.VPCode)[0], ((uint8_t*)&ont2g.VPCode)[1]); // 小端系统应输出:47 43 ``` #### 各方案对比 | 方案 | 执行效率 | 可读性 | 跨平台性 | 推荐指数 | |------|----------|--------|----------|----------| | 显式字节交换 | | | | | | 内存布局修正 | | | | | | 原始错误代码 | | | ❌ | ❌ | #### 注意事项 1. **严格类型转换**: ```c // 避免符号扩展问题 uint16_t temp = ((uint8_t)vp_code[0] << 8) | (uint8_t)vp_code[1]; ``` 2. **端序无关设计**: ```c // 使用网络字节序转换函数 #include <arpa/inet.h> ont2g.VPCode = htons(0x4347); // 主机序转网络序 ``` 3. **防御性编程**: ```c // 确保字符串长度足够 if(strlen(vp_code) >= 2) { // 执行复制操作 } ``` > **根本原理**:小端系统中获取大端序数据,本质是通过**显式控制字节顺序**来覆盖处理器的默认内存布局行为。方案1直接构造目标值,方案2通过反转输入数据利用小端特性实现目标[^1]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值