字节序、位序

article/2025/9/20 20:45:36

字节序

    字节序,又称端序、尾序,英文单词为Endian,该单词来源于于乔纳森·斯威夫特的小说《格列佛游记》,小说中的小人国因为吃鸡蛋的问题而内战,战争开始是由于以下的原因:我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端。可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。老百姓们对这项命令极为反感。历史告诉我们,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位…关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也规定该派的任何人不得做官。1980年,Danny Cohen在其著名的论文"On Holy Wars and a Plea for Peace"中,为平息一场关于字节该以什么样的顺序传送的争论,而引用了该词。

        在计算机科学领域中,字节序是指存放多字节数据的字节(byte)的顺序,典型的情况是整数在内存中的存放方式和网络传输的传输顺序。有时候也可以用指位序(bit)。为了更好地理解,先看下面这段小程序,这个程序是把一个包含4位数字的字符串转换为16进制整数来存储,16进制整数的每一个字节存储一位数字字符。比如:”1234”,转换成16进制整数0x01020304

程序1清单:

#include<stdio.h>

#include<conio.h>

 

int main( )

{

    char input[4] = {0};

    int integer  = 0;

    int i;

    printf("/r/n请输入一个位数,每一位的范围是从到09/r/n");

    for(i = 0; i < 4; i++)

    {

        input[i] = getch();

        if(input[i] >'9' || input[i] <'0')

        {

             printf("input error!/r/n");

             return 1;

        }

        putch(input[i]);

    }

   getch();

    putch('/n');

 

    for(i = 0; i < 4; i++)

    {

        input[i] = input[i] -'0';

    }

 

    memcpy((void*)&integer, (void*)input, 4);

 

    printf("转换后的进制数是:0x%08x/r/n", integer);

 

    getch();

 

    return 0;

}

 

现在来分析一下这段代码

首先,定义了一个字符数组input,用来接收用户输入的4个数字字符;

第二,把4个字符数字转换成对应的数字

第三,把转换的数字复制到整型变量integer中;

最后在屏幕上打印。

如果在PPC或者ARM的机器上编译运行这个程序,那么会在屏幕上打印出结果:0x01020304,这与我们的预期一致;但是在X86的机器上则打印出的结果是:0x04030201。这个令人惊讶的结果正是字节序问题引起。下面来详细谈谈这个问题。

 

    从计算机诞生之后,就有几种不同的字节序,典型的是大端序(big endian)和小端序(little endian),当然还有不常见的混合序(middle endian)。这些用来都是描述多字节数据在内存中的存放方式的。以上面的16进制数0x01020304为例,在计算机中需要用4个字节来保存它,’01’, ’02’’03’’04’各占一个字节。按照人类的计数习惯,最左边的’01’,称之为最高有效位(MSBMost Significant Byte,它具有最高权重);最右边的’04’称之为最低有效位(LSB, Least Significant Byte,他具有最低权重)在计算机中需要用4个字节来保存它,其中’01’, ’02’’03’’04’各占一个字节。大端序的计算机保存这个数值时,按照从低地址到高地址的顺序分别保存MSBLSB四个字节,0x01020304的存储情况如下图所示:




对比我们程序1中4个字节的存放情况可以看出两者是相同的,所以打印出来的值恰好就是0x01020304


而小端序的计算机则以相反的顺序来保存它,如下图所示:

 



可以看到,在小端序的计算机中,0x01020304的保存顺序恰好与上面的程序1中相反,这就是程序1最后输出结果为0x04030201的原因。

 

    我们可以在VC集成环境中来验证上面的分析。在VS2005中调试下面的小程序,在return语句处设置断点,断住后打开内存窗口查看&i处的内容。可以直观的看到在x86(小端序)的机器上整数的存放方式。

程序2清单:

#include<stdio.h>

int main()

{

    int i = 0x01020304;

    printf("i = %#x/r/n",i);

    return 0;

}

 

 

 

 

 

