0

0

go语言是否需要编译

青灯夜游

青灯夜游

发布时间:2022-12-01 19:06:35

|

7731人浏览过

|

来源于php中文网

原创

go语言需要编译。go语言是编译型的静态语言,是一门需要编译才能运行的编程语言,也就说go语言程序在运行之前需要通过编译器生成二进制机器码(二进制的可执行文件),随后二进制文件才能在目标机器上运行。

go语言是否需要编译

本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。

Go语言是一门需要编译才能运行的编程语言,也就说代码在运行之前需要通过编译器生成二进制机器码,随后二进制文件才能在目标机器上运行。

简单来说,Go语言是编译型的静态语言(和C语言一样),所以在运行Go语言程序之前,先要将其编译成二进制的可执行文件。

如果我们想要了解Go语言的实现原理,理解它的编译过程就是一个没有办法绕过的事情。下面就来看看Go语言是怎么完成编译的。  

立即学习go语言免费学习笔记(深入)”;

预备知识

想要深入了解Go语言的编译过程,需要提前了解一下编译过程中涉及的一些术语和专业知识。这些知识其实在我们的日常工作和学习中比较难用到,但是对于理解编译的过程和原理还是非常重要的。

1) 抽象语法树

在计算机科学中,抽象语法树(Abstract Syntax Tree,AST),或简称语法树(Syntax tree),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

之所以说语法是“抽象”的,是因为这里的语法并不会表示出真实语法中出现的每个细节。比如,嵌套括号被隐含在树的结构中,并没有以节点的形式呈现。而类似于 if else 这样的条件判断语句,可以使用带有两个分支的节点来表示。

以算术表达式 1+3*(4-1)+2 为例,可以解析出的抽象语法树如下图所示:

1.gif

图:抽象语法树

抽象语法树可以应用在很多领域,比如浏览器,智能编辑器,编译器。

2) 静态单赋值

在编译器设计中,静态单赋值形式(static single assignment form,通常简写为 SSA form 或是 SSA)是中介码(IR,intermediate representation)的属性,它要求每个变量只分配一次,并且变量需要在使用之前定义。在实践中我们通常会用添加下标的方式实现每个变量只能被赋值一次的特性,这里以下面的代码举一个简单的例子:

x := 1
x := 2
y := x

从上面的描述所知,第一行赋值行为是不需要的,因为 x 在第二行被二度赋值并在第三行被使用,在 SSA 下,将会变成下列的形式:

x1 := 1
x2 := 2
y1 := x2

从使用 SSA 的中间代码我们就可以非常清晰地看出变量 y1 的值和 x1 是完全没有任何关系的,所以在机器码生成时其实就可以省略第一步,这样就能减少需要执行的指令来优化这一段代码。

根据 Wikipedia(维基百科)对 SSA 的介绍来看,在中间代码中使用 SSA 的特性能够为整个程序实现以下的优化:

  • 常数传播(constant propagation)
  • 值域传播(value range propagation)
  • 稀疏有条件的常数传播(sparse conditional constant propagation)
  • 消除无用的程式码(dead code elimination)
  • 全域数值编号(global value numbering)
  • 消除部分的冗余(partial redundancy elimination)
  • 强度折减(strength reduction)
  • 寄存器分配(register allocation)

因为 SSA 的作用的主要作用就是代码的优化,所以是编译器后端(主要负责目标代码的优化和生成)的一部分。当然,除了 SSA 之外代码编译领域还有非常多的中间代码优化方法,优化编译器生成的代码是一个非常古老并且复杂的领域,这里就不展开介绍了。

3) 指令集架构

最后要介绍的一个预备知识就是指令集架构了,指令集架构(Instruction Set Architecture,简称 ISA),又称指令集或指令集体系,是计算机体系结构中与程序设计有关的部分,包含了基本数据类型,指令集,寄存器,寻址模式,存储体系,中断,异常处理以及外部 I/O。指令集架构包含一系列的 opcode 即操作码(机器语言),以及由特定处理器执行的基本命令。

