什么是平台依赖结构

本文作者分享了在UNIX到Linux系统移植过程中遇到的平台依赖结构问题,特别是字节序在网络编程中的重要性。介绍了大端序与小端序的概念,并通过实例解释了字节序转换函数如htons和htonl的用途,强调了编写平台无关代码的重要性。

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

 作者:高玉涵
 时间:2022.2.11 14:30 (福虎年十一)
 博客:blog.youkuaiyun.com/cg_i
 平台:HP-UX、LINUX

序言

 接触 UNIX 程序移值到 Linux 项目,已 11 个多月了,经过大家的努力,初步实现了目标,但接下来的日子,才是完成总体目标的关键,这期间确实有很多感触。作为一个一直都很热爱“底层”技术的开发人员,我即庆幸能接触这个项目,也感慨于有如此多的未知、茫然,无处下手,不过这些状况随着项目的发展,已逐步得到改善。

 项目移植中存在平台依赖的结构。以往只在书中看到的东西,现在确亲身经历,真正印证了“纸上得来终觉浅,唯有实践出真知”这句话。在这里,当基于 RISC 结构的 UNIX 平台移植到基于 X86 结构的平台上时,应用程序代码对字节序的依赖,例如,为了计算或数据操作而使用了字节交换的应用程序。移植使用了字节交换逻辑的代码到 Intel 机器(Little Endian,小端体系结构)上时,需要按照小端(Little Endian)模式修改代码。在没有正确修改字节交换逻辑的情况下,调试问题就变得非常麻烦,因为很难找到数据在哪里出了问题,这主要是因为问题的根源通常是发生问题的代码之前很远的地方。

 我移植的项目,是核心业务系统,处理起来较为棘手,一度给移植过程带来很大麻烦,经过对代码移植前后的区别制定了标准,并做相应修改。

 我在项目移植过程中,每遇到一个问题,基本上都会记录下来,想着给需要的人提供参考,虽然您不一定能遇到,但是移植思想和方法都是相通的,只是具体的内容不同而已。毕竟我是乐于分享的。

为初学者解释什么是平台依赖结构

 我曾有一段时间痴迷于学习网络编程,我并不是特别聪明或理解力特别强的人,所以花费大量时间学习属于程序员必修课的操作系统和算法。对我而言,学习计算机理论是不小的负担。为了能够通俗易懂的说明,同时符合我的水平,我将摘出网络编程中使用的部分技术来举例,它们都很小巧,理解起来不会成为你的负担。至于为何要用网络技术来举例,是因为网络互通就是解决平台依赖最经典的成功案例。

网络字节与地址变换

 不同 CPU 中,4 字节整数型值 1 在内在空间的保存方式是不同的。4 字节整数型值 1 可用 2 进制表示如下。

00000000 00000000 00000000 00000001

 有些 CPU 以这种顺序保存到内在,另外一些 CPU 则以倒序保存。

00000001 00000000 00000000 00000000

 若不考虑这些收发数据则会发生问题,因为保存顺序的不同意味着对接收数据的解析顺序也不同。

字节序(Order)与网络字节序

 CPU 向内在保存数据的方式有 2 种,这意味着 CPU 解析数据的方式也分为 2 种。

  • 大端序(Big Endian):高位字节存放到低位地址。
  • 小端序(Little Endian):高位字节存放到高位地址。

 仅凭描述很难解释清楚,下面通过示例进行说明。假设在 0x20 号开始的地址中保存 4 字节 int 类型数 0x12345678 。大端序 CPU 保存方式如图 1-1 所示。

在这里插入图片描述

图 1-1 大端序字节表示

 整数 0x12345678 中,0x12 是高位字节,0x78 是低位字节。因此,大端序中先保存最高位字节 0x12(最高位字节 0x12 存放到低位地址)。小端序保存方式如图 1-2 所示。

在这里插入图片描述

图 1-2 小端序字节表示

 先保存的是最低字节 0x78 。从以上分析可以看出,每种 CPU 的数据保存方式均不同。因此,代表 CPU 数据保存方式的主机字节序(Host Byte Order)在不同 CPU 中也各不相同。目前主流的 Intel 系列 CPU 以小端方式保存数据。接下来分析 2 台字节序不同的计算机之间数据传递过程中可能出现的问题,如图 1-3 所示。

在这里插入图片描述

图 1-3 字节序问题

 0x12 和 0x34 构成的大端序系统值与 0x34 和 0x12 构成的小端序系统值相同。换言之,只有改变数据保存顺序才能被识别为同一值。图 1-3 中,大端序系统传输数据 0x1234 时未考虑字节序问题,而直接以 0x12、0x34 的顺序发送。结果接收端以小端序保存数据,因此小端序接收的数据变成 0x3412,而非 0x1234 。正因如此,在通过网络传输数据时约定统一方式,这种约定称为网络字节序(Network Byte Order),非常简单——统一为大端序。

 即,先把数据数组转化成大端序格式再进行网络传输。因此,所有计算机接收数据时应识别该数据是网络字节序格式,小端序系统传输时应转化为大端序排列方式。

字节转换(Endian Conversions)

 接下来介绍帮助转换字节序的函数。

  • unsigned short htons(unsigned short);
  • unsigned short ntohs(unsigned short);
  • unsigned long htonl(unsigned long);
  • unsigned long ntohl(unsigned long);

 通过函数名应该能掌握其功能,只需了解以下细节。

  • htons 中的 h 代表主机(host)字节序。
  • ntohs 中的 n 代表网络(network)字节序。

 另外,s 指的是 short,l 指的是 long(Linux 中 long 类型占用 4 个字节,这很关键)。因此,htons 是 h、to、n、s 的组合,也可以解释为“把 short 型数据从主机字节序转化为网络字节序“。

 再举个例子,ntohs 可以解释为“把 short 型数据从主机字节序转化为网络字节序“。

 通常,以 s 作为后缀的函数中,s 代表 2 个字节 short,因此用于端口号转换;以 l 作为后缀的函数中,l 代表 4 个字节,因此用于 IP 地址转换。另外,有些读者可能有如下凝问:

“我的系统是大端序的,就不需要转换字节序了吧?“

 这么说也不能算错。但我认为,有必要编写与大端序无关的统一代码。这样,即使在大端序系统中,最好也经过主机字节序转换为网络字节序的过程。当然,此时主机字节序与网络字节序相同,不会有任何变化。

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
	unsigned short host_port = 0x1234;
	unsigned short net_port;
	unsigned long host_addr = 0x12345678;
	unsigned long net_addr;
	
	net_port = htons(host_port);
	net_addr = htonl(host_addr);
	
	printf("Host ordered port: %#x \n", host_port);
	printf("Network ordered port: %#x \n", net_port);
	printf("Host ordered address: %#lx \n", host_addr);
	printf("Network ordered address: %#lx \n", net_addr);
	return 0;
}

 运行结果:endian_conv.c

gcc endian_conv.c -o conv
./conv
Host ordered port: 0x1234 
Network ordered port: 0x3412 
Host ordered address: 0x12345678 
Network ordered address: 0x78563412 

 这就是在小端序 CPU 中运行的结果。如果在大端序 CPU 中运行,则变量不会改变大部分朋友都会得到类式的运行结果,因为 Intel 和 AMD 系列的 CPU 都采用小端序标准。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值