再看看如何才能让程序1在大端序和小端序的机器上都能正确执行呢?一个办法就是利用预编译宏,针对不同的机器定义定义不同的数据结构。下面是一个例子:

程序3清单:

#include<stdio.h>

#include<conio.h>

 

typedefunion

{

    struct{

#ifdef BIG_ENDIAN

    char msb;

    char midb1;

    char midb2;

    char lsb;

#else

    char lsb;

    char midb2;

    char midb1;

    char msb;

#endif

    } bytes;

    int var;

} INTEGER;

 

int main()

{

    int i         = 0;

    char input[5]  = {0};

    INTEGER integer = {0};

 

    printf("/r/n请输入一个位数,每一位的范围是从到到/r/n");

    for(i = 0; i < 4; i++)

    {

        input[i] = getch();

        if(input[i] >'9' || input[i] <'0')

        {

             printf("input error!/r/n");

             return 1;

        }

        putch(input[i]);

    }

   getch();

    putch('/n');

 

    integer.bytes.msb  = input[0] -'0';

    integer.bytes.midb1 = input[1] -'0';

    integer.bytes.midb2 = input[2] -'0';

    integer.bytes.lsb  = input[3] -'0';

 

    printf("转换后的进制数是:0x%08x/r/n", integer.var);

 

    getch();

 

    return 0;

}

可以看到,这段代码定义了两套数据结构,通过BIG_ENDIAN这个宏定义来决定使用哪一套数据结构。这是个笨拙却有效的方法。下面这个例子则漂亮一些:

程序4清单

#include<stdio.h>

#include<conio.h>

#include<memory.h>

#include<winsock2.h>

 

int main( )

{

    char input[4] = {0};

    int integer  = 0;

    int i;

    printf("/r/n请输入一个位数,每一位的范围是从到到/r/n");

    for(i = 0; i < 4; i++)

    {

        input[i] = getch();

        if(input[i] >'9' || input[i] <'0')

        {

             printf("input error!/r/n");

             return 1;

        }

        putch(input[i]);

    }

   getch();

    putch('/n');

 

    for(i = 0; i < 4; i++)

    {

        input[i] = input[i] -'0';

    }

 

    memcpy((void*)&integer, (void*)input, 4);

 

    integer = ntohl(integer);

 

    printf("转换后的进制数是:0x%08x/r/n", integer);

 

    getch();

 

    return 0;

}

这个程序利用了大端序与人类书写习惯一致的特点,通过ntohl函数将整数进行转换。这个函数的功能是将网络序转换成主机序,在大端机器上它什么也不做,在小端机器上它会将输入参数的值转换成小端序的值。在windows环境下链接时别忘了将Ws2_32.lib库添加进来。

 

主机序和网络序

主机序就是指主机的端序。

网络字节序(网络序)指多字节数据在网络传输中的顺序,TCP协议规定网络序是大端序,即高字节先发送。因此大端序的机器接受到的数据可直接使用,小端序机器则需要转换后使用。BSD socket API中定义了一组转换函数,用于1632bit整数在网络序和本机字节序之间的转换。htonlhtons用于本机序转换到网络序;ntohlntohs用于网络序转换到本机序。一般来说,为了保证程序的可移植性,编写代码时,发送的数据需要使用htonlhtons转换,接收到的数据要使用ntohlntohs转换。

注意:不存在对单字节整数进行转换的函数”ntohc””htonc”!

位序

        位序,一般用于描述串行设备的传输顺序。一般说来大部分硬件都是采用小端序(先传低位),因此,对于一个字节数据,大部分机器上收发的顺序都一样,不会有问题,这就是为什么没有针对单字节数据的API接口”ntohc””htonc”。当然,也有例外,比如­I2C协议就是采用了大端序。这些细节只有在网络协议的数据链路层底端才会碰到,对一般的程序员来说很少涉及。

        但是在C语言中存在一种特殊的数据结构:位域。它的存在,使得C程序员能方便地进行位操作(比如在网络协议中经常出现1bit或者多bit的标示位,它们不是一个完整的字节)。但同时也引起一些难以察觉的问题,这些问题的根源仍然是前面提到的端序。

 

        与字节序一样,一个字节中的8bit顺序在不同端序的机器上并不相同。大端机器上从低地址到高地址顺寻分别是msb->lsb,如下图:

 

 

