Golang编译导致的代码错觉
文章目录
- 背景
- 分析代码
- 疑问
- 直接上汇编
- gdb调试优化后的汇编
- staticunit64s
- 查看禁止优化后的汇编
- 查看编译过程的SSA
- 生成SSA
- b对应的SSA
- c对应的SSA
- go官方文档的解释
- 对比C语言的表现
- 总结
背景
网上看到一段代码,来源是Golang 编译器优化那些事,百思不得其解。本篇文章主要是分析以下代码,一方面是知其然知其所以然,另一方面也是避免以后掉进类似的深坑。
分析代码
func main() {var a byte = (1 << uint(len(s))) / 128 // 编译器计算var b byte = (1 << uint(len(s[:]))) / 128 // 运行时计算, 溢出了var c byte = byte(int(1<<uint(len(s[:]))) / 128) // 运行时计算,用int 所以没有溢出fmt.Println(a, b, c)
}# 输出结果
a:4
b:0:
c:4
从直觉上来看a,b,c的行为应该是类似的,区别是a的len(s)可以直接获取到长度,b和c因为涉及到切片操作,需要在runtime层计算。但这个结果还是很费解。
疑问
- a在汇编的时候表现是什么样子的?为什么说是编译器计算?
- b为什么是0,512/128=4才对? 为什么是0?
- c和b的行为也不一样,只是多了个int类型转换
- 以上行为出现的理论支撑是什么
直接上汇编
# 带优化的编译
go build -o demo ./demo.go
# 输出汇编,查看demo的main函数
go tool objdump -S -s main.main demo# 结果
func main() {0x4808e0 493b6610 CMPQ SP, 0x10(R14)0x4808e4 7673 JBE 0x4809590x4808e6 55 PUSHQ BP0x4808e7 4889e5 MOVQ SP, BP0x4808ea 4883ec58 SUBQ $0x58, SPfmt.Println(a, b, c)
从最终汇编上看不出来什么东西,因为编译器做了太多优化。我们可以用gdb调试去查看生成的汇编以及对应的代码行,会更加清晰一些。
关于gdb调试go程序可以参考我之前的文章:Golang汇编之通过map地址找到value的值_golang如何获取map中的每个value-CSDN博客
gdb调试优化后的汇编
gdb demo // 开始调试
b main.main // 打断点
run // 运行程序
layout split // 查看源码和汇编
si // 根据汇编指令一次一行的查看
结果如下:
- abc在最终汇编中都是常量,编译器做过优化了
- 以上汇编看不到内部都经过哪些转换以及runtime层都做了什么
- 出现了runtime.staticunit64s? 这个是什么意思?
staticunit64s
在go源码中通过rg搜索 staticunit64s,发现在runtime/iface.go中有如下代码。
看起来是为了避免分配小整数值。这里一共有256个,通过地址偏移量来找到0-255的值。
例如上面汇编中的staticuint64s+32意思是偏移32为,打印地址结果确实是4.
可以参考: https://g4s8.wtf/posts/go-low-latency-one/
查看禁止优化后的汇编
# 编译命令: go build -gcflags="-S -N -l" demo.goFUNCDATA $2, main.main.stkobj(SB)MOVB $4, main.a+33(SP)LEAQ go:string."123456789"(SB), DXMOVQ DX, main..autotmp_3+120(SP)MOVQ $9, main..autotmp_3+128(SP)MOVQ $9, main..autotmp_4+40(SP)MOVB $0, main.b+32(SP)LEAQ go:string."123456789"(SB), DXMOVQ DX, main..autotmp_3+120(SP)MOVQ $9, main..autotmp_3+128(SP)MOVQ $9, main..autotmp_4+40(SP)MOVB $4, main.c+31(SP)
这里可以看到,变量a直接作为常量赋值了。
变量b和变量c是有计算过程的,但是有一些注意的点:
MOVQ: 用于将64位的数据从源操作数移动到目标操作数。
MOVB: 用于将8位的数据从源操作数移动到目标操作数。
从代码上也能理解,毕竟我们的a,b,c类型都是byte类型,使用MOVB是合适的。
main…autotmp_3-16(SP) 和 main.b-112(SP) 实际上指向堆栈指针 SP 的偏移位置。autotmp_xx是 runtime 临时变量,反映当前栈帧指针位置。
所以现在的疑问变成了中间计算过程的临时变量类型是什么?是int还是byte?
查看编译过程的SSA
生成SSA
GOSSAFUNC=main go build -gcflags="-N -l" ./demo.go
默认会在当前目录生成个ssa.html,我们可以浏览器打开查看。
b对应的SSA
可以看到中间计算过程的临时变量类型都是byte。那么基本上破案了,byte的类型范围是0-255,我们中间计算的1 << 9的结果是512,从这一步开始就溢出了,所以结果为0. 0/128的结果还是0,因此b=0!
c对应的SSA
可以看到在计算c的时候,因为有int转换类型,中间变量类型都是int类型,因此没有溢出。
go官方文档的解释
以上go编译器的行为理论上来说都能从go的文档中找到蛛丝马迹。我们可以搜索"go spec"来查看文档。
说实话没有找到直接关于这部分的描述,只有以下一句比较符合:
链接: https://go.dev/ref/spec#Constants
An untyped constant has a default type which is the type to which the
constant is implicitly converted in contexts where a typed value is
required, for instance, in a short variable declaration such as
i := 0 where there is no explicit type. The default type of an
untyped constant is bool, rune, int, float64, complex128,
or string respectively, depending on whether it is a boolean, rune,
integer, floating-point, complex, or string constant.
对于没有明确类型指定的常量来说,会默认转换成目标类型,也就是byte.
虽然可以这么理解,但仍然是比较反直觉的,我们可以看看c语言的表现如何。
对比C语言的表现
#include <stdio.h>
#include <string.h>
#include <stdint.h> // For uint8_t// b=4
void calculate_and_print_b_4(char* s) {unsigned char b = (1 << strlen(s)) / 128;printf("b: %u\n", b);
}// b=0
void calculate_and_print_b_0(char* s) {unsigned char b = (uint8_t)(1 << strlen(s)) / 128;printf("b: %u\n", b);
}int main() {char s[] = "123456789";calculate_and_print_b_0(s);return 0;
}
通过gcc编译,然后运行查看:
gcc test.c
./a.out
我们可以看到,如果仿照go的写法,默认输出是4,而不是0.
C语言是计算结束,然后转换成目标类型,和go的表现不一样,更符合直觉。
如果想重现b=0也简单,直接把中间的类型强制转换成uint8即可。
总结
为什么要分析这段程序,主要是觉得坑挺多的,一不小心代码就会出bug,而且还是难以排查的bug。想要避免类似的问题,要么是熟悉go的规范,要么就是尽量写一些“朴实无华”的代码。
越发明白大佬说的那句话,编程语言玩到最后玩的就是规范。虽然go的规范有点晦涩难懂,但我们可以通过遇到的问题去跟规范对照起来理解,也算是一种进步的方式吧。
尽量不要错过深究的机会,也许深究下去会得到更多呢?
end
相关文章:

Golang编译导致的代码错觉
文章目录 背景分析代码疑问 直接上汇编gdb调试优化后的汇编staticunit64s查看禁止优化后的汇编 查看编译过程的SSA生成SSAb对应的SSAc对应的SSAgo官方文档的解释 对比C语言的表现总结 背景 网上看到一段代码,来源是Golang 编译器优化那些事,百思不得其解…...

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件
SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件 H2 是一个用 Java 开发的嵌入式数据库,它的主要特性使其成为嵌入式应用程序的理想选择。H2 仅是一个类库,可以直接嵌入到应用项目中,而无需独立安装客户端和服务器端。 常用开源数…...

web前端文本大小:从入门到精通的全方位解析
web前端文本大小:从入门到精通的全方位解析 在web前端开发的世界中,文本大小的处理既是基础也是关键的一环。无论是对于初学者还是资深开发者,正确且有效地处理文本大小都显得尤为重要。本文将从四个方面、五个方面、六个方面和七个方面&…...

【报文数据流中的反压处理】
报文数据流中的反压处理 1 带存储体的反压1.1 原理图1.2 Demo 尤其是在NP芯片中,经常涉及到报文的数据流处理;为了防止数据丢失,和各模块的流水处理;因此需要到反压机制; 反压机制目前接触到的有两种:一是基…...

数据挖掘丨轻松应用RapidMiner机器学习内置数据分析案例模板详解(下篇)
RapidMiner 案例模板 RapidMiner 机器学习平台提供了一个可视化的操作界面,允许用户通过拖放的方式构建数据分析流程。RapidMiner目前内置了 13 种案例模板,这些模板是预定义的数据分析流程,可以帮助用户快速启动和执行常见的数据分析任务。 …...

时代巨兽!深度神经网络如何改变我们的世界?
深度神经网络 1、 简介1.1 定义深度神经网络1.2 深度学习的发展历程1.3 深度神经网络的应用领域 2、深度神经网络的基本原理2.1 神经元层2.1.1 神经元2.1.2 神经元层 2.2 前向传播2.3 反向传播2.4 激活函数2.4.1、作用2.4.2、常见激活函数2.4.3、选择激活函数的考虑 2.5 损失函…...

LVS+Keepalived高可用负载均衡群集
目录 一.高可用群集相关概述 1.高可用(HA)群集与普通群集的比较 普通群集 高可用群集(HA) 两者比较 2.Keepalived高可用方案 3.Keepalived的体系模块及其作用 4.Keepalived实现原理 二.LVSKeepAlived高可用负载均衡集群的…...

【MySQL】MySQL45讲-读书笔记
1、基础架构:一条SQL查询语句是如何执行的? 1.1 连接器 连接器负责跟客户端建立连接、获取权限、维持和管理连接。 mysql -h$ip -P$port -u$user -p输完命令之后,输入密码。 1.2 查询缓存 MySQL 拿到一个查询请求后,会先到查询缓…...

python:faces swap
# encoding: utf-8 # 版权所有 2024 ©涂聚文有限公司 # 许可信息查看: 两个头像图片之间换脸 # 描述: https://stackoverflow.com/questions/902761/saving-a-numpy-array-as-an-image?answertabvotes # Author : geovindu,Geovin Du 涂聚文. #…...

Android开发之音乐播放器添加排行需求
Music统计功能需求 1.记录歌曲名称与次数(歌曲播放结束算一次),根据播放次数制作一个排行列表;(开始说要记录歌手,后面debug发现这个字段没有,暂时不记录) 2.记录播放歌曲的时长,时间累加;&…...

latex 方括号编号
最近在做简历,需要列出发表的论文。 论文编号一般是采用[1]这种样式,但是找了几个简历模板里头没有直接包含这种编号样式。 我只好求助网络。 在CSDN上找了一圈,这篇博客给了一个思路:在\begin{enumerate}后面添加对应的样式即…...

Vue CLI 4与项目构建实战指南
title: Vue CLI 4与项目构建实战指南 date: 2024/6/9 updated: 2024/6/9 excerpt: 这篇文章介绍了如何使用Vue CLI优化项目构建配置,提高开发效率,涉及配置管理、项目部署策略、插件系统定制以及Webpack和TypeScript的深度集成技巧。 categories: 前端…...

深入解析Web通信 HTTP、HTTPS 和 WebSocket
在现代Web开发中,了解和掌握HTTP、HTTPS以及WebSocket协议是非常重要的。这些协议是实现Web应用程序之间通信的基石。本文将详细介绍这三种协议,包括它们的基本概念、工作原理、优缺点以及适用场景。通过深入解析它们的特点和应用,帮助读者更好地理解和使用这些协议。 一、…...

FISCO BCOS x GitLink,为国产开源技术生态注入新活力
作为中国领先的区块链底层平台之一,FISCO BCOS 自成立以来始终致力于推动国产开源区块链技术的应用和普及。近期,FISCO BCOS 将开源代码托管到CCF官方代码托管平台 GitLink (确实开源),为国产开源技术生态注入新活力。…...

Linux crontabs定时执行任务
文章目录 前言一、安装二、服务1. 启动crond服务2. 关闭crond服务3. 重启crond服务4. 设置crond开机启动5. 禁用crond开机启动6. 查看crond是否开机启动7. 重新载入配置8. 查看crond运行状态 三、使用1. 查看当前用户的crontab2. 编辑用户的crontab3. 删除用户的crontab的内容 …...

QNX简述
文章目录 前言1. QNX简介1.1 什么是QNX1.2 QNX的应用场景1.3 QNX的优点1.4 QNX的发展史1.5 QNX的商业模式 2. QNX的技术特点3. QNX和其它操作系统的比较3.1 QNX VS LINUX3.2 QNX VS FreeRTOS3.3 QNX VS 鸿蒙操作系统 4. 我的疑问4.1 微内核看起来又稳定又容易调试,为…...

[Llama3] ReAct Prompt 测试实验
ReAct 是一种 LLM 提示和结果处理方法,结合了推理、行动计划和知识源整合,使 LLM 超越其语言模型,并在预测中使用来自现实世界的信息。 ReAct 是推理和行动的结合。 介绍 ReAct 的论文表明它比思维链提示更好。与后者不同的是,Re…...

nodejs 某音douyin网页端搜索接口及x_bogus、a_bogus(包含完整源码)(2024-06-13)
前言 x_bogus或a_bogus算法大概是对数据、ua、时间戳、浏览器的几个指纹进行计算,拿到一个110位大数组,然后转字符,在头部再添加十二位随机字符,再进行魔改的base64加密。 问:抖音的x_bogus、a_bogus值有什么用&#x…...

继承深度剖析
前言 从继承开始就开始C进阶了, 这一块需要好好学习,这块知识很重要, 坑有点多,所以是面试笔试的常客。 基本概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段, 它允许程序员在保持原有…...

使用 Vue 和 Ant Design 实现抽屉效果的模块折叠功能
功能描述: 有两个模块,点击上面模块的收起按钮时,上面的模块可以折叠,下面的模块随之扩展 代码实现: 我们在 Vue 组件中定义两个模块的布局和状态管理: const scrollTableY ref(560); // 表格初始高度…...

Springboot整合SpringCache+redis简化缓存开发
使用步骤: 1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency><dependency><groupId>org.springframework.boot</groupI…...

关于EOF标识符
EOF的概念 EOF是C语言中表示文件结束的标志符号,通常被定义为-1,它用于指示已到达文件的末尾或输入流的末尾。 EOF的使用 在输入操作中,EOF常常用于判断是否到达了文件末尾或输入流末尾,以便终止读取操作。例如,在使…...

家用洗地机排行榜前十名:2024十大王牌机型精准种草
最近很多人都在问我洗地机相关的问题,不愧是改善家庭生活品质的“三神器”之一。洗地机依靠其清洁力和清洁效率吸引了越来越多的平时需要做家务人群的兴趣,为了解答大家关于洗地机的各种疑问,我把市面上目前非常火爆的洗地机型号和参数都进行…...

【Chrome插件】如何在Chrome插件开发中处理复杂数据结构的存储
最近俺在接触 Chrome 插件开发,需要把一个数据存放到浏览器的存储中。这个数据结构有点复杂,它包含一个 Map 和一个数组。我使用 chrome.storage.local API来存储这个数据,然后在另一个地方获取数据。保存数据的代码并没有报错,但…...

MySQL 保姆级教程(二):使用 MySQL 检索数据
使用 MySQL 3.2 选择数据库 使用数据库: 输入: USE 数据库名;输出: Database changed分析: 不返回任何结果,显示某种形式的通知 例如: 使用 crashcourse 数据库 use crashcourse; 3.3 了解数据库和表 列出所有的数据库: 输入: SHOW DATABASES;输出: --------…...

Sui Bridge在测试网上线并推出10万SUI激励计划
是一种为Sui设计的原生桥接协议,专门用于在Sui与其他网络之间桥接资产和数据。今天,Sui Bridge宣布在测试网上线。作为一种原生协议,Sui Bridge能够在Ethereum和Sui之间轻松且安全地转移ETH、wBTC、USDC和USDT,使其成为Sui基础设施…...

Spring系统学习 - Bean的作用域
bean作用域介绍 Spring框架提供了不同的作用域来管理Bean的生命周期和可见性,这对于控制不同类型的组件和处理并发请求尤其重要。 singleton(默认): 每个Spring IoC容器只有一个bean实例。当容器创建bean后,它会被缓存…...

贪吃蛇双人模式设计(2)
敲上瘾-CSDN博客控制台程序设置_c语言控制程序窗口大小-CSDN博客贪吃蛇小游戏_贪吃蛇小游戏csdn-CSDN博客 一、功能实现: 玩家1使用↓ → ← ↑按键来操作蛇的方向,使用右Shift键加速,右Ctrl键减速玩家2使用W A S D按键来操作蛇的方向&am…...

mysql什么时候不需要建立索引
WHERE 条件,GROUP BY,ORDER BY 里用不到的字段,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的,因为索引是会占用物理空间的。字段中存在大量重复数据,不需要创建索引,比如性…...

热门开源项目推荐:技术与地址概览
随着开源项目的不断兴起,越来越多的优秀项目涌现出来,为开发者们提供了丰富的资源和灵感。在此,我将为大家推荐几个热门的开源项目,并附上它们的开源地址,以供大家参考和了解。 1. TensorFlow 项目简介: …...