关于golang:教你用golang判断大小端字节序

12次阅读

共计 4544 个字符,预计需要花费 12 分钟才能阅读完成。

前言

哈喽,大家好,我是 asong 。明天想与大家聊一聊计算机硬件中的两种贮存数据的形式:大端字节序(big endian)、小端字节序(little endian)。诚实说,我第一次晓得这个概念还是在学习单片机的时候,不过过后学完就忘了,真正长忘性是在面试的时候,面试官问我:你能用C 语言写段代码判断机器的字节序吗?你肯定好奇为什么要用 C 语言写,傻瓜,这是我大学的时候面试嵌入式岗位呀。扯远啦,其实过后的我是懵逼的,早就忘了什么大端、小端了,所以遗憾的错过嵌入式行业,进入了互联网行业(手动狗头)。

本文的所有代码曾经上传github:https://github.com/asong2020/…;欢送star

因为微信公众号改版,文章推送会乱序,为了第一工夫收到 asong 的音讯,请读者敌人动动小手,在公众号主页右上角设置里 加个星标。感激大家~。

为什么有大小端之分

我始终都不了解,为什么要有大小端辨别,尤其是小端,总是会遗记,因为他不合乎人类的思维习惯,但存在即为正当,存在就有他存在的价值。这里有一个比拟正当的解释:计算机中电路优先解决低位字节,效率比拟高,因为计算机都是从低位开始的,所以计算机外部解决都是小端字节序。然而咱们平时读写数值的办法,习惯用大端字节序,所以除了计算机的外部,其余场景大都是大端字节序,比方:网络传输和文件贮存时都是用的大端字节序。

所以大小端问题很可能与硬件或者软件的创造者们无关,理论在计算机工业利用上,不同的操作系统和不同的芯片类型都有所不同。不同的零碎设计不同,所以咱们也没必要深究为什么要有这个辨别,只须要晓得他们的原理就好了。

什么是大端、小端

大端模式:高位字节排放在内存的低地址端,低位字节排放在内存的高地址端;

小端模式:低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;

这么说也有点含糊,还是配个图来看更清晰:

咱们来看一看数值 0x1A2B3C4D 在大端与小端的表现形式,这里咱们假如地址是从 0x4000 开始:

上图所示:大端和小端的字节序最小单位是 1 字节 (8bit),大端字节序就和咱们平时的写法程序一样,从低地址到高地址写入0x1A2B3C4D,而小端字节序就是咱们平时的写法反过来,因为字节序最小单位为1 字节,所以从低地址到高地址写入0x4D3C2B1A

因为大端、小端很容易混记,所以分享一个我本人记忆的小技巧:

大端:高下高下,也就是高位字节排放在内存低地址端,高地址端存在低位字节;

小端:高高低低;也就是高位字节排放在内存的高地址端,低位字节排放在内存的低地址端;

如何应用 Go 辨别大小端

计算机解决字节序的时候,不晓得什么是高位字节,什么是低位字节。它只晓得按程序读取字节,先读取第一个字节,再读取第二个字节,所以说我就能够依据这个个性来读判断大小端。

在应用 Go 语言实现之前,还是想再用 C 语言实现一遍,因为这是我毕生的痛,毕竟在面试的时候没写进去。

能够利用 C 语言中 union 各字段共享内存的个性,union 型数据所占的空间等于其最大的成员所占的空间,对 union 型的成员的存取都是绝对于 该联合体基地址的偏移量为 0 处开始 ,也就是联合体的拜访不管对哪个变量的存取都是 从 union 的首地址地位开始 联结是一个在同一个存储空间里存储不同类型数据的数据类型。这些存储区的地址都是一样的,联结里不同存储区的内存是重叠的,批改了任何一个其余的会受影响。所以咱们可写出代码如下:

#include "stdio.h"


// big_endian: 1 
// little_endian: 2
int IsLittleEndian() {
    union {
        short value;
        char array[2];
    } u;
    u.value = 0x0102;
    if (u.array[0] == 1 && u.array[1] == 2){return 1;}else if (u.array[0] == 2 && u.array[1] == 1){return 2;}
    return -1;
}

int main() {
    
    int res;
    res = IsLittleEndian();
    printf("result is %d\n",res);
    if (res == 1) {printf("it is big endian");
    }
    if (res == 2){printf("it is little endian");
    }
    return 0;
}

// 运行后果(不同零碎运行后果会有不同)
result is 2
it is little endian% 

当初咱们来思考一下,怎么用 Go 语言验证大小端,Go中是没有 union 这个关键字,那就要另辟蹊径,换一个办法来实现啦,咱们能够通过将 int32 类型 (4 字节) 强制转换成 byte 类型 (单字节),判断起始存储地位内容来实现,因为Go 不反对强制类型转换,咱们能够借助 unsafe 包达到咱们的要求,写出代码如下:

package main

import (
    "fmt"
    "unsafe"
)

func IsLittleEndian()  bool{
    var value int32 = 1 // 占 4byte 转换成 16 进制 0x00 00 00 01 
  // 大端(16 进制):00 00 00 01
  // 小端(16 进制):01 00 00 00
    pointer := unsafe.Pointer(&value)
    pb := (*byte)(pointer)
    if *pb != 1{return false}
    return true
}