小端序的机器上则正好相反

 

现代计算机的最小存储单位是BYTE,无法对bit寻址,因此我们无法直接观察每个字节内部bit的顺序。但是我们仍然可以通过位域来间接观察字节内部bit顺序,以印证上面的说法。

C语言中,位域与结构体类似,其语法规定:先声明的成员位于低地址,后声明的成员位于高地址。那么下面的位域中:

typedefstruct OneByte

{

    bt0 : 1;

    bt1 : 1;

    bt2 : 1;

    bt3 : 1;

    bt4 : 1;

    bt5 : 1;

    bt6 : 1;

    bt7 : 1;

}

成员bt0就位于一个字节中最低地址bit0处,成员bt7就位于一个字节的最地址bit7处。

 

我们看看下面的程序。

#include<stdio.h>

 

typedefstruct OneByte

{

    char bt0 : 1;

    char bt1 : 1;

    char bt2 : 1;

    char bt3 : 1;

    char bt4 : 1;

    char bt5 : 1;

    char bt6 : 1;

    char bt7 : 1;

} ONE_BYTE;

 

int main()

{

    ONE_BYTE onebyte = {0};

    

    onebyte.bt7 = 1;

 

    printf("onebyte = %#x/r/n", *((unsignedchar *)&onebyte));

 

    return 0;

}

 

bt7赋值为1后,onebyte在内存中是这个样子的:

 

而在VC2005中编译运行的结果如下:

 

0x80转换成二进制是1000 0000。由于在X86(小端序)中,高地址bit7msb,因此onebyte的值是0x80了;这就证实了前面的说法。相应的如果是在大端序计算机中,bit7lsb,则onebyte的值是0x01

 

 同字节序问题一样,我们来看看怎么让上面的代码在不同端序的机器上都能打印出0x01这个值呢?前面说过,不存在对单字节整数进行转换的函数"ntohc"和"htonc",那么还是用笨一点却有效的方法:针对不同的端序定义两份位域结构

typedefstruct OneByte

{

#ifdef BIG_ENDIAN   

    char bt0: 1;

    char bt1 : 1;

    char bt2 : 1;

    char bt3 : 1;

    char bt4 : 1;

    char bt5 : 1;

    char bt6 : 1;

    char bt7 : 1;

#else

    char bt7 : 1;

    char bt6 : 1;

    char bt5 : 1;

    char bt4 : 1;

    char bt3 : 1;

    char bt2 : 1;

    char bt1 : 1;

    char bt0 : 1;

#endif

} ONE_BYTE;

其实同样的用法我们在实际的代码里面就可以找到,比如linux源码tcp.h中就有如下定义:

 

struct tcphdr {
    __u16    source;
    __u16    dest;
    __u32    seq;
    __u32    ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16    res1:4,
        doff:4,
        fin:1,
        syn:1,
        rst:1,
        psh:1,
        ack:1,
        urg:1,
        ece:1,
        cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16    doff:4,
        res1:4,
        cwr:1,
        ece:1,
        urg:1,
        ack:1,
        psh:1,
        rst:1,
        syn:1,
        fin:1;
#else
#error    "Adjust your defines"
#endif   
    __u16    window;
    __u16    check;
    __u16    urg_ptr;
};

 

最后一个问题,结束这篇拖了太久的文章。

既然不存在所谓的"ntohc"和"htonc"接口,那么网络通信程序是不是都要自己写代码来处理位序问题呢,尤其是底层驱动驱动程序?答案是大部分情况下不需要,因为大部分芯片都规定了串行发送一个字节的的顺序,因此发送方和接收方按照约定就能正确的接收一个字节了,可以理解为在芯片层面已经帮我们解决了位序不一致的问题。

