当前位置: 首页 > news >正文

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层计算。但这个结果还是很费解。

疑问

  1. a在汇编的时候表现是什么样子的?为什么说是编译器计算?
  2. b为什么是0,512/128=4才对? 为什么是0?
  3. c和b的行为也不一样,只是多了个int类型转换
  4. 以上行为出现的理论支撑是什么

直接上汇编

# 带优化的编译
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 // 根据汇编指令一次一行的查看

结果如下:
image.png

  1. abc在最终汇编中都是常量,编译器做过优化了
  2. 以上汇编看不到内部都经过哪些转换以及runtime层都做了什么
  3. 出现了runtime.staticunit64s? 这个是什么意思?

staticunit64s

在go源码中通过rg搜索 staticunit64s,发现在runtime/iface.go中有如下代码。
看起来是为了避免分配小整数值。这里一共有256个,通过地址偏移量来找到0-255的值。
例如上面汇编中的staticuint64s+32意思是偏移32为,打印地址结果确实是4.

image.png
可以参考: 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

image.png

可以看到中间计算过程的临时变量类型都是byte。那么基本上破案了,byte的类型范围是0-255,我们中间计算的1 << 9的结果是512,从这一步开始就溢出了,所以结果为0. 0/128的结果还是0,因此b=0!

c对应的SSA

image.png

可以看到在计算c的时候,因为有int转换类型,中间变量类型都是int类型,因此没有溢出。

go官方文档的解释

以上go编译器的行为理论上来说都能从go的文档中找到蛛丝马迹。我们可以搜索"go spec"来查看文档。
说实话没有找到直接关于这部分的描述,只有以下一句比较符合:
链接: https://go.dev/ref/spec#Constants
image.png

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语言的表现总结 背景 网上看到一段代码&#xff0c;来源是Golang 编译器优化那些事&#xff0c;百思不得其解…...

SpringBoot整合H2数据库并将其打包成jar包、转换成exe文件

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

web前端文本大小:从入门到精通的全方位解析

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

【报文数据流中的反压处理】

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

数据挖掘丨轻松应用RapidMiner机器学习内置数据分析案例模板详解(下篇)

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

时代巨兽!深度神经网络如何改变我们的世界?

深度神经网络 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.高可用&#xff08;HA&#xff09;群集与普通群集的比较 普通群集 高可用群集&#xff08;HA&#xff09; 两者比较 2.Keepalived高可用方案 3.Keepalived的体系模块及其作用 4.Keepalived实现原理 二.LVSKeepAlived高可用负载均衡集群的…...

【MySQL】MySQL45讲-读书笔记

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

python:faces swap

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

Android开发之音乐播放器添加排行需求

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

latex 方括号编号

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

Vue CLI 4与项目构建实战指南

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

深入解析Web通信 HTTP、HTTPS 和 WebSocket

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

FISCO BCOS x GitLink,为国产开源技术生态注入新活力

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

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 微内核看起来又稳定又容易调试&#xff0c;为…...

[Llama3] ReAct Prompt 测试实验

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

nodejs 某音douyin网页端搜索接口及x_bogus、a_bogus(包含完整源码)(2024-06-13)

前言 x_bogus或a_bogus算法大概是对数据、ua、时间戳、浏览器的几个指纹进行计算&#xff0c;拿到一个110位大数组&#xff0c;然后转字符&#xff0c;在头部再添加十二位随机字符&#xff0c;再进行魔改的base64加密。 问&#xff1a;抖音的x_bogus、a_bogus值有什么用&#x…...

继承深度剖析

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

使用 Vue 和 Ant Design 实现抽屉效果的模块折叠功能

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

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂

蛋白质结合剂&#xff08;如抗体、抑制肽&#xff09;在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上&#xff0c;高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术&#xff0c;但这类方法普遍面临资源消耗巨大、研发周期冗长…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

ArcGIS Pro制作水平横向图例+多级标注

今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作&#xff1a;ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等&#xff08;ArcGIS出图图例8大技巧&#xff09;&#xff0c;那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制

在数字化浪潮席卷全球的今天&#xff0c;数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具&#xff0c;在大规模数据获取中发挥着关键作用。然而&#xff0c;传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时&#xff0c;常出现数据质…...

云原生玩法三问:构建自定义开发环境

云原生玩法三问&#xff1a;构建自定义开发环境 引言 临时运维一个古董项目&#xff0c;无文档&#xff0c;无环境&#xff0c;无交接人&#xff0c;俗称三无。 运行设备的环境老&#xff0c;本地环境版本高&#xff0c;ssh不过去。正好最近对 腾讯出品的云原生 cnb 感兴趣&…...

React---day11

14.4 react-redux第三方库 提供connect、thunk之类的函数 以获取一个banner数据为例子 store&#xff1a; 我们在使用异步的时候理应是要使用中间件的&#xff0c;但是configureStore 已经自动集成了 redux-thunk&#xff0c;注意action里面要返回函数 import { configureS…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

mac 安装homebrew (nvm 及git)

mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用&#xff1a; 方法一&#xff1a;使用 Homebrew 安装 Git&#xff08;推荐&#xff09; 步骤如下&#xff1a;打开终端&#xff08;Terminal.app&#xff09; 1.安装 Homebrew…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧

上周三&#xff0c;HubSpot宣布已构建与ChatGPT的深度集成&#xff0c;这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋&#xff0c;但同时也存在一些关于数据安全的担忧。 许多网络声音声称&#xff0c;这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...