指令集架构常见种类如下:

  • 复杂指令集运算(Complex Instruction Set Computing,简称 CISC);
  • 精简指令集运算(Reduced Instruction Set Computing,简称 RISC);
  • 显式并行指令集运算(Explicitly Parallel Instruction Computing,简称 EPIC);
  • 超长指令字指令集运算(VLIW)。


不同的处理器(CPU)使用了大不相同的机器语言,所以我们的程序想要在不同的机器上运行,就需要将源代码根据架构编译成不同的机器语言。

编译原理

Go语言编译器的源代码在 cmd/compile 目录中,目录下的文件共同构成了Go语言的编译器,学过编译原理的人可能听说过编译器的前端和后端,编译器的前端一般承担着词法分析、语法分析、类型检查和中间代码生成几部分工作,而编译器后端主要负责目标代码的生成和优化,也就是将中间代码翻译成目标机器能够运行的机器码。

2.gif

Go的编译器在逻辑上可以被分成四个阶段:词法与语法分析、类型检查和 AST 转换、通用 SSA 生成和最后的机器代码生成,下面我们来分别介绍一下这四个阶段做的工作。

SiteDynamic企业网站管理系统1.7.7 标准版
SiteDynamic企业网站管理系统1.7.7 标准版

SiteDynamic企业网站管理系统采用较为成熟的ASP+ACCESS编写,是迄今为止国内较先进的ASP语言企业网站管理系统。系统为企业级网站提供一个框架,能满足企业的基本应用,同时系统开放全部源码,用户可以根据自己的需求扩展出自己需求的模块,如:单页面、新闻、产品展示、下载、友情链接、电子商务、广告、会员、在线支付、人才招聘等。整套系统的设计构造,完全考虑大中小企业类网站的功能要求,网站的后台

下载

1) 词法与语法分析

所有的编译过程其实都是从解析代码的源文件开始的,词法分析的作用就是解析源代码文件,它将文件中的字符串序列转换成 Token 序列,方便后面的处理和解析,我们一般会把执行词法分析的程序称为词法解析器(lexer)。

而语法分析的输入就是词法分析器输出的 Token 序列,这些序列会按照顺序被语法分析器进行解析,语法的解析过程就是将词法分析生成的 Token 按照语言定义好的文法(Grammar)自下而上或者自上而下的进行规约,每一个 Go 的源代码文件最终会被归纳成一个 SourceFile 结构:

SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" }

标准的 Golang 语法解析器使用的就是 LALR(1) 的文法,语法解析的结果其实就是上面介绍过的抽象语法树(AST),每一个 AST 都对应着一个单独的Go语言文件,这个抽象语法树中包括当前文件属于的包名、定义的常量、结构体和函数等。

如果在语法解析的过程中发生了任何语法错误,都会被语法解析器发现并将消息打印到标准输出上,整个编译过程也会随着错误的出现而被中止。

2) 类型检查

当拿到一组文件的抽象语法树 AST 之后,Go语言的编译器会对语法树中定义和使用的类型进行检查,类型检查分别会按照顺序对不同类型的节点进行验证,按照以下的顺序进行处理:

  • 常量、类型和函数名及类型;
  • 变量的赋值和初始化;
  • 函数和闭包的主体;
  • 哈希键值对的类型;
  • 导入函数体;
  • 外部的声明;

通过对每一棵抽象节点树的遍历,我们在每一个节点上都会对当前子树的类型进行验证保证当前节点上不会出现类型错误的问题,所有的类型错误和不匹配都会在这一个阶段被发现和暴露出来。

类型检查的阶段不止会对树状结构的节点进行验证,同时也会对一些内建的函数进行展开和改写,例如 make 关键字在这个阶段会根据子树的结构被替换成 makeslice 或者 makechan 等函数。

