用Go语言写一个简单的Gitweb Server

Git的发行版中带了一个叫gitweb的工具,可以以web的方式来查看git仓库。但是gitweb本身是一个CGI脚本,需要一个支持CGI的Web服务器来运行它。这篇文章介绍如何用Go语言写一个简单的服务器,用来运行gitweb。 ...

March 7, 2018

看Python和Go语言对Unicode的支持

Unicode是一个支持世界上绝大多数语言的字符系统。支持Unicode的编程语言更容易实现全球化(internationalization)和进行本地化(localization)。那么支持Unicode需要编程语言具有什么样的特性呢,让我们来以Python 3和Go语言为例子做个探究。 ...

December 9, 2017

LZW压缩算法Go语言简易实现

Catfish3在Cpluscplus网站上写了一篇文章,如何用C++来实现Lempel-Ziv-Welch压缩算法。于是照猫画虎,用Go实现了一个简易的版本,只是确保了功能而且,没有考虑如何变得更好更优雅。 其实Go的runtime包含了一个更加成熟的LZW算法包"compress/lzw",采用了变长Code Bits。跟"compress/lzw"相比,下面的实现采用了16位定长的Code Bits,所以有可能压缩结果比源文件体积更大-_-‘。 ...

December 30, 2015

Go语言:import strings转化为汇编是什么样的?

回答题目中所提的这个问题,我们需要一点小小的背景知识。Go语言的汇编格式是从Plan9继承过来的,与我们常见的nasm和yasm都不相同。 先写一个简单的Go程序x.go: 上面的程序除了导入”strings”包以外什么都不干。 使用Go1.5的编译命令来输出反汇编: `回答题目中所提的这个问题,我们需要一点小小的背景知识。Go语言的汇编格式是从Plan9继承过来的,与我们常见的nasm和yasm都不相同。 先写一个简单的Go程序x.go: 上面的程序除了导入”strings”包以外什么都不干。 使用Go1.5的编译命令来输出反汇编:` 注意我的编译环境是windows_amd64. 几个选项的含义如下: -S 反汇编 -v 输出更多内容 -N 禁止优化 -l 禁止内联 删掉输出内容不相关的部分,并适当调整了顺序,可以得到: 编译每个.go文件会生成相应的.o文件(object文件),每个object文件都有一个初始化函数init,因为strings包被import了,所以x.go的init函数中调用了strings.init: import "strings"这条语句中,"strings"是作为一个字符串,所以它必须在全局数据段定义: 在SB,即Static Base定义了一个7字节的字符串$"string",并给这个字符串取名go.string."strings"。接下来的 帮助申明这个数据是全局的,大小被调整为8个字节,因为要与am64的8字节字长对齐。 接下来又定义了go.string.hdr."strings"(SB),共16个字节,前面8个字节用来保存$go.string."strings"(SB),也就是"string"地址;后面8个字节空着不用,很可能是链接的时候重定向用的。 最后定义了go.importpath.strings.(SB),也是16个字节,前面8个字节同样用来保存"string"地址,后面8个字节保存了"string"的长度。 估计这条数据告诉链接器要导入哪个包。 (完)

October 10, 2015

关于Go语言的更新两则:Go1.5和Godebug

其实已经不能算新闻了,只是觉得有点重要,所以提一下。 Go 1.5发布了 Go 1.5是一个里程碑的版本,最重要的改动是No More C。Go编译器和运行库用Go改写过了,现在Go可以自己编译自己。这意味着Go抓着自己的头发飞起来了,任何严肃的系统编成语言都应该做到这一点。但是个人尝试过后感觉编译速度比以前慢点了,不知道是否是心理作用。 另一个改动是工具链的改动,以前分散的各种工具,比如6a, 8a, 6g, 8g等全都集合到go tool这个子命令下。编译命令go tool compile, 链接命令go tool link, 汇编命令go tool asm。 通过修改环境变量$GOARCH和$GOOS可以控制编译选项,下面的命令编译hello.go生成Linux下的386二进制文件: godebug Go一直缺一个好用的debugger,如果使用gccgo那么可以利用GDB作为debug工具。gccgo以外,基本没啥顺手的的工具,只有一个delve。 godebug的出现可谓带来了一缕新风。godebug由mailgnu推出,这是作者的对其的介绍。 执行go get -u github.com/mailgun/godebug安装godebug。 传统的debugger通过底层系统接口来进行调试,godebug另辟蹊径,它的实现原理是在编译Go代码之前在代码之中插入调试信息。godebug的断点式这样插入的,在代码中增加一行: 我们写一个foobar.go文件,内容如下: 执行godebug run foobar.go会编译运行这个文件,并在断点处停下,输入help可以查看帮助菜单: 可以看到目前godebug可以单步执行代码并且打印表达式的值,基本满足日常的调试的需求了。 Cloudflare的这篇文章以RRDNS为例,介绍godebug的使用。