对于上面提到的tcp.h的例子,因为tcp协议相对于底层芯片之间的通信已经是上层协议了,所以要自行处理位序。

 

拖了几年,这篇文章终于OVER了,汗颜!

 


http://chatgpt.dhexx.cn/article/WmJPZu1R.shtml

相关文章

字节序的详细讲解

字节序 1、字节序的特点2、字节序转换函数2.1、htonl函数 发 将主机字节序的IP地址 转换成网络字节序的IP地址2.2、ntohl函数 收 将网络字节序的IP地址3.3、htons函数 发 将主机字节序的端口 转换成 网络字节序的端口3.4、ntohs函数 收 将网络字节序的端口 转换成 主机字节序的…

理解字节序 大端字节序和小端字节序

以下内容参考了 http://www.ruanyifeng.com/blog/2016/11/byte-order.html https://blog.csdn.net/yishengzhiai005/article/details/39672529 1. 计算机硬件有两种储存数据的方式&#xff1a;大端字节序&#xff08;big endian&#xff09;和小端字节序&#xff08;little …

什么是字节序?

字节序 字节序&#xff0c;顾名思义&#xff0c;就是字节组织的顺序。我们可以将其根据其存储时从低位开始还是从高位开始分为两种&#xff0c;具体如下&#xff1a; 类型简写本质大端BE(big endian)将高序字节存储在起始地址小端LE(little endian)将低序字节存储在起始地址 …

网络字节序和主机字节序详解(附代码)

一、网络字节序和主机字节序 网络字节序和主机字节序是计算机网络中常用的两种数据存储格式。 主机字节序&#xff1a; 指的是在计算机内部存储数据时采用的字节排序方式。对于一个长为4个字节的整数&#xff0c;若采用大端字节序&#xff0c;则该整数在内存中的存储顺序是&a…

字节序

1.字节序 字节序&#xff0c;又称端序或尾序&#xff0c;指的是多字节数据在内存中的存放顺序。例如一个int型变量x占用4个字节&#xff0c;假设它的起始地址&x为0x10&#xff0c;那么x将会被存储在 0x10、0x11、0x12和0x13位置上。 在用C写的客户端和Java写的服务端的通…

字节序详细解读

概念来了&#xff01;&#xff01;&#xff01; 字节序&#xff08;Byte Order&#xff09;是指多字节数据在计算机内存中存储或者网络传输时各字节的存储顺序。 在计算机中是以字节为单位&#xff0c;每个地址对应一个字节&#xff0c;一个字节8bit。在C中&#xff0c;除了8bi…

JitPack的简单使用

JitPack的简单使用 由于工作需要,我要搭建多个项目,但是每个项目的基类,工具包,自定义的view,都是一样的,需要将这些代码复制到好几个项目里,所以萌生了一个想法,将这些基本不会改变的代码,做成一个依赖,一行代码引入项目 打开你的项目Git地址,创建发行版本 打开jitpack官网…

Gitee项目发布到JitPack

Gitee项目发布到JitPack 发布前的准备将Gitee项目发布到jitPack使用自己发布的库 发布前的准备 1.搜先注册一个Gitee账号 2.新建一个本地的android项目&#xff0c;并上传到Gitee。 (1) 根目录下build.gradle配置 dependencies {...classpath "com.android.tools.build:g…

【jitpack】android implementation 远程仓库

目录 前言接入步骤使用说明问题后记结束语 前言 做android的小伙伴都知道&#xff0c;android studio 在使用其他三方项目的时候使用gradle来管理版本,如直接使用如下就能快速把 appcopmt-v7 引入到项目中使用 implementation com.android.support:appcompat-v7:28.0.0 其实…

【JitPack】发布一个你自己的 Android 依赖库

