go mipsgo编译器器如何解析关键

go 语言最近很火,与时俱进,我也看了看go 的语法。
看起来 go 还是不错的,有很多新的feature。 就下载了代码研究了一下。
go 的 src 目录下面存在三套编译文件:
window 平台, 所有 bat 文件plan9 平台,所有 rc 文件unix 类平台,所有bash 文件
以 unix 编译文件为例, go 的编译入口时在 src/all.bash , 这是一个bash 脚步, 这个脚步只是简单的调用了 make.bash 在脚步结束之后,调用 dist banner 输出编译的信息。
if [ ! -f make.bash ]; then
echo 'all.bash must be run from $GOROOT/src' 1&&2
OLDPATH="$PATH"
. ./make.bash "$@" --no-banner
bash run.bash --no-rebuild
PATH="$OLDPATH"
$GOTOOLDIR/dist banner
# print build info
dist 是在 make.bash 中生成的一个可执行文件,go 的所有编译都是在这个文件的控制下完成的。 个人认为这并不是一个好的设计,导致维护编译系统的成功过高,如果要修改一下编译选项,往往要修改 dist 源代码。dist 的代码在目录: /src/cmd/dist 下。
dist 这个命令行程序支持如下几个参数:
print installation banner\n" ; 打印安装的一些信息
"bootstrap
rebuild everything\n"
; 重新编译所有的 go 代码
deletes all built files\n"
; 清楚编译的 go 代码
print environment (-p: include $PATH)\n"
; 打印编译的环境
"install [dir]
install individual directory\n"
;安装某一个目录。会编译目录下代码,安装生成文件
print Go version\n"
;大约go版本信息
想要研究编译细节一定要看看这个程序的代码,后续详细分析。
make.bash, 同样是一个 bash 脚步,打开这个脚步,可以看到这个脚步主要做了如下几件事情:
根据不同的系统,以及参数进行一些初始化的工作编译生成 dist,调用dist 完成整个go 的编译. dist bootstrap用编译生成的 go_bootstrap 完成整个安装过程
很遗憾,这个script 不支持 window。 window 下调用 make.bat 去完成编译。
go 的编译系统不能很好的支持cygwin, 这是让人觉得很不爽的地方。其实整个go 的编译应该建立在Makefile 机制上,而修改go 的编译脚本,让整个源代码不依赖于dist 去完成整个编译的过程,是让go 很好的支持各种不同平台的好的入手点。
有一些环境变量和 make.bash 结合的很紧密,也控制了编译的一些选项:
GOROOT_FINAL :
这个变量用来表明 go 最终安装的路径,如果不设置,默认值为当前源代码的路径GOHOSTARCH :
设定编译 go 语音的电脑的 ARCH(架构) , 386 or amd64 or armGOARCH : 编译生成的 go 所运行的 ARCH。GOOS : 编译生成的 go 所允许的操作系统 GO_GCFLAGS: 编译 5g/6g/8g 时,额外指定的参数GO_LDFLAGS : 编译 5l/6l/8l 时, 额外指定的参数GO_CCFLAGS
编译 5c/6c/8c 时,额外指定的参数CGO_ENABLED:
是否支持 cgo,设置为1 的话,cgo 相关文件会被编译,设置为0 的话,则不会编译GO_EXTLINK_ENABLED : 是否使用Host 环境的 link。设置1的话,则会使用编译环境中带的连接器,0,在不会CC : 设置C编译器名字, gcc 还是 clang , 这个设置的是 host 环境的编译器CC_FOR_TARGET : 设置C编译器名字,这个设置的是 能够生成目标环境代码的编译器CXX_FOR_TARGET: 设置CXX编译器名字, g++ or clang++这个设置的是 能够生成目标环境代码的编译器GO_DISTFLAGS :
为 dist bootstrap 提供额外的参数.
make.bash 的一些分析:
判断 run.bash 是否存在,不存在,则提示,退出
if [ ! -f run.bash ]; then
echo 'make.bash must be run from $GOROOT/src' 1&&2
判断当前是否在cygwin 或者 mingw ,或者其他window 环境下运行。 这里吧 cygwin 简单的划到window 的环境,是不合适的
case "$(uname)" in
*MINGW* | *WIN32* | *CYGWIN*)
echo 'ERROR: Do not use make.bash to build on Windows.'
echo 'Use make.bat instead.'
如果当前是 Darwin 系统,则在编译选项中加入设定最小 macos 版本的条件
if [ "$(uname)" == "Darwin" ]; then
# golang.org/issue/5261
mflag="$mflag -mmacosx-version-min=10.6"
如果CC 没有设置,并且 gcc 在host 环境上没有, clang 确是在host 环境上存在,则设置编译器 为 clang
# if gcc does not exist and $CC is not set, try clang if available.
if [ -z "$CC" -a -z "$(type -t gcc)" -a -n "$(type -t clang)" ]; then
export CC=clang CXX=clang++
编译生成 dist 程序,判断是否编译成功
${CC:-gcc} $mflag -O2 -Wall -o cmd/dist/dist -Icmd/dist "$DEFGOROOT" cmd/dist/*.c
# -e doesn't propagate out of eval, so check success by hand.
eval $(./cmd/dist/dist env -p || echo FAIL=true)
if [ "$FAIL" = true ]; then
如果调用脚本的时候,传递如参数 "--dist--tool" ,那么意味着仅仅编译dist,那么生成disk 之后,安装dist,然后退出
if [ "$1" = "--dist-tool" ]; then
# Stop after building dist tool.
mkdir -p "$GOTOOLDIR"
if [ "$2" != "" ]; then
cp cmd/dist/dist "$2"
mv cmd/dist/dist "$GOTOOLDIR"/dist
否则,就重新编译所以的代码,编译 go_bootstrap,通过执行命令 dist bootstrap
echo "# Building compilers and Go bootstrap tool for host, $GOHOSTOS/$GOHOSTARCH."
buildall="-a"
if [ "$1" = "--no-clean" ]; then
buildall=""
./cmd/dist/dist bootstrap $buildall $GO_DISTFLAGS -v # builds go_bootstrap
用 go_bootstrap 完成整个编译过程
if [ "$GOHOSTARCH" != "$GOARCH" -o "$GOHOSTOS" != "$GOOS" ]; then
echo "# Building packages and commands for host, $GOHOSTOS/$GOHOSTARCH."
# CC_FOR_TARGET is recorded as the default compiler for the go tool. When building for the host, however,
# use the host compiler, CC, from `cmd/dist/dist env` instead.
CC=$CC GOOS=$GOHOSTOS GOARCH=$GOHOSTARCH \
"$GOTOOLDIR"/go_bootstrap install -ccflags "$GO_CCFLAGS" -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
echo "# Building packages and commands for $GOOS/$GOARCH."
CC=$CC_FOR_TARGET "$GOTOOLDIR"/go_bootstrap install $GO_FLAGS -ccflags "$GO_CCFLAGS" -gcflags "$GO_GCFLAGS" -ldflags "$GO_LDFLAGS" -v std
hello go的makefile
通常编译go程序,都是用go build,或者go install, go install实际与go build做的事情是一样的,不同的是go install最后会把可执行文件保存在$GOPATH/b...
linux下自用简单go app编译脚本
#!/bin/bash
#默认$GOROOT 已经存在,并为/usr/local/go/
#log文件目录
LOG_DIR=./log
#DAYS=15代表删除15天前的log文件
自定义go微服务build脚本
#!/bin/bash
#默认$GOROOT 已经存在,并为/usr/local/go/
app=***job
serviePath=`pwd`
GOPATH=$(dirname ...
Golang适合高并发场景的原因分析
http://blog.csdn.net/ghj1976/article/details/
典型的两个现实案例:
我们先看两个用Go做消息推送的案例实际处理能力。
go语言值得学习的开源项目推荐
谷歌官方维护了一个基于go语言的开源项目列表:
https://github.com/golang/go/wiki/Projects
其中有非常多的优秀项目值得学习,有几百行代码适合新手阅读...
Table of Contents1. 前言2. 源码目录浏览3. 客户端4. 相关链接1 前言grpc是一个通用的rpc框架,用google实现,当然也有go语言的版本。在工作中主要用到这个库,所以...
Table of Contents1. 前言2. 负载均衡3. 相关链接1 前言前面一篇文章分析了一个grpc call的大致调用流程,顺着源码走了一遍,但是grpc中有一些特性并没有进行分析,这篇b...
红黑树是一种基于
语言处理器编译器(compiler):阅读以某一种语言(源语言)编写的程序,并把该程序翻译成为一个等价的、用另一种语言(目标语言)编写的程序。编译器的重要任务之一是报告它在翻译过程中发现的源程序中的错...
作法见:http://www.aganzai.com/post-61.html
能做什么呢,写个防止别的程序弹信息的程序?...
没有更多推荐了,51CTO旗下网站
为什么 Go 不是一款好的编程语言
我们写代码可以用于许多不同的事情。假如我写了一个函数用来对一列数字求和,如果我可以用该函数对浮点数、整数以及其他任何类型进行求和那该多棒。如果这些代码包含了类型安全并且可以快速的写出用于整型相加、浮点型相加等的独立函数就更完美了。
作者:crab2313来源:oschina| 09:47
我喜欢 Go. 常用它实现各种功能(包括在写本文时的这个博客). Go 很实用,但不够好。 不是说它有多差, 只是没那么好而已。
一门编程语言, 也许会用上一辈子, 所以选择的时候要注意。
本文专注于&Go&的各种吐槽。&老生常谈的有之,鲜为人知的也有。
我用 Rust 和Haskell 作为参照 (至少, 我以为, 这俩都很不错)。 本文列出的所有问题, 都有解决方案。
那么问题来了
我们写代码可以用于许多不同的事情。假如我写了一个函数用来对一列数字求和,如果我可以用该函数对浮点数、整数以及其他任何类型进行求和那该多棒。如果这些代码包含了类型安全并且可以快速的写出用于整型相加、浮点型相加等的独立函数就更完美了。
好的解决方案:基于限制的泛型和基于参数的多态
到目前为止,我遇到的最好的泛型编程系统是rust和haskell所共用的那个。它一般被称作&被限制的类型&。在haskell中,这个系统被称作&type class&。而在Rust中,它被称作&traits&。像这样:
(Rust, version 0.11)
fn&id&T&(item:&T)&-&&T&{&&&&item&}&
id&::&t&-&&t&id&a&=&a&
在上面这个简单了例子中,我们定义了一个泛型函数id。id函数将它的参数原封不动传回来。很重要的一点是这个函数可以接受任何类型的参数,而不是某个特定的类型。在Rust和haskell中,id函数保留了它参数的类型信息,使得静态类型检查可以顺利工作,并且没有为次在运行期付出任何代价。你可以使用这个函数来写一个克隆函数。
同样,我们可以应用这种方式来定义泛型数据结构。例如:
struct&Stack&T&{&&&&items:&Vec&T&&}&
data&Stack&t&=&Stack&[t]&
跟上面一样,我们在没有运行期额外消耗的情况下得到完全的静态类型安全。
现在,如果我们想写一个通用的函数,我们必须告诉编译器&这个函数只有在它的所有参数支持这个函数中所用用到的操作时,才有意义&。举个例子,如果我们想定义一个将它的三个参数相加,并返回其和的函数,我们必须告诉编译器:这三个参数必须支持加法运算。就象这样:
fn&add3&T:Num&(a:T,&b:T,&c:T)-&T{&&&&a&+&b&+&c&}&
add3&::&Num&t&=&&t&-&&t&-&&t&-&&t&add3&a&b&c&=&a&+&b&+&c&
在上面这个例子中,我们告诉haskell的编译器:&add3这个函数的参数必须是一个Num(算数数类型)&。因为编译器知道一个Num类型的参数支持加法,所以这个函数的表达式可以通过类型检查。在haskell中,这些限制也可应用于data关键字所做的定义中。这是一个可以优雅地定义百分之百类型安全的灵活泛型函数的方式。
go的解决方案:interface{}
Go的普通类型系统的结果是,Go对通用编程的支持很差。
你可以非常轻松的写通用方程。假如你想写一个可以打印被哈希的对象的哈希值。你可以定义一个拥有静态类型安全保证的interface,像这样:
type&Hashable&interface&{&&&&Hash()&[]byte&}&&&func&printHash(item&Hashable)&{&&&&fmt.Println(item.Hash())&}&
现在,你可以提供给printHash任何Hashable的对象,你也得到静态类型检查。这很好。
但如果你想写一个通用的数据结构呢?让我们写一个简单的链表。在Go里写通用数据结构的惯用方法是:
type&LinkedList&struct&{&&&&&value&interface{}&&&&&next&&*LinkedList&}&&&func&(oldNode&*LinkedList)&prepend(value&interface{})&*LinkedList&{&&&&return&&LinkedList{value,&oldNode}&}&&&func&tail(value&interface{})&*LinkedList&{&&&&return&&LinkedList{value,&nil}&}&&&func&traverse(ll&*LinkedList)&{&&&&if&ll&==&nil&{&&&&&&&&return&&&&}&&&&&fmt.Println(ll.value)&&&&traverse(ll.next)&}&&&func&main()&{&&&&node&:=&tail(5).prepend(6).prepend(7)&&&&traverse(node)&}&
发现什么了吗?value的类型是interface{}。interface{}就是所谓的&最高类型&,意味着所有其他的类型都是interface{}的子类型。这大致相当于Java中的Object。呀!(注意:对于Go中是否有最高类型还有争议,因为Go宣称没有子类型。不管这些,保留类比的情况。
在Go里面&正确&构建通用数据结构的方法是将对象设置为最高类,然后把它们放入到数据结构中。大约在2004年,Java就是这么做的。后来人们发现这完全违背了类型系统的本意。当你有这样的数据结构时,你完全消除了一个类型系统能提供的所有好处。比如,下面这个是完全有效的代码:
node&:=&tail(5).prepend(&Hello&).prepend([]byte{1,2,3,4})&
而这在一个良好结构化的程序里完全没有意义。你可能期望的时一个整数链表,但在某个情况下,一些疲惫、靠咖啡清醒的程序员在截止日期前偶然在某处加入了一个字符串。因为Go里面的&通用数据结构不知道它们值的类型,Go的编译器也不会改正,你的程序在你失去从interface{}里面捕获时将崩溃。
相同的问题在任何通用数据结构里都存在,无论是list、map、graph、tree、queue等。
语言可扩展性
高级语言通常有复杂任务的关键字和符号简写。比如,在很多语言中,迭代一个如数组一样的数据集合中所有元素的简写:
for&(String&name&:&names)&{&...&}&
for&name&in&names:&...&
如果我们可以定义类型的相加也会很美好,那么我们可以这么做
point3 = point1 + point2
好的解决方案:把运算符视作函数
将内建的运算符和某个特别命名的函数对应起来,亦或将关键字视作特定函数的别名,这样做可以很好的解决该问题。
某些编程语言,像Python,Rust和Haskell允许我们重载运算符。我们只需要给我们自定义的类添加一个函数,自此,当我们使用某个运算符的时候(例如&+&),解释器(编译器)就会直接调用我们所添加的函数。在Python中,运算符&+&对应于__add__()函数。在Rust中,&+&运算符在Add这个trait中定义为add()函数。在Haskell中,&+&对应于Num这个type class中的(+)。
许多语言都有扩展关键字的方法,例如for-each循环。Haskell没有循环,但是像Rust,Java和Python这样的语言中都有&迭代器&这样的概念使得for-each循环可以应用于任何种类的数据集合结构。
某些人可能会用这个特性做一些很操蛋的事情,这是一个潜在的缺点。例如,某些疯狂的家伙使用&-&来代表两个向量之间的点乘。但这并不完全是运算符重载的问题。无论使用何种语言,都可以写出胡乱命名的函数。
Go的解决方案:没有
Go语言不支持操作符重载或者关键字扩展。
那么如果我们想给其他的东西(例如树,链表)实现range关键字的操作怎么办?太糟糕了。这不是语言的一部分。你这能在内建对象上使用range关键字。对于关键字make也一样,它不能给非内建数据结构申请内存和初始化。
最接近这个可以使用迭代器的关键字的方式是写一个包装函数,这个函数以目标数据结构为参数并返回一个可迭代的对象,我们通过使用这个对象在目标数据结构上迭代(译者注:参见设计模式中的迭代器模式或C++中的迭代器实现)。但是这样做可能会很慢并且复杂,而且无法保证不引入其他的bug。
对于这样一个问题,有人辩解道,&这样更容易让人理解代码,并且我看到的代码就是真正被执行的代码。&也就是说,如果Go语言允许我们扩展像range这样的东西,那么range本身的机制和实现就会变得复杂难以理解。我认为这样的说法没有什么营养,因为不管Go是否通过这种方式让其变得更简单,更易懂,人们总要进行这种在某些数据结构上进行迭代操作。如果我们不想把实现细节隐藏在range()函数里,我们就要把它隐藏在其他的工具函数里,没什么改进。所有的好代码都是易读的,大多数糟糕代码让人很难懂,很显然Go不能改变这个事实。
基础案例与失败条件
那么问题来了
当遇到递归的数据结构(如链表和树)时,我们希望找到一个途径来指出我们到达数据结构的末端。
当遇到可能会执行失败的函数或包含缺失数据片的数据结构时,我们希望找到一个途径明示我们遇到的几种失败情况。
Go 的方解决案: Nil (和多个返回值)
这回我先说 Go&的, 才好引出其他更好解决方案的讨论.
Go 支持 null 指针(nil). 每次看到新的编程语言(如:tabula rasa), 实现这个导致 bug 满天飞的功能, 我替他们可惜.
null 指针的历史,& 满满的都是 bug. 无论是历史, 还是现实, 我都看不出来, 数据存在内存地址为&0x0 的地方有什么意义. 指向 0x0 的指针通常都有特定的含义.&比如,&返回类型是指针的函数出错, 会返回 0x0&. 递归数据结构把&0x0 当作基底(base case), 如: 树结构的页节点, 或链表的结尾.&这也是 null 指针在&Go 中的用法.
然而,这样使用null指针也是不安全的。事实上,null指针是类型系统的后门,它让你能够创造某个根本不是所属类型的实例。程序员有时候会忘记某个指针的值可能是null这个事实,这是一个很常见的情况。在最好的情况下,你的程序会挂掉,而在最坏的情况下,这会产生一个可以被人利用的漏洞。编译器无法轻易地阻止这种情况的发生,因为null指针破坏了语言的类型系统。
对于Go来说,使用多重返回值这个机制,利用它第二个返回值来返回一个代表&失败&的值是一个正确也被鼓励的做法。然而,这种机制很容易被忽略或者误用,并且在表示递归数据结构的时候没有什么用用处。
好的解决方案:代数数据类型和类型安全的错误模式
我们可以使用类型系统来包装错误状况,基底,而不是试图打破类型系统。
现在我们想要构建一个表示链表的类型。我们想表示两种情况:我们是否已经到达了链表的末尾,某个链表的节点上到底有没有被存放在那里的数据。一种类型安全的方式是分别使用不同的类型来表示这些情况,最后将它们组合成一个单独的类型(使用代数数据类型)。现在我们有一个叫做Cons的类型来表示一个存放有某些数据的链表,一个叫做End的类型来表示链表的末尾。我们可以这样写:
enum&List&{&&&Cons(T,&Box),&&&End&}&let&my_list&=&Cons(1,&box&Cons(2,&box&Cons(3,&box&End)));&
data&List&t&=&End&|&Cons&t&(List&t)&let&my_list&=&Cons&1&(Cons&2&(Cons&3&End))&
每个类型都为递归操作这个数据结构的算法声明了一个基底(End)。。Rust和Haskell都不允许null指针的出现,所以我们永远都不会碰到null指针解引用所造成的bug(除非我们做一些很大胆的底层操作)。
这些代数数据结构通过像模式匹配(后面讲它)这样的技术,允许我们写出非常明了的代码。
那么,我们如何得到一个可能返回或者不返回给定类型的数据的函数,或是一个可能内部包含或者没有包含一个给定类型的数据的数据结构呢?也就是说,我们如何将错误状况(failure condition)封装到我们的类型系统中来呢?Rust使用Option,Haskell使用一个叫Maybe的类型来解决这个问题。
我们想象这样一个函数,它所作的事情是搜索一个非空字符串的数组,寻找一个以这&H&开头的字符串,返回第一个找到的这样的字符串,如果没有找到,就返回某种错误状况。在Go语言中,我们可以通过返回nil来表示&没找到&这个错误。但是在Haskell和Rust中,不使用危险的指针,我们就可以安全地完成这个任务。
fn&search'a(strings:&&'a[String])&-&Option&'a&str{&&&for&string&in&strings.iter()&{&&&&&if&string.as_slice()[0]&==&'H'&as&u8&{&&&&&&&return&Some(string.as_slice());&&&&&}&&&}&&&None&}&
search&[]&=&Nothing&search&(x:xs)&=&if&(head&x)&==&'H'&then&Just&x&else&search&xs&
我们可以返回一个包含或者没有包含一个字符串的对象来代替返回一个字符串或者null指针的做法。使用search()函数的程序员也会很清楚地知道这个函数可能会失败(因为它返回的对象的类型已经这么说了),而且程序员必须处理这两种状况,否则报错。这样我们就跟null指针解引用所造成的bug说再见了。
类型推导(Type Inference)
给程序中的每个值都指定类型, 有时看起来点过老土。 某些场合, 值的类型显而易见,如
这里的 y 明显就是整形。更复杂点的,我们甚至可以根据函数的参数类型推断出它的返回类型(反之亦然)。
出色的解决方案: 通用类型推导(General Type Inference)
Rust 和 Haskell 都基于 Hindley-Milner&类型系统, 他们都很擅长类型推导, 你可以实现像下面这样好玩的功能:
map&::&(a&-&b)&-&[a]&-&[b]&let&doubleNums&nums&=&map&(*2)&nums&doubleNums&::&Num&t&=&[t]&-&[t]&
函数 (*2) 有一个 Num 类型参数, 返回也是一个Num 类型, Haskell&由此推断&a 和 b 也是 Num 类型. 最后推断出, 该函数有若干个 Num 类型参数, 返回若个 Num 类型的值.& 这种方式比 Go 和 C++ 的简单类型推导强大多了. 有了它, 哪怕是结构复杂的程序, 就算我们不声明这么多显性类型, 编译器也能正确处理.
Go&的解决方案 : :=
Go 支持 :=&赋值操作符,&&用法如下:
foo&:=&bar()&
它的原理是: 查找 bar() 的返回类型, 然后赋给 foo. 下列代码的道理也一样:
auto&foo&=&bar();&
没什么稀奇的, 无非省去了人工查找函数 bar() 的返回类型, 在键盘上多敲几个字声明 foo 的类型那点时间而已.
不变性(Immutability)
不变性是指,在程序生成的时候,设好的值,以后不会再变。 它的优势很明显, 能减少因程序某个地方的数据结构改变,导致另一个地方出现问题的概率。
此外对程序优化也有利。
出色的解决方案: 默认使用不变性
程序员应当尽可能使用不可变数据结构。 不变性使得判断负面影响和安全性变得更简单。同时也能减少各种 Bug 。
Haskell 默认情况下, 所有的值都是不可变的。改变数据结构就意味着, 在保证正确性的前提下, 重新创建一个新的数据结构。由于&Haskell 采用的是惰性求值(lazy evaluation)和永久性数据结构(persistent data structures), 所以运行的速度还是粉快的。Rust 属于系统级编程语言。不可能使用惰性求值,也就不能像&Haskell 那样始终使用不变性。 因此,虽然 Rust 默认情况下,变量的值是不可变的。 但是,在需要的时候, 还是可以将变量设置成可变的。这样挺好,因为它迫使程序员问自己,&底需不需要将这个变量设成可变的。&这是很好的变成习惯, 对编译器优化代码也有好处。
Go 的方案: 无
Go 不支持这项功能。
控制流结构(Control Flow Structures)
控制流结构是高级编程语言有别于汇编的原因之一. 它允许我们在抽象层面, 有条理地控制程序流程. 毫无疑问, 所有高级语言都支持控制流结构, 否则, 我还说个毛啊. 可惜, 有那么几种相当不错的控制流结构 Go 不支持.
出色的解决方案:模式匹配和复合表达式
模式匹配配合数据结构或值使用的时候, 效果相当好.&简直就是 case/switch 的加强版.& 我们可以像这样对值进行匹配:
match&x&{&&&0&|&1&&=&action_1(),&&&2&..&9&=&action_2(),&&&_&&&&&&=&action_3()&};&
或者像这样解构数据结构(deconstruct data structures):
deg_kelvin&=&match&temperature&{&&&Celsius(t)&=&t&+&273.15,&&&Fahrenheit(t)&=&(t&-&32)/1.8&+&273.15&};&
上面的例子, 有时也称作复合表达式.&&C&和 Go 中的&if&和 case/switch 语句只用来控制程序流程, 不会返回值; 而 Rust 和 Haskell 的 if&和 模式匹配语句则可以. 既然有值返回, 当然也能用来赋给其他东东. 这里给出一个 if 语句的例子:
x&=&if&(y&==&&foo&)&then&1&else&2&
Go 的方案: C语言风格的无值语句( Valueless Statements)
不是我故意找&Go 的茬; 它确实有几个不错的的控制流元素, 如, 用于并行计算的 select.&可惜没有我钟爱的复合表达式和模式匹配. &Go 唯一支持赋值的语句, 是像这样的原子表达式 x := 5 或 x := foo().
嵌入式编程
给嵌入式系统编写程序与在一个有完整操作系统的计算机上编写程序有很大不同。某些语言相比而言更适合嵌入式编程的需要。
对于不少人赞成Go语言可以给机器人编程这件事我很疑惑。基于一些原因,Go语言并不适合用来为嵌入式系统编写程序。这一节并不是对Go语言的指责,Go语言并不是被设计用来编写嵌入式程序的语言。这一章节针对那些吹捧Go语言可以胜任嵌入式编程的人。
子问题 #1:堆和动态内存分配
堆是一块在运行期创建的可以存储任意数量对象的内存区域。我们将对堆的使用称作&动态内存分配&。
通常,在嵌入式系统中使用堆存储空间是不明智的。较大的内存开销和需要管理复杂的数据结构是主要的原因,尤其是当你在一块主频只有8MHz,RAM只有2KB的MCU上写程序的时候。
在实时系统(因为某一操作耗时过长就可能会跪的系统)中使用堆也是不明智的,因为对堆上空间的申请和释放所消耗的时间有很大的不确定性。举个例子,如果你的MCU正在控制一个火箭的引擎,就在这时,如果一个对栈空间的申请比平常多消耗了几百毫秒,导致对阀门的错误计时,就会发生大爆炸。
还有一些原因致使动态内存分配对嵌入式编程没有多大用。例如,许多使用堆的语言同时也拥有垃圾收集机制。垃圾收集机制经常会暂停整个程序一会儿,在堆上寻找垃圾(不再被程序使用的内存)并清除它们。这比单纯的堆空间申请更加具有不确定性。
好的解决方案:让动态内存分配成为可选项
Rust语言的标准库中有很多特性依赖于堆。然而,Rust语言的编译器支持完全关闭这些有关堆的语言特性,并且能够静态地确保这些特性在程序中不被使用。写出完全不使用堆的Rust程序是完全可行的。
Go语言的解决方案:没有
Go语言严重依赖于对堆的运用。没有可行的方式让Go程序完全不使用堆。这不是Go语言的问题。这在Go语言的目的应用领域完全没有问题。
Go并不是一门实时的语言,通常我们不能担保合理复杂的Go程序的执行时间。这可能有点费解,我来解释一下:Go相对而言很快,但不是实时的,这两个概念非常不同。执行速度快对嵌入式程序来说很重要,但是真正重要的是能否担保某些操作的最大执行时间,而这恰恰是Go不能预测的。这个问题有很大一部分是Go语言对于堆空间和垃圾收集机制的使用造成的。
Haskell也有相似的问题。Haskell同样由于对堆的大量使用而不能胜任嵌入式或者实时编程。然而,我没有看见任何人推荐使用Haskell对机器人编程,所以我不用指出这点。
子问题#2:不安全的底层代码
当我们写嵌入式程序的时候,写一些不安全的代码(不安全的的类型转换,或者指针运算)是不可避免的。在C或C++中,做这样的事情是很简单的。如果我需要向0x1234这个内存地址写入0xff这个值来点亮一个LED,我可以这样写:
*(uint8_t*)0x1234 = 0xFF;
这样做很危险,只有当我们写非常底层的系统代码的时候才有意义。这就是Go和Haskell没有简单的方式来做这样的事的原因:它们不是系统编程语言。
好的解决方案:将不安全的代码孤立开来
注重安全和系统编程的Rust语言有一个非常好的解决方案:unsafe代码块。unsafe代码块是一种显示地将不安全的代码分离出来的方式。我们通过如下的方式在Rust语言中向0x1234地址写入0xff:
unsafe{&&&&*(0x1234&as&*mut&u8)&=&0xFF;&&}&
如果我们在unsafe代码块外面做这样的事情,Rust的编译器会警告我们。这样允许我们在满足嵌入式编程需要的同时,保持了程序的安全和稳定。
Go的解决方案:没有
Go语言本来就不是为了做这样的事而出现的,所以没有任何内建的支持。
现在你可能会说,&那么为什么你说Go语言不好?这只是一大堆你的抱怨而已。你可以针对任何语言发牢骚。&没有语言是完美的,这很正确。然而,我希望我的抱怨能在某种程度上说明:
Go语言本质上没有干了什么新的事情
Go语言本身没有被良好地设计
Go语言是其他现代编程语言的退化
原文链接:http://www.oschina.net/translate/why-go-is-not-good
英文原文:
【编辑推荐】
【责任编辑: TEL:(010)】
大家都在看猜你喜欢
热点头条关注头条热点
24H热文一周话题本月最赞
讲师:738429人学习过
讲师:30976人学习过
讲师:16630人学习过
精选博文论坛热帖下载排行
本书内容包括:
● 框架的总览:SQL Server 2005的功能是如何集成在一起的,以及这些功能对于用户的意义。
● 安全性管理、策略...
订阅51CTO邮刊}

我要回帖

更多关于 c语言编译器 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信