September 16, 2015

Go语言的汇编浅析

Go语言的汇编是从Plan9继承过来的。根据Go的文档,与每种体系结构标准的汇编语言不同,Go语言的汇编是一个半抽象(semi-abstract)的汇编语言。其目的是通过抽象,得到一套“恰好”满足Go编译器使用的汇编语言。总而言之,Go汇编不是为了大而全,而是为了给Go编译器提供一个抽象层,使之能工作在不同体系架构。 下面举一个例子看看Go汇编到底是什么样的。我们写一个div.go,内容如下: 使用Go1.5在64位架构上编译,执行命令: 屏幕输出: TEXT "".div(SB), $0-24 在TEXT段定义函数div;div的前缀"".表示div的命名空间,这里为空;SB是Static Base的缩写,也就是TEXT段的起点;$0表示局部变量字节数总和,0表示不存在局部变量;24是传入传出参数的字节数总和,传入两个int,传出一个int,每个int为8个字节,共24字节。 NOP 估计是预留给链接器使用的。 FUNCDATA 为垃圾收集器所用的指令,此外常见的还有PCDATA。 MOVQ "".a+8(FP), AX 将传入参数a的值从内存拷贝到寄存器AX;这里的AX其实是64位寄存器RAX,Go汇编不是通过EAX或者RAX来区分32位或者64位,而是通过指令名称MOVL和MOVQ区分; FP是Frame Pointer的缩写;"".a+8(FP)的意思FP偏移8字节名字为a的变量,这里a同样带有命名空间修饰"".。 MOVQ "".b+16(FP), BP 将传入参数b的值从内存拷贝到寄存器BP。 CMPQ BP, $-1 这是一个快速判断,任何数除以-1相当于取自身的相反数。 JEQ $1, 27 如果上一条指令结果为1,则跳转到27行的NEGQ AX将其取反,并通过MOVQ AX, "".~r2+24(FP)将结果保存到函数返回值。 CQO 将AX双精度符号扩展,即用DX:AX寄存器组合来表示原先存在AX的值,为除法作准备。 IDIVQ BP 把DX:AX除以BP。 MOVQ AX, "".~r2+24(FP)执行完除法指令以后,商保存在AX,这条指令把商保存在FP偏移24字节的内存地址,这个地址取名叫作"".~r2。 RET 从函数返回。 参考: Go还可以通过syso的方式集成体系架构原生的汇编 (https://github.com/golang/go/wiki/GcToolchainTricks)

September 9, 2015

Go语言与C协作的两种方式

任何语言想作为系统编程语言,就必须能够和作为系统语言之母的C语言来打交道,Go语言也不例外。那么它如何与C协作呢? 目前有两种方式,一是通过cgo,二是通过gcc。这两种方式的思路不同,第一种是同时把C语言化成Go来使用,第二种是通过把Go语言化成GCC的中间语言来使用。 cgo cgo的作用有点像swig,将把C语言的结构体和函数包装成Go语言可以使用的结构体和函数。下面是cgo版的hello,world!的例子: 使用下面的命令(Go 1.5)编译成可以在Windows执行的代码: 简单解释一下上面的代码,在import "C"之前注释里的代码会被当成是C代码,Go会生成相关的文件,然后调用C语言编译器将其编译成目标文件,最后链接成一个可执行文件hi.exe。执行hi.exe后就见到了熟悉的Hello, world! gccgo gccgo是Go语言和C协作的另一种方式。在GCC中Go语言被当作一种前端语言,由一个前端处理器来将其转化为中间代码,这个前端处理器就是gccgo。GCC有多个前端,比如常见的C/C++以及不常见的Ada/Obj-C等等。这些前端语言转化生成中间代码以后,可以被链接在一起,生成统一的可执行文件。 使用gccgo的巨大好处是GCC的一整套工具链可供Go语言使用,特别是gdb可以用来调试Go代码。有了这套工具链,Go和其他C库或者二进制代码集成也就变方便了。 gccgo随GCC发布,而不包括在Go的发行版中,根据Go的文档,GCC 4.9将包含完整的Go1.2的实现。 参考

September 7, 2015

实用主义的Go语言:函数是一等公民

在C语言中,函数的使用受到几点限制: 函数本身无法作为值传递,必须通过函数指针 无法定义匿名函数,无法在函数中定义子函数 所以,C语言中函数是二等公民,在使用上有一定限制。 Go语言中的函数破除了上述的限制,被提升到了一等公民的位置,也就是所谓的first-class object。每种语言对first-class object的定义都不一样,Go语言中,first-class object的函数意味着: 函数本身可以作为值传递 支持匿名函数和闭包(closure) 函数可以满足接口(interface) 函数本身可以作为值传递 这意味着,函数可以和整形值以及字符串一样,可以赋给一个变量,作为函数参数传递,以及作为函数返回值等等: C语言中可以通过函数指针实现类似的功能。 匿名函数和闭包(closure) Go语言中同样不支持嵌套函数,下面的语法是非法的: 你不能在一个函数中嵌套一个有名字的函数,但是可以嵌套一个匿名的函数: 而且这个匿名函数支持闭包(closure)。闭包使嵌套的函数能够访问父函数的局部变量,上面的例子中,嵌套的bar()把父函数foo()的局部变量i进行了加一操作。 C语言不支持闭包,所以也不支持在函数体内定义匿名函数。 函数可以满足接口(interface) 既然是first-class object,Go语言中的函数自然也要能被赋值给一个interface。 以Go语言自带的"net/http"包为例,它定义了一个Handler interface: 同时"net/http"又定义了一个函数类型: HandlerFunc能够满足Handler interface,因为HandlerFunc实现了 ServeHTTP: 总的来说,变为一等公民(first-class object)后,Go语言中的函数在语法上变得更加安全便捷了。 参考文章: Function Types in Go (golang) “First Class Functions in Go”

May 8, 2015

实用主义的Go语言:从struct到interface

用惯面向对象语言的人刚转到Go语言可能会有点不习惯,会以面向对象的思维来写Go代码。Go确实带有一点面向对象的风采,Go中的interface难道不是面向对象中的interface?Go中的struct难道不是面向对象中的class? 但其实两者是有差别的,面向对象的程序设计是自上向下的,从interface到class;而Go的方式是自下向上,从struct到interface。 举例子,假设有两种鸭子,一种是橡皮做的小黄鸭YellowDuck,另一种是绿头小野鸭MallardDuck。在Go中用struct表示这两种鸭子: 小黄鸭和小野鸭有一个共同的特点是都会“叫”,我们用quack()方法来表示“叫”这个动作,在Go语言中用interface表示quack(): 上面的代码简单明了,跟下面的Java语言的版本区别不大: 接下来做个小改动,为鸭子们添加一个“飞行”的动作fly(),把这个动作放在接口Flyable中。问题来了,小野鸭(MallardDuck)会飞,可是橡皮做的小黄鸭YellowDuck不会飞啊。所以,只有小野鸭MallardDuck能实现Flyable接口。 Go语言版本: Java语言版本: 如果你足够仔细,可能已经注意到了Go和Java的区别。在Java中需要像这样MallardDuck implements Quackable, Flyable静态指出MallardDuck实现了Quackable和Flyable方法;但是在Go中,这并不是必须的,struct和interface并没有特定的关联,要判断一个struct是否满足一个inteface,只需: 如果MallardDuck满足Flyable定义的所有方法,上面的ok的值就是true。这就是Go语言所谓的类型断言(Type Assertion)。这也是Go灵活的地方,一个struct是否满足一个interface的这种检查,既可以在编译的时候进行,也可以在代码运行的时候进行;而Java等面向对象语言,接口和类的关系是静态的,编译时就定下来的。 这样方便我们组织代码。在面向对象中,假如我们有interface A、B,B扩展了A;然后又class C、D,C实现了B,D扩展了C。如果A中有一个方法fly(),则fly()会出现在A、B、C、D四个地方,会使代码变得难以理解。在Go语言中,我们可以把fly()放到interface Flyable中,然后通过类型断言来判断C和D是否实现Flyable,代码就会清晰许多。 总而言之。面向对象是自上向下的设计,先有interface再有class,编译时候的关系就确定了;而Go采取的方式是自下而上,先有struct再有interface,可以在运行的时候通过类型断言来判断某个struct是否实现某个interface。从这种意义上讲,而面向对象语言偏“完美主义”,而Go语言是偏“实用主义”的,是Duck Typing的一种体现!

April 10, 2015

实用主义的Go语言:struct的嵌入

和传统的面向对象语言如C++和Java相比,Go语言并没有被面向对象的封装、继承、多态那一套理论束缚,而是偏向于实用主义,虽然有点面向对象的味道,但实际上却不是那么回事,写起来却一点也不别扭。 在乎实用而不在乎形式,这一点跟脚本语言很相像。脚本语言是用来干杂活的,要是动不动就来封装、继承、多态那一套未免太累。从这一点看Go跟JavaScript在意境上最为接近,都是不拘泥于形式。 在Go语言中,你可以将一个结构体包含到另外一个结构体: 对于foo来说,可以通过foo.b.hi()来调用Bar的hi方法,通过foo.b.msg来访问Bar的msg属性。 但是如果定义Foo时将b Bar中的b忽略,只留下Bar,那么就变成将Bar“嵌入”Foo: 这样,从语法上,在调用Bar的方法或属性的时候,就从foo.b.hi()变成foo.hi(),从foo.b.msg变成foo.msg。看起来像是Foo“继承”了Bar,但其实不是,因为创建Foo对象的时候,仍然需要显示构造Bar才行: 我们仍然可以通过foo.Bar.hi()以及foo.Bar.msg来访问Bar的属性。 从这方面看,Go语言是“偏好组合胜过继承(Favor composition than inheritance)”的忠实信徒。

April 9, 2015

C++的面向对象和Go语言接口方式的比较

C语言中的struct只能表示数据的集合,对数据集合的操作要在单独的函数中实现。比如我们在C语言中定义一个Cat的结构体,Cat有meow()以及sleep()两种行为: 我们把color和breed看作Cat的属性;meow()和sleep()看作是Cat的两个方法。但是在C语言中,属性color和breed是属于Cat结构体的,而方法meow()和sleep()却并不属于Cat结构体,并且没有一个概念能把这两个方法集合到一起,也就是说无法像C++那样创建一个结构体CatBehavior,把这两个方法集合在一起: 为此,C++引入了封装的概念,让C的struct支持方法。在C++中,你可以把属性和方法放一起了: 但是这样还不够,面向对象的准则是只暴露接口而不暴露内部的方法和属性,这样类与类使用者之间的耦合度才能降低,修改类的内部实现不至于对类的既有使用者造成太大影响。 所以下一步,我们要创建一个接口类CatBehavior来定义Cat的两种行为meow和sleep: Cat继承并实现CatBehavior: 上述代码翻译成Go语言是这样子的: Go和C++最大的区别是Go中的接口并不是通过继承来实现的,而是一种更松散的关系,只要Cat的方法中具备了CatBehavior接口中列举的所有方法,就可以说Cat实现了CatBehavior。 大部分时候,Go语言这种松散的接口就足够了,使用C++这种继承体制反而会营造出很多不必要的的类继承关系。举个例子,假设你家里既养了猫又养了狗,每天晚上你要让它们上床睡觉,需要goToBed函数: 那么,在C++中类关系可能会这样: 上面代码的继承结构有三级,第一级Sleepable,第二级CatBehavior和DogBehavior,第三级才是Cat和Dog。 在Go语言中就不需要这么层级了,接口Sleepable和CatBehavior和DogBehavior可以是同一级: 那么在Go语言中能不能模拟出像C++接口继承这种父子关系呢,或许可以这样做: 一些总结 接口封装是必须的,但是继承和多态并不是必须的。而且继承和多态容易被误用而过度设计,产生庞大的类型继承体系,导致复杂的依赖关系以及难以理解的代码。 Go语言的接口方式是对继承和多态的一种简化,在Go中类型与类型的关系是平的而不是树状的。 Go语言的接口方式有点泛型的味道,泛型的实用性其实比继承和多态要高。虽然C++是面向对象语言,但是它的标准库STL却是一个泛型库。 C++中的多态需要运行时才能确定被调用的函数,而Go中的接口是在编译的时候定下来的,有一定性能上的优势。

January 11, 2015

Go语言中的指针

Go语言中的指针语法和C++一脉相承,都是用*作为符号,虽然语法上接近,但是实际差异不小。 Go使用var定义变量: Go虽然有指针,但是没有指针算数,不能对其进行加减。但可以把指针值赋给另一个指针,因此Go的指针更像是C++的引用,却又没有C++中引用初始化后不能重新赋值的限制。 Go使用nil来表示无效指针,C++使用NULL(0)或nullptr(C++11)来表示空指针。 当指向一个struct结构体时,Go的指针使用.而不是->来引用结构体的成员: 指针在Go中是“安全”的,指针无法指向任意的内存区域,缓冲区溢出的问题不会发生。但注意,解引用一个空指针同样能导致Go程序崩溃。 指针的使用是和内存的分配紧密相关的。C&C++需要手动管理内存,然而Go使用垃圾收集器自动管理内存,这样就减少很多使用指针上的操心。首先我们再也不需要显示释放内存,悬挂指针(dangling pointer,指向已释放的内存)以及多次释放同一个指针指向内存的问题就不会发生。我们甚至不用担心内存是在栈上分配的还是堆上分配的。C++中的各种智能指针std::unique_ptr, std::shared_ptr, std::weak_ptr, std::auto_ptr也用不上了。一切都由Go的运行时系统帮助处理。 在Go中,取一个变量的地址是安全的,不会产生悬挂指针。如果在程序中取了一个变量的地址,编译器会自动在栈上分配这个变量,下面的三个函数是等价的: 反之,在C++函数中,返回一个函数局部变量的指针是一个低级错误。 参考:...

May 5, 2014