func main()  {fmt.Println(IsLittleEndian())
}
// 运行后果:ture

大小端字节序转化

这里大家可能会有纳闷,为什么要有大小端转化,这是因为在波及到网络传输、文件存储时,因为不同零碎的大小端字节序不同,这是就须要大小端转化,能力保障读取到的数据是正确的。我在大学时做 armdsp通信的时候,就遇到个大小端转换的问题,因为 arm 是小端,dsp是大端,所以在不理解这个知识点的时候,通信的数据就是乱的,导致我调试了良久。

大小端的转换其实还算比较简单,通过位操作就能够实现,这里咱们用 uint32 类型作为例子:

func SwapEndianUin32(val uint32)  uint32{return (val & 0xff000000) >> 24 | (val & 0x00ff0000) >> 8 |
        (val & 0x0000ff00) << 8 | (val & 0x000000ff) <<24
}

是的,你没看错,就是这么简略,这里也很简略,就不细讲了。

其实 go 官网库 encoding/binary 中曾经提供了大小端应用的库,咱们要想进行大小端转换,齐全能够应用官网库,没必要本人造轮子。咱们看一下这个库怎么应用:

// use encoding/binary
// bigEndian littleEndian
func BigEndianAndLittleEndianByLibrary()  {
    var value uint32 = 10
    by := make([]byte,4)
    binary.BigEndian.PutUint32(by,value)
    fmt.Println("转换成大端后",by)
    fmt.Println("应用大端字节序输入后果:",binary.BigEndian.Uint32(by))
    little := binary.LittleEndian.Uint32(by)
    fmt.Println("大端字节序应用小端输入后果:",little)
}
// 后果:转换成大端后  [0 0 0 10]
应用大端字节序输入后果:10
大端字节序应用小端输入后果:167772160

grpc中对大端的利用

大家对 gRPC 肯定很相熟,最近在看 gRPC 源码时,看到 gRPC 封装 message 时,在封装 header 时,特意指定了应用大端字节序,源码如下:

// msgHeader returns a 5-byte header for the message being transmitted and the
// payload, which is compData if non-nil or data otherwise.
func msgHeader(data, compData []byte) (hdr []byte, payload []byte) {hdr = make([]byte, headerLen)
    if compData != nil {hdr[0] = byte(compressionMade)
        data = compData
    } else {hdr[0] = byte(compressionNone)
    }

    // Write length of payload into buf
    binary.BigEndian.PutUint32(hdr[payloadLen:], uint32(len(data)))
    return hdr, data
}

结尾

在本文的最初咱们再来做一下总结:

  • 大端小端是不同的字节顺序存储形式,统称为 字节序
  • 大端:是指数据的高字节位 保留在 内存的低地址中,而数据的低字节位 保留在 内存的高地址中。这样的存储模式有点儿相似于把数据当作字符串程序解决:地址由小向大减少,而数据从高位往低位放。和咱们”从左到右“浏览习惯统一。
  • 小端:是指数据的高字节位 保留在 内存的高地址中,而数据的低字节位 保留在 内存的低地址中。这种存储模式将地址的高下和数据位权无效地联合起来,高地址局部权值高,低地址局部权值低,和咱们的逻辑办法统一
  • 辨别:计算机解决字节序的时候,不晓得什么是高位字节,什么是低位字节。它只晓得按程序读区字节,先读取第一个字节,再读取第二个字节,所以说我就能够依据这个个性来读判断大小端。
  • 转换:通过位操作就能够实现,具体能够应用规范库encoding/binary

本文的所有代码曾经上传github:https://github.com/asong2020/…;欢送star

好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者继续创作更多优质内容的能源!

创立了一个 Golang 学习交换群,欢送各位大佬们踊跃入群,咱们一起学习交换。入群形式:加我 vx 拉你入群,或者公众号获取入群二维码

结尾给大家发一个小福利吧,最近我在看 [微服务架构设计模式] 这一本书,讲的很好,本人也收集了一本 PDF,有须要的小伙能够到自行下载。获取形式:关注公众号:[Golang 梦工厂],后盾回复:[微服务],即可获取。

我翻译了一份 GIN 中文文档,会定期进行保护,有须要的小伙伴后盾回复 [gin] 即可下载。

翻译了一份 Machinery 中文文档,会定期进行保护,有须要的小伙伴们后盾回复 [machinery] 即可获取。

我是 asong,一名普普通通的程序猿,让咱们一起缓缓变强吧。欢送各位的关注,咱们下期见~~~

举荐往期文章:

  • Go 看源码必会常识之 unsafe 包
  • 源码分析 panic 与 recover,看不懂你打我好了!
  • 详解并发编程根底之原子操作(atomic 包)
  • 详解 defer 实现机制
  • 空构造体引发的大型打脸现场
  • Leaf—Segment 分布式 ID 生成零碎(Golang 实现版本)
  • 十张动图带你搞懂排序算法(附 go 实现代码)
  • go 参数传递类型
  • 手把手教姐姐写音讯队列
  • 常见面试题之缓存雪崩、缓存穿透、缓存击穿
  • 详解 Context 包,看这一篇就够了!!!
  • 高并发零碎的限流策略:漏桶和令牌桶(附源码分析)
正文完
 0