其实类型检查不止对类型进行了验证工作,还对 AST 进行了改写以及处理Go语言内置的关键字,所以,这一过程在整个编译流程中是非常重要的,没有这个步骤很多关键字其实就没有办法工作。【相关推荐:Go视频教程

3) 中间代码生成

当我们将源文件转换成了抽象语法树,对整个语法树的语法进行解析并进行类型检查之后,就可以认为当前文件中的代码基本上不存在无法编译或者语法错误的问题了,Go语言的编译器就会将输入的 AST 转换成中间代码。

Go语言编译器的中间代码使用了 SSA(Static Single Assignment Form) 的特性,如果我们在中间代码生成的过程中使用这种特性,就能够比较容易的分析出代码中的无用变量和片段并对代码进行优化。

在类型检查之后,就会通过一个名为 compileFunctions 的函数开始对整个Go语言项目中的全部函数进行编译,这些函数会在一个编译队列中等待几个后端工作协程的消费,这些 Goroutine 会将所有函数对应的 AST 转换成使用 SSA 特性的中间代码。

4) 机器码生成

Go语言源代码的 cmd/compile/internal 目录中包含了非常多机器码生成相关的包,不同类型的 CPU 分别使用了不同的包进行生成 amd64、arm、arm64、mips、mips64、ppc64、s390x、x86 和 wasm,也就是说Go语言能够在几乎全部常见的 CPU 指令集类型上运行。

编译器入口

Go语言的编译器入口是 src/cmd/compile/internal/gc 包中的 main.go 文件,这个 600 多行的 Main 函数就是Go语言编译器的主程序,这个函数会先获取命令行传入的参数并更新编译的选项和配置,随后就会开始运行 parseFiles 函数对输入的所有文件进行词法与语法分析得到文件对应的抽象语法树:

func Main(archInit func(*Arch)) {
    // ...
    lines := parseFiles(flag.Args())

接下来就会分九个阶段对抽象语法树进行更新和编译,就像我们在上面介绍的,整个过程会经历类型检查、SSA 中间代码生成以及机器码生成三个部分:

  • 检查常量、类型和函数的类型;
  • 处理变量的赋值;
  • 对函数的主体进行类型检查;
  • 决定如何捕获变量;
  • 检查内联函数的类型;
  • 进行逃逸分析;
  • 将闭包的主体转换成引用的捕获变量;
  • 编译顶层函数;
  • 检查外部依赖的声明;

了解了剩下的编译过程之后,我们重新回到词法和语法分析后的具体流程,在这里编译器会对生成语法树中的节点执行类型检查,除了常量、类型和函数这些顶层声明之外,它还会对变量的赋值语句、函数主体等结构进行检查:

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias) {
        xtop[i] = typecheck(n, ctxStmt)
    }
}

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias {
        xtop[i] = typecheck(n, ctxStmt)
    }
}

for i := 0; i < len(xtop); i++ {
    n := xtop[i]
    if op := n.Op; op == ODCLFUNC || op == OCLOSURE {
        typecheckslice(Curfn.Nbody.Slice(), ctxStmt)
    }
}

checkMapKeys()

for _, n := range xtop {
    if n.Op == ODCLFUNC && n.Func.Closure != nil {
        capturevars(n)
    }
}

escapes(xtop)

for _, n := range xtop {
    if n.Op == ODCLFUNC && n.Func.Closure != nil {
        transformclosure(n)
    }
}

类型检查会对传入节点的子节点进行遍历,这个过程会对 make 等关键字进行展开和重写,类型检查结束之后并没有输出新的数据结构,只是改变了语法树中的一些节点,同时这个过程的结束也意味着源代码中已经不存在语法错误和类型错误,中间代码和机器码也都可以正常的生成了。

    initssaconfig()

    peekitabs()

    for i := 0; i < len(xtop); i++ {
        n := xtop[i]
        if n.Op == ODCLFUNC {
            funccompile(n)
        }
    }

    compileFunctions()

    for i, n := range externdcl {
        if n.Op == ONAME {
            externdcl[i] = typecheck(externdcl[i], ctxExpr)
        }
    }

    checkMapKeys()
}