文章目录 背景步骤新建 Android Library 项目配置 Gradle提交项目到 githubPull Request 到主分支创建 tag 并发布 release 版本JitPack 生成项目的依赖库第三方应用集成使用依赖库 JitPack 生成依赖库的 pom.xml 文件地址参考链接 背景 最近突然想开发个 Android Library&…

Android私有库gitee发布到JitPack后,编译通过但依赖不到。

JitPack编译通过后点击查看log只有这个&#xff1a; Subscription is not active right now Requested subscription: gitee.com/huile_1_0. Your subscriptions are listed in https://jitpack.io/w/user Please contact Support or repository admins if you need assistanc…

JitPack让第三方依赖更简单(第一种方法)

前面我们讲了如何将我们开发常用的工具发布到jcenter&#xff0c;然后进行依赖&#xff0c;这样有利于提高开发的效率&#xff0c;但是&#xff0c;又出现了一种新的发布方式&#xff0c;虽然现在使用的人还没有jcenter多&#xff0c;但是个人感觉未来使用的人会超过jcenter&am…

Android自定义library上传到JitPack

一、背景 最近公司不是太忙&#xff0c;闲的无聊&#xff0c;准备整理下属于自己的library库&#xff0c;想把自己平时用到的库保存起来到JitPack上&#xff0c;用的时候直接依赖添加。下面是我们把library发布到JitPack上去的记录过程。 二、项目配置 1.版本不同配置方法有些不…

maven { url “https://jitpack.io“ }

maven { url "https://jitpack.io" } 不能写在项目的build.gradle里面&#xff0c;要写在项目的setting.gradle

AndroidStudio之https://jitpack.io

前言 很多小伙伴自己写了一个库,打算开源出来,但是直接给别人发jar包或者aar包,别人使用都很不方便,而且版本更新也不方便,所以很多小伙伴把开源库放到了远程仓库里(如maven或jcenter),但是麻烦就麻烦在需要打包导出等。 而今天我要推荐一个超级方便的远程仓库:https://jitpa…

Android 发布代码到github 并且部署到 JitPack maven 仓库详细步骤

废话不多说&#xff0c;直接上步骤干货 Step1 在项目根目录的build.gradle 文件中加入 buildscript {repositories {maven { url https://jitpack.io }}dependencies {classpath com.github.dcendents:android-maven-gradle-plugin:2.1} }allprojects {repositories {maven {…

JitPack让第三方依赖更简单(第二种方法)

文章目录 前言步骤使用结语 每日一句正能量 如果青春是醺人欲醉的海风&#xff0c;那么自信就是这和风前行的路标&#xff0c;如果青春是巍峨入云的高耸&#xff0c;那么拼搏就是这山脉层层拔高的动力&#xff1b;如果青春是高歌奋进的谱曲&#xff0c;那么坚强就是这旋律奏响的…

发布开源库的踩坑经历:jitpack.io

前言 很多小伙伴自己写了一个库,打算开源出来,但是直接给别人发jar包或者aar包,别人使用都很不方便,而且版本更新也不方便,所以很多小伙伴把开源库放到了远程仓库里(如maven或jcenter),但是麻烦就麻烦在需要打包导出等。 而今天我要推荐一个超级方便的远程仓库:JitPack | Pub…

maven { url ‘https://www.jitpack.io‘ }在新版Android Studio中的位置

Android Studio升级到Arctic Fox后&#xff0c;一些配置项的位置都做了改变。 github上的很多库通过JitPack.io发布的&#xff0c;引用这些库时&#xff0c;除了在模块的build.gradle文件中加入依赖&#xff0c;还要在项目的build.gradle文件中加入maven { url ‘https://www.j…

真香警告~JitPack 开源库集成平台

文章目录 前言简介使用场景:一、准备工作1.1 生成私人令牌1.2 在JitPack中配置 Gitee 访问权限 二、准备Gitee 码云项目2.1 将本地的开发项目上传到 Gitee仓库2.2 为源码仓库创建 发行&#xff08;Release&#xff09;版本 三、开源库集成JitPack3.1 进入 JitPack&#xff0c;使…