在主程序运行的最后,会将顶层的函数编译成中间代码并根据目标的 CPU 架构生成机器码,不过这里其实也可能会再次对外部依赖进行类型检查以验证正确性。

总结

Go语言的编译过程其实是非常有趣并且值得学习的,通过对Go语言四个编译阶段的分析和对编译器主函数的梳理,我们能够对 Golang 的实现有一些基本的理解,掌握编译的过程之后,Go语言对于我们来讲也不再那么神秘,所以学习其编译原理的过程还是非常有必要的。

更多编程相关知识,请访问:编程视频!!

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

23

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

68

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

162

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

84

2026.03.04

Swift iOS架构设计与MVVM模式实战
Swift iOS架构设计与MVVM模式实战

本专题聚焦 Swift 在 iOS 应用架构设计中的实践,系统讲解 MVVM 模式的核心思想、数据绑定机制、模块拆分策略以及组件化开发方法。内容涵盖网络层封装、状态管理、依赖注入与性能优化技巧。通过完整项目案例,帮助开发者构建结构清晰、可维护性强的 iOS 应用架构体系。

113

2026.03.03

C++高性能网络编程与Reactor模型实践
C++高性能网络编程与Reactor模型实践

本专题围绕 C++ 在高性能网络服务开发中的应用展开,深入讲解 Socket 编程、多路复用机制、Reactor 模型设计原理以及线程池协作策略。内容涵盖 epoll 实现机制、内存管理优化、连接管理策略与高并发场景下的性能调优方法。通过构建高并发网络服务器实战案例,帮助开发者掌握 C++ 在底层系统与网络通信领域的核心技术。

29

2026.03.03

Golang 测试体系与代码质量保障:工程级可靠性建设
Golang 测试体系与代码质量保障:工程级可靠性建设

Go语言测试体系与代码质量保障聚焦于构建工程级可靠性系统。本专题深入解析Go的测试工具链(如go test)、单元测试、集成测试及端到端测试实践,结合代码覆盖率分析、静态代码扫描(如go vet)和动态分析工具,建立全链路质量监控机制。通过自动化测试框架、持续集成(CI)流水线配置及代码审查规范,实现测试用例管理、缺陷追踪与质量门禁控制,确保代码健壮性与可维护性,为高可靠性工程系统提供质量保障。

79

2026.02.28

Golang 工程化架构设计:可维护与可演进系统构建
Golang 工程化架构设计:可维护与可演进系统构建

Go语言工程化架构设计专注于构建高可维护性、可演进的企业级系统。本专题深入探讨Go项目的目录结构设计、模块划分、依赖管理等核心架构原则,涵盖微服务架构、领域驱动设计(DDD)在Go中的实践应用。通过实战案例解析接口抽象、错误处理、配置管理、日志监控等关键工程化技术,帮助开发者掌握构建稳定、可扩展Go应用的最佳实践方法。

62

2026.02.28

Golang 性能分析与运行时机制:构建高性能程序
Golang 性能分析与运行时机制:构建高性能程序

Go语言以其高效的并发模型和优异的性能表现广泛应用于高并发、高性能场景。其运行时机制包括 Goroutine 调度、内存管理、垃圾回收等方面,深入理解这些机制有助于编写更高效稳定的程序。本专题将系统讲解 Golang 的性能分析工具使用、常见性能瓶颈定位及优化策略,并结合实际案例剖析 Go 程序的运行时行为,帮助开发者掌握构建高性能应用的关键技能。

51

2026.02.28

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Rust 教程
Rust 教程

共28课时 | 6.6万人学习

Kotlin 教程
Kotlin 教程

共23课时 | 4.2万人学习

Go 教程
Go 教程

共32课时 | 5.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号