Showing preview only (1,202K chars total). Download the full file or copy to clipboard to get everything.
Repository: chai2010/advanced-go-programming-book
Branch: master
Commit: 4447d098a22b
Files: 459
Total size: 1.1 MB
Directory structure:
gitextract_23g4on1p/
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ └── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── .nojekyll
├── LICENSE
├── Makefile
├── README.md
├── SUMMARY.md
├── appendix/
│ ├── appendix-a-trap.md
│ ├── appendix-b-gems.md
│ ├── appendix-c-author.md
│ └── readme.md
├── book.ini
├── ch1-basic/
│ ├── ch1-01-genesis.md
│ ├── ch1-02-hello-revolution.md
│ ├── ch1-03-array-string-and-slice.md
│ ├── ch1-04-func-method-interface.md
│ ├── ch1-05-mem.md
│ ├── ch1-06-goroutine.md
│ ├── ch1-07-error-and-panic.md
│ ├── ch1-08-ext.md
│ └── readme.md
├── ch2-cgo/
│ ├── ch2-01-hello-cgo.md
│ ├── ch2-02-basic.md
│ ├── ch2-03-cgo-types.md
│ ├── ch2-04-func.md
│ ├── ch2-05-internal.md
│ ├── ch2-06-qsort.md
│ ├── ch2-07-memory.md
│ ├── ch2-08-class.md
│ ├── ch2-09-static-shared-lib.md
│ ├── ch2-10-link.md
│ ├── ch2-11-ext.md
│ └── readme.md
├── ch3-asm/
│ ├── ch3-01-basic.md
│ ├── ch3-02-arch.md
│ ├── ch3-03-const-and-var.md
│ ├── ch3-04-func.md
│ ├── ch3-05-control-flow.md
│ ├── ch3-06-func-again.md
│ ├── ch3-07-hack-asm.md
│ ├── ch3-08-goroutine-id.md
│ ├── ch3-09-debug.md
│ ├── ch3-10-ext.md
│ └── readme.md
├── ch4-rpc/
│ ├── ch4-01-rpc-intro.md
│ ├── ch4-02-pb-intro.md
│ ├── ch4-03-netrpc-hack.md
│ ├── ch4-04-grpc.md
│ ├── ch4-05-grpc-hack.md
│ ├── ch4-06-grpc-ext.md
│ ├── ch4-07-pbgo.md
│ ├── ch4-08-grpcurl.md
│ ├── ch4-09-ext.md
│ └── readme.md
├── ch5-web/
│ ├── ch5-01-introduction.md
│ ├── ch5-02-router.md
│ ├── ch5-03-middleware.md
│ ├── ch5-04-validator.md
│ ├── ch5-05-database.md
│ ├── ch5-06-ratelimit.md
│ ├── ch5-07-layout-of-web-project.md
│ ├── ch5-08-interface-and-web.md
│ ├── ch5-09-gated-launch.md
│ ├── ch5-10-ext.md
│ └── readme.md
├── ch6-cloud/
│ ├── ch6-01-dist-id.md
│ ├── ch6-02-lock.md
│ ├── ch6-03-delay-job.md
│ ├── ch6-04-search-engine.md
│ ├── ch6-05-load-balance.md
│ ├── ch6-06-config.md
│ ├── ch6-07-crawler.md
│ ├── ch6-08-ext.md
│ └── readme.md
├── chaoxi/
│ ├── .gitignore
│ └── .keep
├── contributors.json
├── doc.go
├── docs/
│ └── .gitignore
├── errata/
│ ├── README.md
│ ├── a.go
│ └── a_amd64.s
├── examples/
│ ├── README.md
│ ├── ch1.1/
│ │ └── 1-hello/
│ │ └── main.go
│ ├── ch1.2/
│ │ ├── 1-hello-b-1972/
│ │ │ └── main.b
│ │ ├── 10-hello-go-200806/
│ │ │ └── hello.go.txt
│ │ ├── 11-hello-go-20080627/
│ │ │ └── hello.go.txt
│ │ ├── 12-hello-go-20080811/
│ │ │ └── hello.go.txt
│ │ ├── 13-hello-go-20081024/
│ │ │ └── hello.go.txt
│ │ ├── 14-hello-go-20090115/
│ │ │ └── hello.go.txt
│ │ ├── 15-hello-go-20091211/
│ │ │ └── hello.go
│ │ ├── 16-hello-go-v2/
│ │ │ └── hello.go
│ │ ├── 2-hello-c-1974/
│ │ │ └── hello-c-01.c
│ │ ├── 3-hello-c-1978/
│ │ │ └── hello-c-02.c
│ │ ├── 4-hello-c-1988/
│ │ │ └── hello-c-03.c
│ │ ├── 5-hello-c-1989/
│ │ │ └── hello-c-04.c
│ │ ├── 6-hello-newsqueak-1989/
│ │ │ └── hello.newsqueak
│ │ ├── 7-prime-newsqueak/
│ │ │ └── prime.newsqueak
│ │ ├── 8-hello-alef-1993/
│ │ │ └── hello.alef
│ │ ├── 9-hello-limbo-1995/
│ │ │ └── hello.limbo
│ │ ├── xx-hello-go-asm/
│ │ │ ├── hello.go
│ │ │ └── hello_amd64.s
│ │ ├── xx-hello-go-cgo/
│ │ │ └── hello.go
│ │ └── xx-hello-go-swig/
│ │ ├── hello.cc
│ │ ├── hello.go
│ │ └── hello.swigcxx
│ ├── ch2.1/
│ │ ├── hello-01/
│ │ │ └── main.go
│ │ ├── hello-02/
│ │ │ └── main.go
│ │ ├── hello-03/
│ │ │ ├── hello.c
│ │ │ └── main.go
│ │ ├── hello-04/
│ │ │ └── main.go
│ │ ├── hello-05/
│ │ │ └── main.go
│ │ └── hello-06/
│ │ └── main.go
│ ├── ch2.10/
│ │ ├── hello-py/
│ │ │ ├── Makefile
│ │ │ ├── gopkg.h
│ │ │ ├── main.go
│ │ │ └── py3-config.go
│ │ └── hello-so/
│ │ ├── Makefile
│ │ ├── _test_so.c
│ │ ├── hello.py
│ │ ├── main.go
│ │ └── say-hello.h
│ ├── ch2.4/
│ │ └── return-go-ptr/
│ │ └── main.go
│ ├── ch2.5/
│ │ ├── 01-cgo-gen-files/
│ │ │ ├── Makefile
│ │ │ ├── _obj/
│ │ │ │ ├── _cgo_export.c
│ │ │ │ ├── _cgo_export.h
│ │ │ │ ├── _cgo_flags
│ │ │ │ ├── _cgo_gotypes.go
│ │ │ │ ├── _cgo_main.c
│ │ │ │ ├── hello.cgo1.go
│ │ │ │ ├── hello.cgo2.c
│ │ │ │ ├── main.cgo1.go
│ │ │ │ └── main.cgo2.c
│ │ │ ├── hello.go
│ │ │ ├── main.go
│ │ │ ├── nocgo_1.go
│ │ │ └── nocgo_x.go
│ │ ├── 02-go-call-c-func/
│ │ │ ├── Makefile
│ │ │ ├── _obj/
│ │ │ │ ├── _cgo_export.c
│ │ │ │ ├── _cgo_export.h
│ │ │ │ ├── _cgo_flags
│ │ │ │ ├── _cgo_gotypes.go
│ │ │ │ ├── _cgo_main.c
│ │ │ │ ├── main.cgo1.go
│ │ │ │ └── main.cgo2.c
│ │ │ └── main.go
│ │ └── 03-c-call-go-func/
│ │ ├── Makefile
│ │ ├── _obj/
│ │ │ ├── _cgo_export.c
│ │ │ ├── _cgo_export.h
│ │ │ ├── _cgo_flags
│ │ │ ├── _cgo_gotypes.go
│ │ │ ├── _cgo_main.c
│ │ │ ├── sum.cgo1.go
│ │ │ └── sum.cgo2.c
│ │ ├── main.c
│ │ ├── sum.go
│ │ └── sum.h
│ ├── ch2.6/
│ │ ├── 01-qsort-v1/
│ │ │ ├── Makefile
│ │ │ └── main.c
│ │ ├── 02-qsort-v2/
│ │ │ ├── main.go
│ │ │ ├── qsort.go
│ │ │ ├── qsort_test.go
│ │ │ └── test_helper.go
│ │ ├── 03-qsort-v3/
│ │ │ ├── main.go
│ │ │ ├── sort.go
│ │ │ └── sort_test.go
│ │ └── 04-qsort-v4/
│ │ ├── main.go
│ │ ├── sort.go
│ │ └── sort_test.go
│ ├── ch2.8/
│ │ ├── class-cc2go/
│ │ │ ├── main.go
│ │ │ ├── my_buffer.cc
│ │ │ ├── my_buffer.go
│ │ │ ├── my_buffer.h
│ │ │ ├── my_buffer_capi.cc
│ │ │ ├── my_buffer_capi.go
│ │ │ └── my_buffer_capi.h
│ │ └── class-go2cc/
│ │ ├── goobj.go
│ │ ├── main.cc
│ │ ├── main.go
│ │ ├── persion.go
│ │ ├── person.cc
│ │ ├── person.h
│ │ ├── person_capi.go
│ │ └── person_capi.h
│ ├── ch2.9/
│ │ ├── incorrect-dll-api/
│ │ │ ├── Makefile
│ │ │ ├── main.go
│ │ │ └── mystring/
│ │ │ ├── Makefile
│ │ │ ├── mystring.c
│ │ │ └── mystring.h
│ │ ├── make-clib-dll/
│ │ │ ├── Makefile
│ │ │ ├── _test_main.c
│ │ │ ├── main.go
│ │ │ ├── number-win64.def
│ │ │ └── number.h
│ │ ├── make-clib-from-multi-pkg/
│ │ │ ├── Makefile
│ │ │ ├── _test_main.c
│ │ │ ├── main.go
│ │ │ ├── main.h
│ │ │ └── number/
│ │ │ ├── number.go
│ │ │ └── number.h
│ │ ├── make-clib-shared/
│ │ │ ├── Makefile
│ │ │ ├── _test_main.c
│ │ │ ├── main.go
│ │ │ └── number.h
│ │ ├── make-clib-static/
│ │ │ ├── Makefile
│ │ │ ├── _test_main.c
│ │ │ ├── main.go
│ │ │ └── number.h
│ │ ├── plugin/
│ │ │ ├── Makefile
│ │ │ ├── main.go
│ │ │ └── plugin.go
│ │ ├── use-clib-shared/
│ │ │ ├── Makefile
│ │ │ ├── main.go
│ │ │ └── number/
│ │ │ ├── Makefile
│ │ │ ├── number.c
│ │ │ └── number.h
│ │ ├── use-clib-static-v1/
│ │ │ ├── Makefile
│ │ │ ├── main.go
│ │ │ └── number/
│ │ │ ├── Makefile
│ │ │ ├── number.c
│ │ │ └── number.h
│ │ └── use-clib-static-v2/
│ │ ├── Makefile
│ │ ├── main.go
│ │ ├── number/
│ │ │ ├── Makefile
│ │ │ ├── number.c
│ │ │ └── number.h
│ │ └── z_link_number_c.c
│ ├── ch2.x/
│ │ ├── hello/
│ │ │ ├── .gitignore
│ │ │ ├── Makefile
│ │ │ ├── _obj/
│ │ │ │ ├── _cgo_export.c
│ │ │ │ ├── _cgo_export.h
│ │ │ │ ├── _cgo_flags
│ │ │ │ ├── _cgo_gotypes.go
│ │ │ │ ├── _cgo_main.c
│ │ │ │ ├── hello.cgo1.go
│ │ │ │ └── hello.cgo2.c
│ │ │ └── hello.go
│ │ ├── hello-swig-v1/
│ │ │ ├── Makefile
│ │ │ ├── hello.cc
│ │ │ ├── hello.swigcxx
│ │ │ ├── hello_test.go
│ │ │ └── runme.go
│ │ └── hello-swig-v2/
│ │ ├── Makefile
│ │ ├── hello.cc
│ │ ├── hello.go
│ │ ├── hello.i
│ │ ├── runme.go
│ │ └── swig_wrap.cc
│ ├── ch3.1/
│ │ ├── id-01/
│ │ │ ├── pkg.go
│ │ │ └── runme.go
│ │ ├── id-02/
│ │ │ ├── pkg.go
│ │ │ ├── pkg_amd64.s
│ │ │ └── runme.go
│ │ ├── main-01/
│ │ │ ├── Makefile
│ │ │ ├── main.go
│ │ │ └── main_amd64.s
│ │ ├── str-01/
│ │ │ └── pkg.go
│ │ ├── str-02/
│ │ │ ├── pkg.go
│ │ │ ├── pkg_amd64.s
│ │ │ └── runme.go
│ │ └── str-03/
│ │ ├── pkg.go
│ │ ├── pkg_amd64.s
│ │ └── runme.go
│ ├── ch3.6/
│ │ ├── asm-split/
│ │ │ ├── main.go
│ │ │ └── main_amd64.s
│ │ ├── closure-01/
│ │ │ └── main.go
│ │ └── closure-02/
│ │ ├── main.go
│ │ └── main_amd64.s
│ ├── ch3.8/
│ │ ├── ch3.8-1/
│ │ │ └── main.go
│ │ ├── ch3.8-2/
│ │ │ └── main.go
│ │ ├── ch3.8-3/
│ │ │ └── main.go
│ │ ├── ch3.8-4/
│ │ │ ├── main.go
│ │ │ └── main_amd64.s
│ │ ├── ch3.8-5/
│ │ │ ├── main.go
│ │ │ └── main_amd64.s
│ │ ├── ch3.8-6/
│ │ │ ├── gls/
│ │ │ │ ├── gls.go
│ │ │ │ ├── goid.go
│ │ │ │ └── goid_amd64.s
│ │ │ ├── go.mod
│ │ │ └── main.go
│ │ ├── hello/
│ │ │ └── main.go
│ │ └── hello-asm/
│ │ ├── main.go
│ │ └── main_amd64.s
│ ├── ch3.x/
│ │ ├── add/
│ │ │ ├── add.go
│ │ │ ├── add_asm.go
│ │ │ ├── add_asm_amd64.s
│ │ │ ├── add_asm_generic.go
│ │ │ ├── add_test.go
│ │ │ └── runme.go
│ │ ├── binary_search/
│ │ │ ├── binary_search.go
│ │ │ ├── binary_search_amd64.s
│ │ │ └── binary_search_test.go
│ │ ├── cfun/
│ │ │ ├── main.go
│ │ │ └── vendor/
│ │ │ └── asmpkg/
│ │ │ ├── asmpkg.go
│ │ │ └── asmpkg_amd64.s
│ │ ├── globalvar/
│ │ │ ├── asm_amd64.s
│ │ │ ├── globalvar.go
│ │ │ └── runme.go
│ │ ├── hello/
│ │ │ ├── hello.go
│ │ │ ├── hello_amd64.s
│ │ │ └── runme.go
│ │ ├── ifelse/
│ │ │ ├── ifelse.go
│ │ │ ├── ifelse_ams_amd64.s
│ │ │ ├── ifelse_test.go
│ │ │ └── runme.go
│ │ ├── instr/
│ │ │ ├── bench_test.go
│ │ │ ├── instr.go
│ │ │ └── instr_amd64.s
│ │ ├── loop/
│ │ │ ├── loop.go
│ │ │ ├── loop_asm_amd64.s
│ │ │ ├── loop_test.go
│ │ │ └── runme.go
│ │ ├── min/
│ │ │ ├── min.go
│ │ │ ├── min_asm_amd64.s
│ │ │ ├── min_test.go
│ │ │ └── runme.go
│ │ ├── slice/
│ │ │ ├── runme.go
│ │ │ ├── slice.go
│ │ │ ├── slice_asm_amd64.s
│ │ │ └── slice_test.go
│ │ ├── stackmap/
│ │ │ ├── stackmap.go
│ │ │ ├── stackmap_amd64.s
│ │ │ └── stackmap_test.go
│ │ ├── sum/
│ │ │ ├── sum.go
│ │ │ ├── sum_amd64.s
│ │ │ └── sum_test.go
│ │ └── vector/
│ │ ├── sum_amd64.s
│ │ ├── vector.go
│ │ ├── vector_amd64.s
│ │ └── vector_test.go
│ ├── ch4.1/
│ │ ├── hello-client-v1/
│ │ │ └── main.go
│ │ ├── hello-server-v1/
│ │ │ └── main.go
│ │ ├── hello-service-v2/
│ │ │ ├── api/
│ │ │ │ └── hello.go
│ │ │ ├── client/
│ │ │ │ └── main.go
│ │ │ └── server/
│ │ │ └── main.go
│ │ └── hello-service-v3/
│ │ ├── client/
│ │ │ └── main.go
│ │ ├── server/
│ │ │ └── main.go
│ │ └── server-on-http/
│ │ └── main.go
│ ├── ch4.2/
│ │ ├── hello-server/
│ │ │ └── main.go
│ │ ├── hello.pb/
│ │ │ ├── Makefile
│ │ │ ├── hello.pb.go
│ │ │ └── hello.proto
│ │ └── protoc-gen-go-netrpc/
│ │ ├── main.go
│ │ └── netprpc.go
│ ├── ch4.3/
│ │ ├── rpc-auth/
│ │ │ ├── client/
│ │ │ │ └── main.go
│ │ │ ├── main.go
│ │ │ └── server/
│ │ │ └── main.go
│ │ ├── rpc-context/
│ │ │ ├── client/
│ │ │ │ └── main.go
│ │ │ └── server/
│ │ │ └── main.go
│ │ └── rpc-reverse/
│ │ ├── client/
│ │ │ └── main.go
│ │ └── server/
│ │ └── main.go
│ ├── ch4.4/
│ │ ├── 1/
│ │ │ ├── client/
│ │ │ │ └── main.go
│ │ │ ├── helloservice/
│ │ │ │ ├── hello.pb.go
│ │ │ │ └── hello.proto
│ │ │ └── server/
│ │ │ └── main.go
│ │ ├── 2/
│ │ │ ├── HelloService/
│ │ │ │ ├── hello.pb.go
│ │ │ │ └── hello.proto
│ │ │ ├── client/
│ │ │ │ └── main.go
│ │ │ └── server/
│ │ │ └── main.go
│ │ ├── 3/
│ │ │ ├── clientpub/
│ │ │ │ └── main.go
│ │ │ ├── clientsub/
│ │ │ │ └── main.go
│ │ │ ├── pubsubservice/
│ │ │ │ ├── pubsubservice.pb.go
│ │ │ │ └── pubsubservice.proto
│ │ │ └── server/
│ │ │ └── main.go
│ │ ├── basic/
│ │ │ └── client/
│ │ │ ├── Makefile
│ │ │ ├── hello.pb.go
│ │ │ ├── hello.proto
│ │ │ └── main.go
│ │ └── grpc-pubsub/
│ │ ├── clientPub/
│ │ │ └── clientPub.go
│ │ ├── clientSub/
│ │ │ └── clientSub.go
│ │ ├── pubsubservice/
│ │ │ ├── pubsubservice.pb.go
│ │ │ └── pubsubservice.proto
│ │ └── server/
│ │ └── server.go
│ ├── ch4.5/
│ │ ├── on-web/
│ │ │ ├── Makefile
│ │ │ ├── helloworld.pb.go
│ │ │ ├── helloworld.proto
│ │ │ ├── main.go
│ │ │ └── tls-config/
│ │ │ ├── Makefile
│ │ │ ├── server.crt
│ │ │ └── server.key
│ │ ├── panic-and-log/
│ │ │ ├── Makefile
│ │ │ ├── helloworld.pb.go
│ │ │ ├── helloworld.proto
│ │ │ └── main.go
│ │ ├── rest-and-swagger/
│ │ │ └── dummy.txt
│ │ ├── tls/
│ │ │ ├── Makefile
│ │ │ ├── helloworld.pb.go
│ │ │ ├── helloworld.proto
│ │ │ ├── main.go
│ │ │ └── tls-config/
│ │ │ ├── Makefile
│ │ │ ├── ca.crt
│ │ │ ├── ca.key
│ │ │ ├── ca.srl
│ │ │ ├── client.crt
│ │ │ ├── client.csr
│ │ │ ├── client.key
│ │ │ ├── server.crt
│ │ │ ├── server.csr
│ │ │ └── server.key
│ │ └── tok/
│ │ ├── Makefile
│ │ ├── helloworld.pb.go
│ │ ├── helloworld.proto
│ │ └── main.go
│ ├── ch4.6/
│ │ ├── pb2-default-value/
│ │ │ ├── Makefile
│ │ │ ├── helloworld.pb.go
│ │ │ └── helloworld.proto
│ │ ├── rest/
│ │ │ ├── Makefile
│ │ │ ├── helloworld.pb.go
│ │ │ ├── helloworld.pb.gw.go
│ │ │ ├── helloworld.proto
│ │ │ ├── helloworld.swagger.json
│ │ │ └── main.go
│ │ └── validators/
│ │ ├── Makefile
│ │ ├── helloworld.pb.go
│ │ ├── helloworld.proto
│ │ └── helloworld.validator.pb.go
│ └── ch4.7/
│ ├── http-router/
│ │ └── dummy.txt
│ ├── pb-option/
│ │ ├── Makefile
│ │ ├── dummy.txt
│ │ ├── helloworld.pb.go
│ │ ├── helloworld.proto
│ │ └── main/
│ │ └── helloworld.pb.go
│ ├── pb-web-frameswork/
│ │ └── dummy.txt
│ └── plugin-framework/
│ └── dummy.txt
├── gen_contributors.go
├── go.mod
├── go.sum
├── images/
│ ├── Makefile
│ ├── ch1-10-slice-1.ditaa.txt
│ ├── ch1-11-init.ditaa.txt
│ ├── ch1-12-init.ditaa.txt
│ ├── ch1-7-array-4int.ditaa.txt
│ ├── ch1-8-string-1.ditaa.txt
│ ├── ch1-9-string-2.ditaa.txt
│ ├── ch2-1-x-ptr-to-y-ptr.plantuml
│ ├── ch2-2-int32-to-char-ptr.plantuml
│ ├── ch2-3-x-slice-to-y-slice.plantuml
│ ├── ch2-4-cgo-generated-files.dot
│ ├── ch2-5-call-c-sum-v1.plantuml
│ ├── ch2-6-call-c-sum-v2.plantuml
│ ├── ch3-10-func-arg-01.ditaa.txt
│ ├── ch3-11-func-local-var-01.ditaa.txt
│ ├── ch3-12-func-call-frame-01.ditaa.txt
│ ├── ch3-13-func-stack-frame-layout-01.ditaa.txt
│ ├── ch3-2-arch-amd64-01.ditaa.txt
│ ├── ch3-3-arch-amd64-02.ditaa.txt
│ ├── ch3-4-pkg-var-decl-01.ditaa.txt
│ ├── ch3-6-pkg-var-decl-02.ditaa.txt
│ ├── ch3-7-pkg-var-decl-03.ditaa.txt
│ ├── ch3-8-func-decl-01.ditaa.txt
│ ├── ch3-9-func-decl-02.ditaa.txt
│ ├── ch6-controller-logic-dao-storage.plantuml
│ ├── ch6-interface-impl.plantuml
│ └── github-social.drawio
├── index.md
├── preface-pdf.md
└── preface.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.
# Use of this source code is governed by a Apache
# license that can be found in the LICENSE file.
# http://editorconfig.org/
root = true
# Unix-style newlines with a newline ending every file
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = false
[*]
indent_style = tab
[*.{go,proto}]
charset = utf-8
indent_style = tab
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
[*.yaml]
indent_style = space
indent_size = 2
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
提示:哪一章节的问题,建议如何修改
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
提示:解决了什么问题,也可以讲下理由。
================================================
FILE: .gitignore
================================================
/_book
/book
*.out*
_zz*
================================================
FILE: .nojekyll
================================================
================================================
FILE: LICENSE
================================================
BSD 2-Clause License
Copyright (c) 2017, chai2010
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================
FILE: Makefile
================================================
# Copyright 2016 <chaishushan{AT}gmail.com>. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
#
# WaBook: Mini Markdown Book
# https://github.com/wa-lang/wabook
#
default:
wabook serve
build:
-rm book
wabook build
-rm book/.gitignore
-rm book/.nojekyll
-rm -rf book/.git
deploy:
-@make clean
wabook build
-rm book/.gitignore
-rm -rf book/.git
-rm -rf book/examples
cd book && git init
cd book && git add .
cd book && git commit -m "first commit"
cd book && git branch -M gh-pages
cd book && git remote add origin git@github.com:chai2010/advanced-go-programming-book.git
cd book && git push -f origin gh-pages
clean:
-rm -rf book
================================================
FILE: README.md
================================================
# Go语言高级编程 (Advanced Go Programming)
- *凹语言(专为 WebAssembly 设计): https://github.com/wa-lang/wa*
- *WaBook(Go语言实现的MD电子书构建工具): https://github.com/wa-lang/wabook*
----
两位作者的公众号:
<table>
<tr>
<td>
<img width="222px" src="https://github.com/chai2010/advanced-go-programming-book/raw/master/css.png">
</td>
<td>
<img width="222px" src="https://github.com/chai2010/advanced-go-programming-book/raw/master/cch.png">
</td>
</tr>
</table>
----
## Go语言高级编程(第2版)终于出版
](gobook-v2.jpg)
- 简介: https://mp.weixin.qq.com/s/fJihKW7HKBDRgAW9L-rG1w
- 异步:https://www.epubit.com/bookDetails?id=UB8e75a5d38685c
- 豆瓣:https://book.douban.com/subject/37436371/
- 当当:https://product.dangdang.com/11990190944.html
----
本书涵盖CGO、Go汇编语言、RPC实现、Web框架实现、分布式系统等高阶主题,针对Go语言有一定经验想深入了解Go语言各种高级用法的开发人员。对于刚学习Go语言的读者,建议先从[《Go语言圣经》](https://github.com/golang-china/gopl-zh)开始系统学习Go语言的基础知识。如果希望深入学习Go语言语法树结构,可以参考[《Go语法树入门——开启自制编程语言和编译器之旅》](https://github.com/chai2010/go-ast-book)。如果想从头实现一个玩具Go语言可以参考[《从头实现µGo语言》](https://github.com/wa-lang/ugo-compiler-book)。

- 作者:柴树杉,Github [@chai2010](https://github.com/chai2010),Twitter [@chaishushan](https://twitter.com/chaishushan),主页 https://chai2010.cn/about
- 作者:曹春晖,Github [@cch123](https://github.com/cch123),主页 [xargin](http://xargin.com)
- 网址:https://github.com/chai2010/advanced-go-programming-book
- 在线阅读:https://chai2010.cn/advanced-go-programming-book
- 豆瓣:- https://book.douban.com/subject/34442131/
## 购买链接:
- 京东:https://item.jd.com/12647494.html
- 异步:https://www.epubit.com/book/detail/40090
- 当当:http://product.dangdang.com/27896588.html
<!--
## 抄袭&侵权
- [千锋教育原文抄袭《Go语言高级编程》](https://mp.weixin.qq.com/s/0Jtx79ZSgKY8bBdEvl-PlQ) 证据截图:[01.pdf](chaoxi/zhihu-qianfeng-01.pdf), [02.pdf](chaoxi/zhihu-qianfeng-02.pdf)
-->
## 相关文章
1. [GopherChina 2019大会推荐书单来啦](https://zhuanlan.zhihu.com/p/63910336)
1. [深入Go的底层,带你走近一群有追求的人](https://mp.weixin.qq.com/s/obnnVkO2EiFnuXk_AIDHWw)
## 相关报告
1. [Go语言简介](https://talks.godoc.org/github.com/chai2010/awesome-go-zh/chai2010/chai2010-golang-intro.slide) - [chai2010](https://github.com/chai2010/awesome-go-zh/tree/master/chai2010) 武汉·黄鹤会 2018/12/16
1. [GIAC: 2018 - Go 语言将要走向何方?](https://github.com/chai2010/awesome-go-zh/blob/master/chai2010/giac2018) - [chai2010](https://github.com/chai2010/awesome-go-zh/tree/master/chai2010) 上海·GIAC全球互联网架构大会 2018/11/23
1. [Go语言并发编程](https://talks.godoc.org/github.com/chai2010/awesome-go-zh/chai2010/chai2010-golang-concurrency.slide) - [chai2010](https://github.com/chai2010/awesome-go-zh/tree/master/chai2010) 武汉·光谷猫友会 2018/09/16, [整理01](https://mp.weixin.qq.com/s/UaY9gJU85dq-dXlOhLYY1Q)/[整理02](https://mp.weixin.qq.com/s/_aKNO-H11GEDA-l0rycfQQ)
1. 深入CGO编程: https://github.com/chai2010/gopherchina2018-cgo-talk
## 版权声明
<a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-nd/4.0/88x31.png" /></a><br /><span xmlns:dct="http://purl.org/dc/terms/" property="dct:title">Go语言高级编程</span> 由 <a xmlns:cc="http://creativecommons.org/ns#" href="https://github.com/chai2010/advanced-go-programming-book" property="cc:attributionName" rel="cc:attributionURL">柴树杉,曹春晖</a> 采用 <a rel="license" href="http://creativecommons.org/licenses/by-nc-nd/4.0/">知识共享 署名-非商业性使用-禁止演绎 4.0 国际 许可协议</a>进行许可。
严禁任何商业行为使用或引用该文档的全部或部分内容!
欢迎大家提供建议!
----
## 鸣谢
感谢大家提供 PR!以下排名不分先后:
<!--
1. get contributors.json
https://api.github.com/repos/chai2010/advanced-go-programming-book/contributors
2. go run gen_contributors.go
3. replace contributors table
-->
| [<img src="https://avatars3.githubusercontent.com/u/15542874?v=4" width="100px;"/><br /><sub><b>fuwensun</b></sub>](https://github.com/fuwensun) | [<img src="https://avatars0.githubusercontent.com/u/1927478?v=4" width="100px;"/><br /><sub><b>qichengzx</b></sub>](https://github.com/qichengzx) | [<img src="https://avatars0.githubusercontent.com/u/914267?v=4" width="100px;"/><br /><sub><b>lewgun</b></sub>](https://github.com/lewgun) | [<img src="https://avatars1.githubusercontent.com/u/26503046?v=4" width="100px;"/><br /><sub><b>LaoK996</b></sub>](https://github.com/LaoK996) | [<img src="https://avatars3.githubusercontent.com/u/15144321?v=4" width="100px;"/><br /><sub><b>plpan</b></sub>](https://github.com/plpan) | [<img src="https://avatars3.githubusercontent.com/u/7970646?v=4" width="100px;"/><br /><sub><b>xiaoliwang</b></sub>](https://github.com/xiaoliwang) | [<img src="https://avatars0.githubusercontent.com/u/16658738?v=4" width="100px;"/><br /><sub><b>barryz</b></sub>](https://github.com/barryz) |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars3.githubusercontent.com/u/19967175?v=4" width="100px;"/><br /><sub><b>alphayan</b></sub>](https://github.com/alphayan) | [<img src="https://avatars1.githubusercontent.com/u/10794816?v=4" width="100px;"/><br /><sub><b>leobuzhi</b></sub>](https://github.com/leobuzhi) | [<img src="https://avatars2.githubusercontent.com/u/19154488?v=4" width="100px;"/><br /><sub><b>iikira</b></sub>](https://github.com/iikira) | [<img src="https://avatars0.githubusercontent.com/u/26301186?v=4" width="100px;"/><br /><sub><b>fognome</b></sub>](https://github.com/fognome) | [<img src="https://avatars2.githubusercontent.com/u/12817?v=4" width="100px;"/><br /><sub><b>darren</b></sub>](https://github.com/darren) | [<img src="https://avatars3.githubusercontent.com/u/6910037?v=4" width="100px;"/><br /><sub><b>jiayx</b></sub>](https://github.com/jiayx) | [<img src="https://avatars1.githubusercontent.com/u/2696746?v=4" width="100px;"/><br /><sub><b>orangle</b></sub>](https://github.com/orangle) |
| [<img src="https://avatars0.githubusercontent.com/u/9821034?v=4" width="100px;"/><br /><sub><b>yangtaooo</b></sub>](https://github.com/yangtaooo) | [<img src="https://avatars1.githubusercontent.com/u/1538704?v=4" width="100px;"/><br /><sub><b>bcb51</b></sub>](https://github.com/bcb51) | [<img src="https://avatars3.githubusercontent.com/u/38324300?v=4" width="100px;"/><br /><sub><b>mathrobot</b></sub>](https://github.com/mathrobot) | [<img src="https://avatars3.githubusercontent.com/u/40716445?v=4" width="100px;"/><br /><sub><b>7535</b></sub>](https://github.com/7535) | [<img src="https://avatars1.githubusercontent.com/u/1229983?v=4" width="100px;"/><br /><sub><b>cloverstd</b></sub>](https://github.com/cloverstd) | [<img src="https://avatars2.githubusercontent.com/u/1488134?v=4" width="100px;"/><br /><sub><b>douglarek</b></sub>](https://github.com/douglarek) | [<img src="https://avatars0.githubusercontent.com/u/6734408?v=4" width="100px;"/><br /><sub><b>RealDeanZhao</b></sub>](https://github.com/RealDeanZhao) |
| [<img src="https://avatars2.githubusercontent.com/u/8447684?v=4" width="100px;"/><br /><sub><b>yyt030</b></sub>](https://github.com/yyt030) | [<img src="https://avatars3.githubusercontent.com/u/2748184?v=4" width="100px;"/><br /><sub><b>yuqaf1989</b></sub>](https://github.com/yuqaf1989) | [<img src="https://avatars0.githubusercontent.com/u/21311269?v=4" width="100px;"/><br /><sub><b>BeccaBecca</b></sub>](https://github.com/BeccaBecca) | [<img src="https://avatars3.githubusercontent.com/u/152362?v=4" width="100px;"/><br /><sub><b>cloudzhou</b></sub>](https://github.com/cloudzhou) | [<img src="https://avatars1.githubusercontent.com/u/631411?v=4" width="100px;"/><br /><sub><b>ezioruan</b></sub>](https://github.com/ezioruan) | [<img src="https://avatars1.githubusercontent.com/u/16048119?v=4" width="100px;"/><br /><sub><b>hacknode</b></sub>](https://github.com/hacknode) | [<img src="https://avatars1.githubusercontent.com/u/8757196?v=4" width="100px;"/><br /><sub><b>Frozen-Shadow</b></sub>](https://github.com/Frozen-Shadow) |
----
https://api.github.com/repos/chai2010/advanced-go-programming-book/contributors
================================================
FILE: SUMMARY.md
================================================
# 目录
[Go语言高级编程](index.md)
[前言](preface.md)
- [语言基础](ch1-basic/readme.md)
- [Go语言创世纪](ch1-basic/ch1-01-genesis.md)
- [Hello, World 的革命](ch1-basic/ch1-02-hello-revolution.md)
- [数组、字符串和切片](ch1-basic/ch1-03-array-string-and-slice.md)
- [函数、方法和接口](ch1-basic/ch1-04-func-method-interface.md)
- [面向并发的内存模型](ch1-basic/ch1-05-mem.md)
- [常见的并发模式](ch1-basic/ch1-06-goroutine.md)
- [错误和异常](ch1-basic/ch1-07-error-and-panic.md)
- [补充说明](ch1-basic/ch1-08-ext.md)
- [CGO编程](ch2-cgo/readme.md)
- [快速入门](ch2-cgo/ch2-01-hello-cgo.md)
- [CGO基础](ch2-cgo/ch2-02-basic.md)
- [类型转换](ch2-cgo/ch2-03-cgo-types.md)
- [函数调用](ch2-cgo/ch2-04-func.md)
- [内部机制](ch2-cgo/ch2-05-internal.md)
- [实战: 封装qsort](ch2-cgo/ch2-06-qsort.md)
- [CGO内存模型](ch2-cgo/ch2-07-memory.md)
- [C++类包装](ch2-cgo/ch2-08-class.md)
- [静态库和动态库](ch2-cgo/ch2-09-static-shared-lib.md)
- [编译和链接参数](ch2-cgo/ch2-10-link.md)
- [补充说明](ch2-cgo/ch2-11-ext.md)
- [汇编语言](ch3-asm/readme.md)
- [快速入门](ch3-asm/ch3-01-basic.md)
- [计算机结构](ch3-asm/ch3-02-arch.md)
- [常量和全局变量](ch3-asm/ch3-03-const-and-var.md)
- [函数](ch3-asm/ch3-04-func.md)
- [控制流](ch3-asm/ch3-05-control-flow.md)
- [再论函数](ch3-asm/ch3-06-func-again.md)
- [汇编语言的威力](ch3-asm/ch3-07-hack-asm.md)
- [例子:Goroutine ID](ch3-asm/ch3-08-goroutine-id.md)
- [Delve调试器](ch3-asm/ch3-09-debug.md)
- [补充说明](ch3-asm/ch3-10-ext.md)
- [第4章 RPC和Protobuf](ch4-rpc/readme.md)
- [RPC入门](ch4-rpc/ch4-01-rpc-intro.md)
- [Protobuf](ch4-rpc/ch4-02-pb-intro.md)
- [玩转RPC](ch4-rpc/ch4-03-netrpc-hack.md)
- [gRPC入门](ch4-rpc/ch4-04-grpc.md)
- [gRPC进阶](ch4-rpc/ch4-05-grpc-hack.md)
- [gRPC和Protobuf扩展](ch4-rpc/ch4-06-grpc-ext.md)
- [pbgo: 基于Protobuf的框架](ch4-rpc/ch4-07-pbgo.md)
- [grpcurl工具](ch4-rpc/ch4-08-grpcurl.md)
- [补充说明](ch4-rpc/ch4-09-ext.md)
- [Go和Web](ch5-web/readme.md)
- [Web开发简介](ch5-web/ch5-01-introduction.md)
- [请求路由](ch5-web/ch5-02-router.md)
- [中间件](ch5-web/ch5-03-middleware.md)
- [请求校验](ch5-web/ch5-04-validator.md)
- [和数据库打交道](ch5-web/ch5-05-database.md)
- [服务流量限制](ch5-web/ch5-06-ratelimit.md)
- [大型Web项目分层](ch5-web/ch5-07-layout-of-web-project.md)
- [接口和表驱动开发](ch5-web/ch5-08-interface-and-web.md)
- [灰度发布和A/B测试](ch5-web/ch5-09-gated-launch.md)
- [补充说明](ch5-web/ch5-10-ext.md)
- [分布式系统](ch6-cloud/readme.md)
- [分布式 id 生成器](ch6-cloud/ch6-01-dist-id.md)
- [分布式锁](ch6-cloud/ch6-02-lock.md)
- [延时任务系统](ch6-cloud/ch6-03-delay-job.md)
- [分布式搜索引擎](ch6-cloud/ch6-04-search-engine.md)
- [负载均衡](ch6-cloud/ch6-05-load-balance.md)
- [分布式配置管理](ch6-cloud/ch6-06-config.md)
- [分布式爬虫](ch6-cloud/ch6-07-crawler.md)
- [补充说明](ch6-cloud/ch6-08-ext.md)
- [附录](appendix/readme.md)
- [附录A: Go语言常见坑](appendix/appendix-a-trap.md)
- [附录B: 有趣的代码片段](appendix/appendix-b-gems.md)
- [附录C: 作者简介](appendix/appendix-c-author.md)
================================================
FILE: appendix/appendix-a-trap.md
================================================
# 附录A:Go语言常见坑
这里列举的Go语言常见坑都是符合Go语言语法的,可以正常的编译,但是可能是运行结果错误,或者是有资源泄漏的风险。
## 可变参数是空接口类型
当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。
```go
func main() {
var a = []interface{}{1, 2, 3}
fmt.Println(a)
fmt.Println(a...)
}
```
不管是否展开,编译器都无法发现错误,但是输出是不同的:
```
[1 2 3]
1 2 3
```
## 数组是值传递
在函数调用参数中,数组是值传递,无法通过修改数组类型的参数返回结果。
```go
func main() {
x := [3]int{1, 2, 3}
func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
```
必要时需要使用切片。
## map遍历是顺序不固定
map是一种hash表实现,每次遍历的顺序都可能不一样。
```go
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}
for k, v := range m {
println(k, v)
}
}
```
## 返回值被屏蔽
在局部作用域中,命名的返回值内同名的局部变量屏蔽:
```go
func Foo() (err error) {
if err := Bar(); err != nil {
return
}
return
}
```
## recover必须在defer函数中运行
recover捕获的是祖父级调用时的异常,直接调用时无效:
```go
func main() {
recover()
panic(1)
}
```
直接defer调用也是无效:
```go
func main() {
defer recover()
panic(1)
}
```
defer调用时多层嵌套依然无效:
```go
func main() {
defer func() {
func() { recover() }()
}()
panic(1)
}
```
必须在defer函数中直接调用才有效:
```go
func main() {
defer func() {
recover()
}()
panic(1)
}
```
## main函数提前退出
后台Goroutine无法保证完成任务。
```go
func main() {
go println("hello")
}
```
## 通过Sleep来回避并发中的问题
休眠并不能保证输出完整的字符串:
```go
func main() {
go println("hello")
time.Sleep(time.Second)
}
```
类似的还有通过插入调度语句:
```go
func main() {
go println("hello")
runtime.Gosched()
}
```
## 独占CPU导致其它Goroutine饿死
Goroutine 是协作式抢占调度(Go1.14版本之前),Goroutine本身不会主动放弃CPU:
```go
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {} // 占用CPU
}
```
解决的方法是在for循环加入runtime.Gosched()调度函数:
```go
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
}()
for {
runtime.Gosched()
}
}
```
或者是通过阻塞的方式避免CPU占用:
```go
func main() {
runtime.GOMAXPROCS(1)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(i)
}
os.Exit(0)
}()
select{}
}
```
Go1.14 版本引入基于系统信号的异步抢占调度,可以避免 Goroutine 饿死的情况。
## 不同Goroutine之间不满足顺序一致性内存模型
因为在不同的Goroutine,main函数中无法保证能打印出`hello, world`:
```go
var msg string
var done bool
func setup() {
msg = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
println(msg)
}
```
解决的办法是用显式同步:
```go
var msg string
var done = make(chan bool)
func setup() {
msg = "hello, world"
done <- true
}
func main() {
go setup()
<-done
println(msg)
}
```
msg的写入是在channel发送之前,所以能保证打印`hello, world`
## 闭包错误引用同一个变量
```go
func main() {
for i := 0; i < 5; i++ {
defer func() {
println(i)
}()
}
}
```
改进的方法是在每轮迭代中生成一个局部变量:
```go
func main() {
for i := 0; i < 5; i++ {
i := i
defer func() {
println(i)
}()
}
}
```
或者是通过函数参数传入:
```go
func main() {
for i := 0; i < 5; i++ {
defer func(i int) {
println(i)
}(i)
}
}
```
## 在循环内部执行defer语句
defer在函数退出时才能执行,在for执行defer会导致资源延迟释放:
```go
func main() {
for i := 0; i < 5; i++ {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}
}
```
解决的方法可以在for中构造一个局部函数,在局部函数内部执行defer:
```go
func main() {
for i := 0; i < 5; i++ {
func() {
f, err := os.Open("/path/to/file")
if err != nil {
log.Fatal(err)
}
defer f.Close()
}()
}
}
```
## 切片会导致整个底层数组被锁定
切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。
```go
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = data[:1]
}
// do some thing
}
```
解决的方法是将结果克隆一份,这样可以释放底层的数组:
```go
func main() {
headerMap := make(map[string][]byte)
for i := 0; i < 5; i++ {
name := "/path/to/file"
data, err := ioutil.ReadFile(name)
if err != nil {
log.Fatal(err)
}
headerMap[name] = append([]byte{}, data[:1]...)
}
// do some thing
}
```
## 空指针和空接口不等价
比如返回了一个错误指针,但是并不是空的error接口:
```go
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
```
## 内存地址会变化
Go语言中对象的地址可能发生变化,因此指针不能从其它非指针类型的值生成:
```go
func main() {
var x int = 42
var p uintptr = uintptr(unsafe.Pointer(&x))
runtime.GC()
var px *int = (*int)(unsafe.Pointer(p))
println(*px)
}
```
当内存发生变化的时候,相关的指针会同步更新,但是非指针类型的uintptr不会做同步更新。
同理CGO中也不能保存Go对象地址。
## Goroutine泄露
Go语言是带内存自动回收的特性,因此内存一般不会泄漏。但是Goroutine确存在泄漏的情况,同时泄漏的Goroutine引用的内存同样无法被回收。
```go
func main() {
ch := func() <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
} ()
return ch
}()
for v := range ch {
fmt.Println(v)
if v == 5 {
break
}
}
}
```
上面的程序中后台Goroutine向管道输入自然数序列,main函数中输出序列。但是当break跳出for循环的时候,后台Goroutine就处于无法被回收的状态了。
我们可以通过context包来避免这个问题:
```go
func main() {
ctx, cancel := context.WithCancel(context.Background())
ch := func(ctx context.Context) <-chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
} ()
return ch
}(ctx)
for v := range ch {
fmt.Println(v)
if v == 5 {
cancel()
break
}
}
}
```
当main函数在break跳出循环时,通过调用`cancel()`来通知后台Goroutine退出,这样就避免了Goroutine的泄漏。
================================================
FILE: appendix/appendix-b-gems.md
================================================
# 附录B:有趣的代码片段
这里收集一些比较有意思的Go程序片段。
## 自重写程序
UNIX/Go语言之父 Ken Thompson 在1983年的图灵奖演讲 Reflections on Trusting Trust 就给出了一个C语言的自重写程序。
最短的C语言自重写程序是 Vlad Taeerov 和 Rashit Fakhreyev 的版本:
```c
main(a){printf(a="main(a){printf(a=%c%s%c,34,a,34);}",34,a,34);}
```
下面的Go语言版本自重写程序是 [rsc](https://research.swtch.com/zip) 提供的:
```go
/* Go quine */
package main
import "fmt"
func main() {
fmt.Printf("%s%c%s%c\n", q, 0x60, q, 0x60)
}
var q = `/* Go quine */
package main
import "fmt"
func main() {
fmt.Printf("%s%c%s%c\n", q, 0x60, q, 0x60)
}
var q = `
```
在 golang-nuts 中还有很多版本:
```go
package main;func main(){c:="package main;func main(){c:=%q;print(c,c)}";print(c,c)}
```
```go
package main;func main(){print(c+"\x60"+c+"\x60")};var c=`package main;func main(){print(c+"\x60"+c+"\x60")};var c=`
```
如果有更短的版本欢迎告诉我们。
## 三元表达式
```go
func If(condition bool, trueVal, falseVal interface{}) interface{} {
if condition {
return trueVal
}
return falseVal
}
a, b := 2, 3
max := If(a > b, a, b).(int)
println(max)
```
## 禁止 main 函数退出的方法
```go
func main() {
defer func() { for {} }()
}
func main() {
defer func() { select {} }()
}
func main() {
defer func() { <-make(chan bool) }()
}
```
## 基于管道的随机数生成器
随机数的一个特点是不好预测。如果一个随机数的输出是可以简单预测的,那么一般会称为伪随机数。
```go
func main() {
for i := range random(100) {
fmt.Println(i)
}
}
func random(n int) <-chan int {
c := make(chan int)
go func() {
defer close(c)
for i := 0; i < n; i++ {
select {
case c <- 0:
case c <- 1:
}
}
}()
return c
}
```
基于select语言特性构造的随机数生成器。
## Assert测试断言
```go
type testing_TBHelper interface {
Helper()
}
func Assert(tb testing.TB, condition bool, args ...interface{}) {
if x, ok := tb.(testing_TBHelper); ok {
x.Helper() // Go1.9+
}
if !condition {
if msg := fmt.Sprint(args...); msg != "" {
tb.Fatalf("Assert failed, %s", msg)
} else {
tb.Fatalf("Assert failed")
}
}
}
func Assertf(tb testing.TB, condition bool, format string, a ...interface{}) {
if x, ok := tb.(testing_TBHelper); ok {
x.Helper() // Go1.9+
}
if !condition {
if msg := fmt.Sprintf(format, a...); msg != "" {
tb.Fatalf("Assertf failed, %s", msg)
} else {
tb.Fatalf("Assertf failed")
}
}
}
func AssertFunc(tb testing.TB, fn func() error) {
if x, ok := tb.(testing_TBHelper); ok {
x.Helper() // Go1.9+
}
if err := fn(); err != nil {
tb.Fatalf("AssertFunc failed, %v", err)
}
}
```
================================================
FILE: appendix/appendix-c-author.md
================================================
# 附录C:作者简介
- 柴树杉(Github [@chai2010](https://github.com/chai2010); Twitter [@chaishushan](https://twitter.com/chaishushan))Go语言代码贡献者,Dart语言和WebAssembly等技术爱好者,著有[《WebAssembly标准入门》](https://github.com/chai2010/awesome-wasm-zh/blob/master/webassembly-primer.md)等书。
- 曹春晖(Github [@cch123](https://github.com/cch123))在 web 领域工作多年,开源爱好者。对大型网站系统的架构和相关工具的实现很感兴趣,并且有一些研究成果。目前在滴滴平台技术部工作。
================================================
FILE: appendix/readme.md
================================================
# 附录
附录部分主要包含量三个部分:第一部分是摘录量一些Go语言常见的坑和解决方案;第二部分是一些有趣的代码片段;第三部分是作者信息。
================================================
FILE: book.ini
================================================
# https://giscus.app
[book]
title = "Go语言高级编程"
authors = ["柴树杉", "曹春晖"]
description = "Go语言高级编程"
language = "zh"
multilingual = false
src = "."
[build]
build-dir = "book"
[custom]
content_header = "<ul dir=\"auto\"><li><em>凹语言(Go实现, 面向WASM设计): <a href=\"https://github.com/wa-lang/wa\">https://github.com/wa-lang/wa</a></em></li><li><em>WaBook(Go语言实现的MD电子书构建工具): <a href=\"https://github.com/wa-lang/wabook\">https://github.com/wa-lang/wabook</a></em></li></ul><hr>"
content_footer = "<hr><table><tr><td><img width=\"222px\" src=\"https://chai2010.cn/advanced-go-programming-book/css.png\"></td><td><img width=\"222px\" src=\"https://chai2010.cn/advanced-go-programming-book/cch.png\"></td></tr></table>"
page_footer = "<span>© 2019-2022 | <a href=\"https://github.com/chai2010/advanced-go-programming-book\">柴树杉、曹春晖</a> 保留所有权利</span>"
[giscus]
enaled = true
data_repo = "chai2010/advanced-go-programming-book"
data_repo_id = "MDEwOlJlcG9zaXRvcnkxMTU4NTc5NTQ="
data_category = "General"
data_category_id = "DIC_kwDOBufaIs4CAwFi"
[output.html]
git-repository-url = "https://github.com/chai2010/advanced-go-programming-book"
edit-url-template = "https://github.com/chai2010/advanced-go-programming-book/edit/master/{path}"
git-repository-icon = "fa-github"
================================================
FILE: ch1-basic/ch1-01-genesis.md
================================================
# 1.1 Go 语言创世纪
Go 语言最初由 Google 公司的 *Robert Griesemer*、*Ken Thompson* 和 *Rob Pike* 三个大牛于 2007 年开始设计发明,设计新语言的最初的洪荒之力来自于对超级复杂的 C++11 特性的吹捧报告的鄙视,最终的目标是设计网络和多核时代的 C 语言。到 2008 年中期,语言的大部分特性设计已经完成,并开始着手实现编译器和运行时,大约在这一年 *Russ Cox* 作为主力开发者加入。到了 2009 年,Go 语言已经逐步趋于稳定。同年 9 月,Go 语言正式发布并开源了代码。
Go 语言很多时候被描述为“类 C 语言”,或者是“21 世纪的 C 语言”。从各种角度看,Go 语言确实是从 C 语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等诸多编程思想,还有彻底继承和发扬了 C 语言简单直接的暴力编程哲学等。*图1-1*是《Go语言圣经》中给出的 Go 语言的基因图谱,我们可以从中看到有哪些编程语言对 Go 语言产生了影响。

*图 1-1 Go 语言基因族谱*
首先看基因图谱的左边一支。可以明确看出 Go 语言的并发特性是由贝尔实验室的 *Hoare* 于 1978 年发布的 CSP 理论演化而来。其后,CSP 并发模型在 Squeak/NewSqueak 和 Alef 等编程语言中逐步完善并走向实际应用,最终这些设计经验被消化并吸收到了 Go 语言中。业界比较熟悉的 Erlang 编程语言的并发编程模型也是 CSP 理论的另一种实现。
再看基因图谱的中间一支。中间一支主要包含了 Go 语言中面向对象和包特性的演化历程。Go 语言中包和接口以及面向对象等特性则继承自 *Niklaus Wirth* 所设计的 Pascal 语言以及其后所衍生的相关编程语言。其中包的概念、包的导入和声明等语法主要来自于 Modula-2 编程语言,面向对象特性所提供的方法的声明语法等则来自于 Oberon 编程语言。最终 Go 语言演化出了自己特有的支持鸭子面向对象模型的隐式接口等诸多特性。
最后是基因图谱的右边一支,这是对 C 语言的致敬。Go 语言是对 C 语言最彻底的一次扬弃,不仅仅是语法和 C 语言有着很多差异,最重要的是舍弃了 C 语言中灵活但是危险的指针运算。而且,Go 语言还重新设计了 C 语言中部分不太合理运算符的优先级,并在很多细微的地方都做了必要的打磨和改变。当然,C 语言中少即是多、简单直接的暴力编程哲学则被 Go 语言更彻底地发扬光大了(Go 语言居然只有 25 个关键字,sepc 语言规范还不到 50 页)。
Go 语言其它的一些特性零散地来自于其他一些编程语言;比如 iota 语法是从 APL 语言借鉴,词法作用域与嵌套函数等特性来自于 Scheme 语言(和其他很多编程语言)。Go 语言中也有很多自己发明创新的设计。比如 Go 语言的切片为轻量级动态数组提供了有效的随机存取的性能,这可能会让人联想到链表的底层的共享机制。还有 Go 语言新发明的 defer 语句(*Ken* 发明)也是神来之笔。
## 1.1.1 来自贝尔实验室特有基因
作为 Go 语言标志性的并发编程特性则来自于贝尔实验室的 *Tony Hoare* 于 1978 年发表的鲜为外界所知的关于并发研究的基础文献:顺序通信进程(communicating sequential processes ,缩写为 CSP)。在最初的 CSP 论文中,程序只是一组没有中间共享状态的平行运行的处理过程,它们之间使用管道进行通信和控制同步。*Tony Hoare* 的 CSP 并发模型只是一个用于描述并发性基本概念的描述语言,它并不是一个可以编写可执行程序的通用编程语言。
CSP 并发模型最经典的实际应用是来自爱立信发明的 Erlang 编程语言。不过在 Erlang 将 CSP 理论作为并发编程模型的同时,同样来自贝尔实验室的 *Rob Pike* 以及其同事也在不断尝试将 CSP 并发模型引入当时的新发明的编程语言中。他们第一次尝试引入 CSP 并发特性的编程语言叫 Squeak(老鼠的叫声),是一个用于提供鼠标和键盘事件处理的编程语言,在这个语言中管道是静态创建的。然后是改进版的 Newsqueak 语言(新版老鼠的叫声),新提供了类似 C 语言语句和表达式的语法,还有类似 Pascal 语言的推导语法。Newsqueak 是一个带垃圾回收的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在 Newsqueak 语言中管道已经是动态创建的,管道属于第一类值、可以保存到变量中。然后是 Alef 编程语言(Alef 也是 C 语言之父 *Ritchie* 比较喜爱的编程语言),Alef 语言试图将 Newsqueak 语言改造为系统编程语言,但是因为缺少垃圾回收机制而导致并发编程很痛苦(这也是继承 C 语言手工管理内存的代价)。在 Aelf 语言之后还有一个叫 Limbo 的编程语言(地狱的意思),这是一个运行在虚拟机中的脚本语言。Limbo 语言是 Go 语言最接近的祖先,它和 Go 语言有着最接近的语法。到设计 Go 语言时,*Rob Pike* 在 CSP 并发编程模型的实践道路上已经积累了几十年的经验,关于 Go 语言并发编程的特性完全是信手拈来,新编程语言的到来也是水到渠成了。
*图1-2*展示了 Go 语言库早期代码库日志可以看出最直接的演化历程(Git 用 `git log --before={2008-03-03} --reverse` 命令查看)。

*图 1-2 Go 语言开发日志*
从早期提交日志中也可以看出,Go 语言是从 *Ken Thompson* 发明的 B 语言、*Dennis M. Ritchie* 发明的 C 语言逐步演化过来的,它首先是 C 语言家族的成员,因此很多人将 Go 语言称为 21 世纪的 C 语言。
*图1-3*是 Go 语言中来自贝尔实验室特有并发编程基因的演化过程:

*图 1-3 Go 语言并发演化历史*
纵观整个贝尔实验室的编程语言的发展进程,从 B 语言、C 语言、Newsqueak、Alef、Limbo 语言一路走来,Go 语言继承了来着贝尔实验室的半个世纪的软件设计基因,终于完成了 C 语言革新的使命。纵观这几年来的发展趋势,Go 语言已经成为云计算、云存储时代最重要的基础编程语言。
## 1.1.2 你好, 世界
按照惯例,介绍所有编程语言的第一个程序都是“Hello, World!”。虽然本教假设读者已经了解了 Go 语言,但是我们还是不想打破这个惯例(因为这个传统正是从 Go 语言的前辈 C 语言传承而来的)。下面的代码展示的 Go 语言程序输出的是中文“你好, 世界!”。
```Go
package main
import "fmt"
func main() {
fmt.Println("你好, 世界!")
}
```
将以上代码保存到 `hello.go` 文件中。因为代码中有非 ASCII 的中文字符,我们需要将文件的编码显式指定为无 BOM 的 UTF8 编码格式(源文件采用 UTF8 编码是 Go 语言规范所要求的)。然后进入命令行并切换到 `hello.go` 文件所在的目录。目前我们可以将 Go 语言当作脚本语言,在命令行中直接输入 `go run hello.go` 来运行程序。如果一切正常的话。应该可以在命令行看到输出 `你好, 世界!` 的结果。
现在,让我们简单介绍一下程序。所有的 Go 程序,都是由最基本的函数和变量构成,函数和变量被组织到一个个单独的 Go 源文件中,这些源文件再按照作者的意图组织成合适的 package,最终这些 package 再有机地组成一个完整的 Go 语言程序。其中,函数用于包含一系列的语句(指明要执行的操作序列),以及执行操作时存放数据的变量。我们这个程序中函数的名字是 main。虽然Go语言中,函数的名字没有太多的限制,但是 main 包中的 main 函数默认是每一个可执行程序的入口。而 package 则用于包装和组织相关的函数、变量和常量。在使用一个 package 之前,我们需要使用 import 语句导入包。例如,我们这个程序中导入了 fmt 包(fmt 是 format 单词的缩写,表示格式化相关的包),然后我们才可以使用 fmt 包中的 Println 函数。
而双引号包含的“你好, 世界!”则是 Go 语言的字符串面值常量。和 C 语言中的字符串不同,Go 语言中的字符串内容是不可变更的。在以字符串作为参数传递给 fmt.Println 函数时,字符串的内容并没有被复制——传递的仅仅是字符串的地址和长度(字符串的结构在 `reflect.StringHeader` 中定义)。在 Go 语言中,函数参数都是以复制的方式(不支持以引用的方式)传递(比较特殊的是,Go 语言闭包函数对外部变量是以引用的方式使用)。
================================================
FILE: ch1-basic/ch1-02-hello-revolution.md
================================================
# 1.2 Hello, World 的革命
在创世纪章节中我们简单介绍了 Go 语言的演化基因族谱,对其中来自于贝尔实验室的特有并发编程基因做了重点介绍,最后引出了 Go 语言版的“Hello, World”程序。其实“Hello, World”程序是展示各种语言特性的最好的例子,是通向该语言的一个窗口。这一节我们将沿着各个编程语言演化的时间轴,简单回顾下“Hello, World”程序是如何逐步演化到目前的 Go 语言形式、最终完成它的革命使命的。

*图 1-4 Go 语言并发演化历史*
## 1.2.1 B 语言 - Ken Thompson, 1972
首先是 B 语言,B 语言是 Go 语言之父贝尔实验室的 *Ken Thompson* 早年间开发的一种通用的程序设计语言,设计目的是为了用于辅助 UNIX 系统的开发。但是因为B语言缺乏灵活的类型系统导致使用比较困难。后来,*Ken Thompson* 的同事 *Dennis Ritchie* 以 B 语言为基础开发出了 C 语言,C 语言提供了丰富的类型,极大地增加了语言的表达能力。到目前为止它依然是世界上最常用的程序语言之一。而B语言自从被它取代之后,则就只存在于各种文献之中,成为了历史。
目前见到的 B 语言版本的“Hello World”,一般认为是来自于 *Brian W. Kernighan* 编写的 B 语言入门教程(Go 核心代码库中的第一个提交者名字正是 *Brian W. Kernighan*),程序如下:
```c
main() {
extrn a, b, c;
putchar(a); putchar(b); putchar(c);
putchar('!*n');
}
a 'hell';
b 'o, w';
c 'orld';
```
由于 B 语言缺乏灵活的数据类型,只能分别以 `a`、`b`、`c` 全局变量来定义要输出的内容,并且每个变量的长度必须对齐到了 4 个字节(有一种写汇编语言的感觉)。然后通过多次调用 `putchar` 函数输出字符,最后的 `'!*n'` 表示输出一个换行的意思。
总体来说,B 语言简单,功能也比较简陋。
## 1.2.2 C 语言 - Dennis Ritchie, 1974 ~ 1989
C 语言是由 *Dennis Ritchie* 在 B 语言的基础上改进而来,它增加了丰富的数据类型,并最终实现了用它重写 UNIX 的伟大目标。C 语言可以说是现代 IT 行业最重要的软件基石,目前主流的操作系统几乎全部是由 C 语言开发的,许多基础系统软件也是 C 语言开发的。C 系家族的编程语言占据统治地位达几十年之久,半个多世纪以来依然充满活力。
在 *Brian W. Kernighan* 于 1974 年左右编写的 C 语言入门教程中,出现了第一个 C 语言版本的“Hello World”程序。这给后来大部分编程语言教程都以“Hello World”为第一个程序提供了惯例。第一个 C 语言版本的“Hello World”程序如下:
```c
main()
{
printf("hello, world");
}
```
关于这个程序,有几点需要说明的:首先是 `main` 函数因为没有明确返回值类型,默认返回 `int` 类型;其次 `printf` 函数默认不需要导入函数声明即可以使用;最后 `main` 没有明确返回语句,但默认返回 0 值。在这个程序出现时,C 语言还远未标准化,我们看到的是上古时代的 C 语言语法:函数不用写返回值,函数参数也可以忽略,使用 `printf` 时不需要包含头文件等。
这个例子同样出现在了 1978 年出版的《C 程序设计语言》第一版中,作者正是 *Brian W. Kernighan* 和 *Dennis M. Ritchie*(简称 *K&R*)。书中的“Hello World”末尾增加了一个换行输出:
```c
main()
{
printf("hello, world\n");
}
```
这个例子在字符串末尾增加了一个换行,C 语言的 `\n` 换行比 B 语言的 `'!*n'` 换行看起来要简洁了一些。
在 *K&R* 的教程面世 10 年之后的 1988 年,《C 程序设计语言》第二版终于出版了。此时 ANSI C 语言的标准化草案已经初步完成,但正式版本的文档尚未发布。不过书中的“Hello World”程序根据新的规范增加了 `#include <stdio.h>` 头文件包含语句,用于包含`printf`函数的声明(新的 C89 标准中,仅仅是针对`printf`函数而言,依然可以不用声明函数而直接使用)。
```c
#include <stdio.h>
main()
{
printf("hello, world\n");
}
```
然后到了 1989 年,ANSI C 语言第一个国际标准发布,一般被称为 C89。C89 是流行最广泛的一个 C 语言标准,目前依然被大量使用。《C 程序设计语言》第二版的也再次印刷新版本,并针对新发布的 C89 规范建议,给 `main` 函数的参数增加了 `void` 输入参数说明,表示没有输入参数的意思。
```c
#include <stdio.h>
main(void)
{
printf("hello, world\n");
}
```
至此,C 语言本身的进化基本完成。后面的 C92、C99、C11 都只是针对一些语言细节做了完善。因为各种历史因素, C89 依然是使用最广泛的标准。
## 1.2.3 Newsqueak - Rob Pike, 1989
Newsqueak 是 *Rob Pike* 发明的老鼠语言的第二代,是他用于实践 CSP 并发编程模型的战场。Newsqueak 是新的 squeak 语言的意思,其中 squeak 是老鼠吱吱吱的叫声,也可以看作是类似鼠标点击的声音。Squeak 是一个提供鼠标和键盘事件处理的编程语言,Squeak语言的管道是静态创建的。改进版的 Newsqueak 语言则提供了类似 C 语言语句和表达式的语法和类似 Pascal 语言的推导语法。Newsqueak 是一个带自动垃圾回收的纯函数式语言,它再次针对键盘、鼠标和窗口事件管理。但是在 Newsqueak 语言中管道是动态创建的,属于第一类值,因此可以保存到变量中。
Newsqueak 类似脚本语言,内置了一个 `print` 函数,它的“Hello World”程序看不出什么特色:
```go
print("Hello,", "World", "\n");
```
从上面的程序中,除了猜测 `print` 函数可以支持多个参数外,我们很难看到 Newsqueak 语言相关的特性。由于 Newsqueak 语言和 Go 语言相关的特性主要是并发和管道。因此,我们这里通过一个并发版本的“素数筛”算法来略窥 Newsqueak 语言的特性。“素数筛”的原理如图:

*图 1-5 素数筛*
Newsqueak 语言并发版本的“素数筛”程序如下:
```go
// 向管道输出从 2 开始的自然数序列
counter := prog(c:chan of int) {
i := 2;
for(;;) {
c <-= i++;
}
};
// 针对 listen 管道获取的数列,过滤掉是 prime 倍数的数
// 新的序列输出到 send 管道
filter := prog(prime:int, listen, send:chan of int) {
i:int;
for(;;) {
if((i = <-listen)%prime) {
send <-= i;
}
}
};
// 主函数
// 每个管道第一个流出的数必然是素数
// 然后基于这个新的素数构建新的素数过滤器
sieve := prog() of chan of int {
c := mk(chan of int);
begin counter(c);
prime := mk(chan of int);
begin prog(){
p:int;
newc:chan of int;
for(;;){
prime <-= p =<- c;
newc = mk();
begin filter(p, c, newc);
c = newc;
}
}();
become prime;
};
// 启动素数筛
prime := sieve();
```
其中 `counter` 函数用于向管道输出原始的自然数序列,每个 `filter` 函数对象则对应每一个新的素数过滤管道,这些素数过滤管道根据当前的素数筛子将输入管道流入的数列筛选后重新输出到输出管道。`mk(chan of int)` 用于创建管道,类似 Go 语言的 `make(chan int)` 语句;`begin filter(p, c, newc)` 关键字启动素数筛的并发体,类似 Go 语言的 `go filter(p, c, newc)` 语句;`become` 用于返回函数结果,类似 `return` 语句。
Newsqueak 语言中并发体和管道的语法和 Go 语言已经比较接近了,后置的类型声明和 Go 语言的语法也很相似。
## 1.2.4 Alef - Phil Winterbottom, 1993
在 Go 语言出现之前,Alef 语言是作者心中比较完美的并发语言,Alef 语法和运行时基本是无缝兼容 C 语言。Alef 语言中的对线程和进程的并发体都提供了支持,其中 `proc receive(c)` 用于启动一个进程,`task receive(c)` 用于启动一个线程,它们之间通过管道 `c` 进行通讯。不过由于 Alef 缺乏内存自动回收机制,导致并发体的内存资源管理异常复杂。而且 Alef 语言只在 Plan9 系统中提供过短暂的支持,其它操作系统并没有实际可以运行的 Alef 开发环境。而且 Alef 语言只有《Alef 语言规范》和《Alef 编程向导》两个公开的文档,因此在贝尔实验室之外关于 Alef 语言的讨论并不多。
由于 Alef 语言同时支持进程和线程并发体,而且在并发体中可以再次启动更多的并发体,导致了 Alef 的并发状态会异常复杂。同时 Alef 没有自动垃圾回收机制(Alef 因为保留的 C 语言灵活的指针特性,也导致了自动垃圾回收机制实现比较困难),各种资源充斥于不同的线程和进程之间,导致并发体的内存资源管理异常复杂。Alef 语言全部继承了 C 语言的语法,可以认为是增强了并发语法的 C 语言。下图是 Alef 语言文档中展示的一个可能的并发体状态:

*图 1-6 Alef 并发模型*
Alef 语言并发版本的“Hello World”程序如下:
```c
#include <alef.h>
void receive(chan(byte*) c) {
byte *s;
s = <- c;
print("%s\n", s);
terminate(nil);
}
void main(void) {
chan(byte*) c;
alloc c;
proc receive(c);
task receive(c);
c <- = "hello proc or task";
c <- = "hello proc or task";
print("done\n");
terminate(nil);
}
```
程序开头的 `#include <alef.h>` 语句用于包含 Alef 语言的运行时库。`receive` 是一个普通函数,程序中用作每个并发体的入口函数;`main` 函数中的 `alloc c` 语句先创建一个 `chan(byte*)` 类型的管道,类似 Go 语言的 `make(chan []byte)` 语句;然后分别以进程和线程的方式启动 `receive` 函数;启动并发体之后,`main` 函数向 `c` 管道发送了两个字符串数据; 而进程和线程状态运行的 `receive` 函数会以不确定的顺序先后从管道收到数据后,然后分别打印字符串;最后每个并发体都通过调用 `terminate(nil)` 来结束自己。
Alef 的语法和 C 语言基本保持一致,可以认为它是在 C 语言的语法基础上增加了并发编程相关的特性,可以看作是另一个维度的 C++ 语言。
## 1.2.5 Limbo - Sean Dorward, Phil Winterbottom, Rob Pike, 1995
Limbo(地狱)是用于开发运行在小型计算机上的分布式应用的编程语言,它支持模块化编程,编译期和运行时的强类型检查,进程内基于具有类型的通信管道,原子性垃圾收集和简单的抽象数据类型。Limbo 被设计为:即便是在没有硬件内存保护的小型设备上,也能安全运行。Limbo 语言主要运行在 Inferno 系统之上。
Limbo 语言版本的“Hello World”程序如下:
```go
implement Hello;
include "sys.m"; sys: Sys;
include "draw.m";
Hello: module
{
init: fn(ctxt: ref Draw->Context, args: list of string);
};
init(ctxt: ref Draw->Context, args: list of string)
{
sys = load Sys Sys->PATH;
sys->print("hello, world\n");
}
```
从这个版本的“Hello World”程序中,我们已经可以发现很多 Go 语言特性的雏形。第一句 `implement Hello;` 基本对应 Go 语言的 `package Hello` 包声明语句。然后是 `include "sys.m"; sys: Sys;` 和 `include "draw.m";` 语句用于导入其它的模块,类似 Go 语言的 `import "sys"` 和 `import "draw"` 语句。然后 Hello 包模块还提供了模块初始化函数 `init`,并且函数的参数的类型也是后置的,不过 Go 语言的初始化函数是没有参数的。
## 1.2.6 Go 语言 - 2007~2009
贝尔实验室后来经历了多次动荡,包括 *Ken Thompson* 在内的 Plan9 项目原班人马最终加入了 Google 公司。在发明 Limbo 等前辈语言诞生十多年之后,在 2007 年底,Go 语言三个最初的作者因为偶然的因素聚集到一起批斗 C++(传说是 C++ 语言的布道师在 Google 公司到处鼓吹的 C++11 各种牛逼特性彻底惹恼了他们),他们终于抽出了 20% 的自由时间创造了 Go 语言。最初的 Go 语言规范从 2008 年 3 月开始编写,最初的 Go 程序也是直接编译到 C 语言然后再二次编译为机器码。到了 2008 年 5 月,Google 公司的领导们终于发现了 Go 语言的巨大潜力,从而开始全力支持这个项目(Google 的创始人甚至还贡献了`func`关键字),让他们可以将全部工作时间投入到 Go 语言的设计和开发中。在 Go 语言规范初版完成之后,Go 语言的编译器终于可以直接生成机器码了。
### 1.2.6.1 hello.go - 2008 年 6 月
```go
package main
func main() int {
print "hello, world\n";
return 0;
}
```
这是初期 Go 语言程序正式开始测试的版本。其中内置的用于调试的 `print` 语句已经存在,不过是以命令的方式使用。入口 `main` 函数还和 C 语言中的 `main` 函数一样返回 `int` 类型的值,而且需要 `return` 显式地返回值。每个语句末尾的分号也还存在。
### 1.2.6.2 hello.go - 2008 年 6 月 27 日
```go
package main
func main() {
print "hello, world\n";
}
```
入口函数 `main` 已经去掉了返回值,程序默认通过隐式调用 `exit(0)` 来返回。Go 语言朝着简单的方向逐步进化。
### 1.2.6.3 hello.go - 2008 年 8 月 11 日
```go
package main
func main() {
print("hello, world\n");
}
```
用于调试的内置的 `print` 由开始的命令改为普通的内置函数,使得语法更加简单一致。
### 1.2.6.4 hello.go - 2008 年 10 月 24 日
```go
package main
import "fmt"
func main() {
fmt.printf("hello, world\n");
}
```
作为 C 语言中招牌的 `printf` 格式化函数已经移植了到了 Go 语言中,函数放在 `fmt` 包中(`fmt` 是格式化单词 `format` 的缩写)。不过 `printf` 函数名的开头字母依然是小写字母,采用大写字母表示导出的特性还没有出现。
### 1.2.6.5 hello.go - 2009年1月15日
```go
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n");
}
```
Go 语言开始采用是否大小写首字母来区分符号是否可以被导出。大写字母开头表示导出的公共符号,小写字母开头表示包内部的私有符号。国内用户需要注意的是,汉字中没有大小写字母的概念,因此以汉字开头的符号目前是无法导出的(针对问题中国用户已经给出相关建议,等 Go2 之后或许会调整对汉字的导出规则)。
### 1.2.6.7 hello.go - 2009 年 12 月 11 日
```go
package main
import "fmt"
func main() {
fmt.Printf("hello, world\n")
}
```
Go 语言终于移除了语句末尾的分号。这是 Go 语言在 2009 年 11 月 10 号正式开源之后第一个比较重要的语法改进。从 1978 年 C 语言教程第一版引入的分号分割的规则到现在,Go 语言的作者们花了整整 32 年终于移除了语句末尾的分号。在这 32 年的演化的过程中必然充满了各种八卦故事,我想这一定是 Go 语言设计者深思熟虑的结果(现在 Swift 等新的语言也是默认忽略分号的,可见分号确实并不是那么的重要)。
## 1.2.7 你好, 世界! - V2.0
在经过半个世纪的涅槃重生之后,Go 语言不仅仅打印出了 Unicode 版本的“Hello, World”,而且可以方便地向全球用户提供打印服务。下面版本通过 `http` 服务向每个访问的客户端打印中文的“你好, 世界!”和当前的时间信息。
```go
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func main() {
fmt.Println("Please visit http://127.0.0.1:12345/")
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
s := fmt.Sprintf("你好, 世界! -- Time: %s", time.Now().String())
fmt.Fprintf(w, "%v\n", s)
log.Printf("%v\n", s)
})
if err := http.ListenAndServe(":12345", nil); err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
```
我们通过Go语言标准库自带的 `net/http` 包构造了一个独立运行的 http 服务。其中 `http.HandleFunc("/", ...)` 针对 `/` 根路径请求注册了响应处理函数。在响应处理函数中,我们依然使用 `fmt.Fprintf` 格式化输出函数实现了通过 http 协议向请求的客户端打印格式化的字符串,同时通过标准库的日志包在服务器端也打印相关字符串。最后通过 `http.ListenAndServe` 函数调用来启动 http 服务。
至此,Go 语言终于完成了从单机单核时代的 C 语言到 21 世纪互联网时代多核环境的通用编程语言的蜕变。
================================================
FILE: ch1-basic/ch1-03-array-string-and-slice.md
================================================
# 1.3 数组、字符串和切片
在主流的编程语言中数组及其相关的数据结构是使用得最为频繁的,只有在它(们)不能满足时才会考虑链表、hash 表(hash 表可以看作是数组和链表的混合体)和更复杂的自定义数据结构。
Go 语言中数组、字符串和切片三者是密切相关的数据结构。这三种数据类型,在底层原始数据有着相同的内存结构,在上层,因为语法的限制而有着不同的行为表现。首先,Go 语言的数组是一种值类型,虽然数组的元素可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。Go 语言字符串底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制。切片的行为更为灵活,切片的结构和字符串结构类似,但是解除了只读限制。切片的底层数据虽然也是对应数据类型的数组,但是每个切片还有独立的长度和容量信息,切片赋值和函数传参数时也是将切片头信息部分按传值方式处理。因为切片头含有底层数据的指针,所以它的赋值也不会导致底层数据的复制。其实 Go 语言的赋值和函数传参规则很简单,除了闭包函数以引用的方式对外部变量访问之外,其它赋值和函数传参数都是以传值的方式处理。要理解数组、字符串和切片三种不同的处理方式的原因需要详细了解它们的底层数据结构。
## 1.3.1 数组
数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一个部分,不同长度或不同类型的数据组成的数组都是不同的类型,因此在 Go 语言中很少直接使用数组(不同长度的数组因为类型不同无法直接赋值)。和数组对应的类型是切片,切片是可以动态增长和收缩的序列,切片的功能也更加灵活,但是要理解切片的工作原理还是要先理解数组。
我们先看看数组有哪些定义方式:
```go
var a [3]int // 定义长度为 3 的 int 型数组, 元素全部为 0
var b = [...]int{1, 2, 3} // 定义长度为 3 的 int 型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为 3 的 int 型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为 6 的 int 型数组, 元素为 1, 2, 0, 0, 5, 6
```
第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。
第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。
第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和 `map[int]Type` 类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用零值初始化。
第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
数组的内存结构比较简单。比如下面是一个 `[4]int{2,3,5,7}` 数组值对应的内存结构:

*图 1-7 数组布局*
Go 语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如 C 语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
```go
var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a // b 是指向数组的指针
fmt.Println(a[0], a[1]) // 打印数组的前 2 个元素
fmt.Println(b[0], b[1]) // 通过数组指针访问数组元素的方式和数组类似
for i, v := range b { // 通过数组指针迭代数组的元素
fmt.Println(i, v)
}
```
其中 `b` 是指向 `a` 数组的指针,但是通过 `b` 访问数组中元素的写法和 `a` 类似的。还可以通过 `for range` 来迭代数组指针指向的数组元素。其实数组指针类型除了类型和数组不同之外,通过数组指针操作数组的方式和通过数组本身的操作类似,而且数组指针赋值时只会拷贝一个指针。但是数组指针类型依然不够灵活,因为数组的长度是数组类型的组成部分,指向不同长度数组的数组指针类型也是完全不同的。
可以将数组看作一个特殊的结构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数 `len` 可以用于计算数组的长度,`cap` 函数可以用于计算数组的容量。不过对于数组类型来说,`len` 和 `cap` 函数返回的结果始终是一样的,都是对应数组类型的长度。
我们可以用 `for` 循环来迭代数组。下面常见的几种方式都可以用来遍历数组:
```go
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}
```
用 `for range` 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。
用 `for range` 方式迭代,还可以忽略迭代时的下标:
```go
var times [5][0]int
for range times {
fmt.Println("hello")
}
```
其中 `times` 对应一个 `[5][0]int` 类型的数组,虽然第一维数组有长度,但是数组的元素 `[0]int` 大小是 0,因此整个数组占用的内存大小依然是 0。没有付出额外的内存代价,我们就通过 `for range` 方式实现了 `times` 次快速迭代。
数组不仅仅可以用于数值类型,还可以定义字符串数组、结构体数组、函数数组、接口数组、管道数组等等:
```go
// 字符串数组
var s1 = [2]string{"hello", "world"}
var s2 = [...]string{"你好", "世界"}
var s3 = [...]string{1: "世界", 0: "你好", }
// 结构体数组
var line1 [2]image.Point
var line2 = [...]image.Point{image.Point{X: 0, Y: 0}, image.Point{X: 1, Y: 1}}
var line3 = [...]image.Point{{0, 0}, {1, 1}}
// 图像解码器数组
var decoder1 [2]func(io.Reader) (image.Image, error)
var decoder2 = [...]func(io.Reader) (image.Image, error){
png.Decode,
jpeg.Decode,
}
// 接口数组
var unknown1 [2]interface{}
var unknown2 = [...]interface{}{123, "你好"}
// 管道数组
var chanList = [2]chan int{}
```
我们还可以定义一个空的数组:
```go
var d [0]int // 定义一个长度为 0 的数组
var e = [0]int{} // 定义一个长度为 0 的数组
var f = [...]int{} // 定义一个长度为 0 的数组
```
长度为 0 的数组在内存中并不占用空间。空数组虽然很少直接使用,但是可以用于强调某种特有类型的操作时避免分配额外的内存空间,比如用于管道的同步操作:
```go
c1 := make(chan [0]int)
go func() {
fmt.Println("c1")
c1 <- [0]int{}
}()
<-c1
```
在这里,我们并不关心管道中传输数据的真实类型,其中管道接收和发送操作只是用于消息的同步。对于这种场景,我们用空数组来作为管道类型可以减少管道元素赋值时的开销。当然一般更倾向于用无类型的匿名结构体代替:
```go
c2 := make(chan struct{})
go func() {
fmt.Println("c2")
c2 <- struct{}{} // struct{} 部分是类型, {} 表示对应的结构体值
}()
<-c2
```
我们可以用 `fmt.Printf` 函数提供的 `%T` 或 `%#v` 谓词语法来打印数组的类型和详细信息:
```go
fmt.Printf("b: %T\n", b) // b: [3]int
fmt.Printf("b: %#v\n", b) // b: [3]int{1, 2, 3}
```
在 Go 语言中,数组类型是切片和字符串等结构的基础。以上数组的很多操作都可以直接用于字符串或切片中。
## 1.3.2 字符串
一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。由于 Go 语言的源代码要求是 UTF8 编码,导致 Go 源代码中出现的字符串面值常量一般也是 UTF8 编码的。源代码中的文本字符串通常被解释为采用 UTF8 编码的 Unicode 码点(rune)序列。因为字节序列对应的是只读的字节序列,因此字符串可以包含任意的数据,包括 byte 值 0。我们也可以用字符串表示 GBK 等非 UTF8 编码的数据,不过这种时候将字符串看作是一个只读的二进制数组更准确,因为 `for range` 等语法并不能支持非 UTF8 编码的字符串的遍历。
Go 语言字符串的底层结构在 `reflect.StringHeader` 中定义:
```go
type StringHeader struct {
Data uintptr
Len int
}
```
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。字符串其实是一个结构体,因此字符串的赋值操作也就是 `reflect.StringHeader` 结构体的复制过程,并不会涉及底层字节数组的复制。在前面数组一节提到的 `[2]string` 字符串数组对应的底层结构和 `[2]reflect.StringHeader` 对应的底层结构是一样的,可以将字符串数组看作一个结构体数组。
我们可以看看字符串“Hello, world”本身对应的内存结构:

*图 1-8 字符串布局*
分析可以发现,“Hello, world”字符串底层数据和以下数组是完全一致的:
```go
var data = [...]byte{
'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd',
}
```
字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量):
```go
s := "hello, world"
hello := s[:5]
world := s[7:]
s1 := "hello, world"[:5]
s2 := "hello, world"[7:]
```
字符串和数组类似,内置的 `len` 函数返回字符串的长度。也可以通过 `reflect.StringHeader` 结构访问字符串的长度(这里只是为了演示字符串的结构,并不是推荐的做法):
```go
fmt.Println("len(s):", (*reflect.StringHeader)(unsafe.Pointer(&s)).Len) // 12
fmt.Println("len(s1):", (*reflect.StringHeader)(unsafe.Pointer(&s1)).Len) // 5
fmt.Println("len(s2):", (*reflect.StringHeader)(unsafe.Pointer(&s2)).Len) // 5
```
根据 Go 语言规范,Go 语言的源文件都是采用 UTF8 编码。因此,Go 源文件中出现的字符串面值常量一般也是 UTF8 编码的(对于转义字符,则没有这个限制)。提到 Go 字符串时,我们一般都会假设字符串对应的是一个合法的 UTF8 编码的字符序列。可以用内置的 `print` 调试函数或 `fmt.Print` 函数直接打印,也可以用 `for range` 循环直接遍历 UTF8 解码后的 Unicode 码点值。
下面的“Hello, 世界”字符串中包含了中文字符,可以通过打印转型为字节类型来查看字符底层对应的数据:
```go
fmt.Printf("%#v\n", []byte("Hello, 世界"))
```
输出的结果是:
```go
[]byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x2c, 0x20, 0xe4, 0xb8, 0x96, 0xe7, \
0x95, 0x8c}
```
分析可以发现`0xe4, 0xb8, 0x96`对应中文“世”,`0xe7, 0x95, 0x8c`对应中文“界”。我们也可以在字符串面值中直指定 UTF8 编码后的值(源文件中全部是 ASCII 码,可以避免出现多字节的字符)。
```go
fmt.Println("\xe4\xb8\x96") // 打印: 世
fmt.Println("\xe7\x95\x8c") // 打印: 界
```
下图展示了“Hello, 世界”字符串的内存结构布局:

*图 1-9 字符串布局*
Go 语言的字符串中可以存放任意的二进制字节序列,而且即使是 UTF8 字符序列也可能会遇到坏的编码。如果遇到一个错误的 UTF8 编码输入,将生成一个特别的 Unicode 字符‘\uFFFD’,这个字符在不同的软件中的显示效果可能不太一样,在印刷中这个符号通常是一个黑色六角形或钻石形状,里面包含一个白色的问号‘�’。
下面的字符串中,我们故意损坏了第一字符的第二和第三字节,因此第一字符将会打印为“�”,第二和第三字节则被忽略,后面的“abc”依然可以正常解码打印(错误编码不会向后扩散是 UTF8 编码的优秀特性之一)。
```go
fmt.Println("\xe4\x00\x00\xe7\x95\x8cabc") // �界abc
```
不过在 `for range` 迭代这个含有损坏的 UTF8 字符串时,第一字符的第二和第三字节依然会被单独迭代到,不过此时迭代的值是损坏后的 0:
```go
for i, c := range "\xe4\x00\x00\xe7\x95\x8cabc" {
fmt.Println(i, c)
}
// 0 65533 // \uFFFD, 对应 �
// 1 0 // 空字符
// 2 0 // 空字符
// 3 30028 // 界
// 6 97 // a
// 7 98 // b
// 8 99 // c
```
如果不想解码 UTF8 字符串,想直接遍历原始的字节码,可以将字符串强制转为 `[]byte` 字节序列后再行遍历(这里的转换一般不会产生运行时开销):
```go
for i, c := range []byte("世界abc") {
fmt.Println(i, c)
}
```
或者是采用传统的下标方式遍历字符串的字节数组:
```go
const s = "\xe4\x00\x00\xe7\x95\x8cabc"
for i := 0; i < len(s); i++ {
fmt.Printf("%d %x\n", i, s[i])
}
```
Go 语言除了 `for range` 语法对 UTF8 字符串提供了特殊支持外,还对字符串和 `[]rune` 类型的相互转换提供了特殊的支持。
```go
fmt.Printf("%#v\n", []rune("世界")) // []int32{19990, 30028}
fmt.Printf("%#v\n", string([]rune{'世', '界'})) // 世界
```
从上面代码的输出结果来看,我们可以发现 `[]rune` 其实是 `[]int32` 类型,这里的 `rune` 只是 `int32` 类型的别名,并不是重新定义的类型。`rune` 用于表示每个 Unicode 码点,目前只使用了 21 个 bit 位。
字符串相关的强制类型转换主要涉及到 `[]byte` 和 `[]rune` 两种类型。每个转换都可能隐含重新分配内存的代价,最坏的情况下它们的运算时间复杂度都是 `O(n)`。不过字符串和 `[]rune` 的转换要更为特殊一些,因为一般这种强制类型转换要求两个类型的底层内存结构要尽量一致,显然它们底层对应的 `[]byte` 和 `[]int32` 类型是完全不同的内部布局,因此这种转换可能隐含重新分配内存的操作。
下面分别用伪代码简单模拟 Go 语言对字符串内置的一些操作,这样对每个操作的处理的时间复杂度和空间复杂度都会有较明确的认识。
**`for range` 对字符串的迭代模拟实现**
```go
func forOnString(s string, forBody func(i int, r rune)) {
for i := 0; len(s) > 0; {
r, size := utf8.DecodeRuneInString(s)
forBody(i, r)
s = s[size:]
i += size
}
}
```
`for range` 迭代字符串时,每次解码一个 Unicode 字符,然后进入 `for` 循环体,遇到崩坏的编码并不会导致迭代停止。
**`[]byte(s)` 转换模拟实现**
```go
func str2bytes(s string) []byte {
p := make([]byte, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
p[i] = c
}
return p
}
```
模拟实现中新创建了一个切片,然后将字符串的数组逐一复制到了切片中,这是为了保证字符串只读的语义。当然,在将字符串转为 `[]byte` 时,如果转换后的变量并没有被修改的情形,编译器可能会直接返回原始的字符串对应的底层数据。
**`string(bytes)` 转换模拟实现**
```go
func bytes2str(s []byte) (p string) {
data := make([]byte, len(s))
for i, c := range s {
data[i] = c
}
hdr := (*reflect.StringHeader)(unsafe.Pointer(&p))
hdr.Data = uintptr(unsafe.Pointer(&data[0]))
hdr.Len = len(s)
return p
}
```
因为 Go 语言的字符串是只读的,无法直接同构构造底层字节数组生成字符串。在模拟实现中通过 `unsafe` 包获取了字符串的底层数据结构,然后将切片的数据逐一复制到了字符串中,这同样是为了保证字符串只读的语义不会受切片的影响。如果转换后的字符串在生命周期中原始的 `[]byte` 的变量并不会发生变化,编译器可能会直接基于 `[]byte` 底层的数据构建字符串。
**`[]rune(s)` 转换模拟实现**
```go
func str2runes(s string) []rune{
var p []int32
for len(s)>0 {
r,size:=utf8.DecodeRuneInString(s)
p=append(p,int32(r))
s=s[size:]
}
return []rune(p)
}
```
因为底层内存结构的差异,字符串到 `[]rune` 的转换必然会导致重新分配 `[]rune` 内存空间,然后依次解码并复制对应的 Unicode 码点值。这种强制转换并不存在前面提到的字符串和字节切片转化时的优化情况。
**`string(runes)` 转换模拟实现**
```go
func runes2string(s []int32) string {
var p []byte
buf := make([]byte, 3)
for _, r := range s {
n := utf8.EncodeRune(buf, r)
p = append(p, buf[:n]...)
}
return string(p)
}
```
同样因为底层内存结构的差异,`[]rune` 到字符串的转换也必然会导致重新构造字符串。这种强制转换并不存在前面提到的优化情况。
## 1.3.3 切片(slice)
简单地说,切片就是一种简化版的动态数组。因为动态数组的长度是不固定,切片的长度自然也就不能是类型的组成部分了。数组虽然有适用它们的地方,但是数组的类型和操作都不够灵活,因此在 Go 代码中数组使用的并不多。而切片则使用得相当广泛,理解切片的原理和用法是一个 Go 程序员的必备技能。
我们先看看切片的结构定义,`reflect.SliceHeader`:
```go
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
```
可以看出切片的开头部分和 Go 字符串是一样的,但是切片多了一个 `Cap` 成员表示切片指向的内存空间的最大容量,由起始索引位置到 `Data` 末尾的元素数量(而不是字节数)决定。下图是 `x := []int{2,3,5,7,11}` 和 `y := x[1:3]` 两个切片对应的内存结构。

*图 1-10 切片布局*
让我们看看切片有哪些定义方式:
```go
var (
a []int // nil 切片, 和 nil 相等, 一般用来表示一个不存在的切片
b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
c = []int{1, 2, 3} // 有 3 个元素的切片, len 和 cap 都为 3
d = c[:2] // 有 2 个元素的切片, len 为 2, cap 为 3
e = c[0:2:cap(c)] // 有 2 个元素的切片, len 为 2, cap 为 3
f = c[:0] // 有 0 个元素的切片, len 为 0, cap 为 3
g = c[1:2] // 有 1 个元素的切片, len 为 1, cap 为 2
h = make([]int, 3) // 有 3 个元素的切片, len 和 cap 都为 3
i = make([]int, 2, 3) // 有 2 个元素的切片, len 为 2, cap 为 3
j = make([]int, 0, 3) // 有 0 个元素的切片, len 为 0, cap 为 3
)
```
和数组一样,内置的 `len` 函数返回切片中有效元素的长度,内置的 `cap` 函数返回切片容量大小,容量必须大于或等于切片的长度。也可以通过 `reflect.SliceHeader` 结构访问切片的信息(只是为了说明切片的结构,并不是推荐的做法)。切片可以和 `nil` 进行比较,只有当切片底层数据指针为空时切片本身为 `nil`,这时候切片的长度和容量信息将是无效的。如果有切片的底层数据指针为空,但是长度和容量不为 0 的情况,那么说明切片本身已经被损坏了(比如直接通过 `reflect.SliceHeader` 或 `unsafe` 包对切片作了不正确的修改)。
遍历切片的方式和遍历数组的方式类似:
```go
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}
```
其实除了遍历之外,只要是切片的底层数据指针、长度和容量没有发生变化的话,对切片的遍历、元素的读取和修改都和数组是一样的。在对切片本身赋值或参数传递时,和数组指针的操作方式类似,只是复制切片头信息(`reflect.SliceHeader`),并不会复制底层的数据。对于类型,和数组的最大不同是,切片的类型和长度信息无关,只要是相同类型元素构成的切片均对应相同的切片类型。
如前所说,切片是一种简化版的动态数组,这是切片类型的灵魂。除了构造切片和遍历切片之外,添加切片元素、删除切片元素都是切片处理中经常遇到的问题。
**添加切片元素**
内置的泛型函数 `append` 可以在切片的尾部追加 `N` 个元素:
```go
var a []int
a = append(a, 1) // 追加 1 个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加 1 个切片, 切片需要解包
```
不过要注意的是,在容量不足的情况下,`append` 的操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价。即使容量足够,依然需要用 `append` 函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。
除了在切片的尾部追加,我们还可以在切片的开头添加元素:
```go
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加 1 个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加 1 个切片
```
在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制 1 次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多。
由于 `append` 函数返回新的切片,也就是它支持链式操作。我们可以将多个 `append` 操作组合起来,实现在切片中间插入元素:
```go
var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第 i 个位置插入 x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第 i 个位置插入切片
```
每个添加操作中的第二个 `append` 调用都会创建一个临时切片,并将 `a[i:]` 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 `a[:i]`。
可以用 `copy` 和 `append` 组合可以避免创建中间的临时切片,同样是完成添加元素的操作:
```go
a = append(a, 0) // 切片扩展 1 个空间
copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置
a[i] = x // 设置新添加的元素
```
第一句 `append` 用于扩展切片的长度,为要插入的元素留出空间。第二句 `copy` 操作将要插入位置开始之后的元素向后挪动一个位置。第三句真实地将新添加的元素赋值到对应的位置。操作语句虽然冗长了一点,但是相比前面的方法,可以减少中间创建的临时切片。
用 `copy` 和 `append` 组合也可以实现在中间位置插入多个元素(也就是插入一个切片):
```go
a = append(a, x...) // 为 x 切片扩展足够的空间
copy(a[i+len(x):], a[i:]) // a[i:] 向后移动 len(x) 个位置
copy(a[i:], x) // 复制新添加的切片
```
稍显不足的是,在第一句扩展切片容量的时候,扩展空间部分的元素复制是没有必要的。没有专门的内置函数用于扩展切片的容量,`append` 本质是用于追加元素而不是扩展容量,扩展切片容量只是 `append` 的一个副作用。
**删除切片元素**
根据要删除元素的位置有三种情况:从开头位置删除,从中间位置删除,从尾部删除。其中删除切片尾部的元素最快:
```go
a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部 1 个元素
a = a[:len(a)-N] // 删除尾部 N 个元素
```
删除开头的元素可以直接移动数据指针:
```go
a = []int{1, 2, 3}
a = a[1:] // 删除开头 1 个元素
a = a[N:] // 删除开头 N 个元素
```
删除开头的元素也可以不移动数据指针,但是将后面的数据向开头移动。可以用 `append` 原地完成(所谓原地完成是指在原有的切片数据对应的内存区间内完成,不会导致内存空间结构的变化):
```go
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头 1 个元素
a = append(a[:0], a[N:]...) // 删除开头 N 个元素
```
也可以用 `copy` 完成删除开头的元素:
```go
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头 1 个元素
a = a[:copy(a, a[N:])] // 删除开头 N 个元素
```
对于删除中间的元素,需要对剩余的元素进行一次整体挪动,同样可以用 `append` 或 `copy` 原地完成:
```go
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间 1 个元素
a = append(a[:i], a[i+N:]...) // 删除中间 N 个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间 1 个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间 N 个元素
```
删除开头的元素和删除尾部的元素都可以认为是删除中间元素操作的特殊情况。
**切片内存技巧**
在本节开头的数组部分我们提到过有类似 `[0]int` 的空数组,空数组一般很少用到。但是对于切片来说,`len` 为 `0` 但是 `cap` 容量不为 `0` 的切片则是非常有用的特性。当然,如果 `len` 和 `cap` 都为 `0` 的话,则变成一个真正的空切片,虽然它并不是一个 `nil` 值的切片。在判断一个切片是否为空时,一般通过 `len` 获取切片的长度来判断,一般很少将切片和 `nil` 值做直接的比较。
比如下面的 `TrimSpace` 函数用于删除 `[]byte` 中的空格。函数实现利用了 0 长切片的特性,实现高效而且简洁。
```go
func TrimSpace(s []byte) []byte {
b := s[:0]
for _, x := range s {
if x != ' ' {
b = append(b, x)
}
}
return b
}
```
其实类似的根据过滤条件原地删除切片元素的算法都可以采用类似的方式处理(因为是删除操作不会出现内存不足的情形):
```go
func Filter(s []byte, fn func(x byte) bool) []byte {
b := s[:0]
for _, x := range s {
if !fn(x) {
b = append(b, x)
}
}
return b
}
```
切片高效操作的要点是要降低内存分配的次数,尽量保证 `append` 操作不会超出 `cap` 的容量,降低触发内存分配的次数和每次分配内存大小。
**避免切片内存泄漏**
如前面所说,切片操作并不会复制底层的数据。底层的数组会被保存在内存中,直到它不再被引用。但是有时候可能会因为一个小的内存引用而导致底层整个数组处于被使用的状态,这会延迟自动内存回收器对底层数组的回收。
例如,`FindPhoneNumber` 函数加载整个文件到内存,然后搜索第一个出现的电话号码,最后结果以切片方式返回。
```go
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return regexp.MustCompile("[0-9]+").Find(b)
}
```
这段代码返回的 `[]byte` 指向保存整个文件的数组。因为切片引用了整个原始数组,导致自动垃圾回收器不能及时释放底层数组的空间。一个小的需求可能导致需要长时间保存整个文件数据。这虽然这并不是传统意义上的内存泄漏,但是可能会拖慢系统的整体性能。
要修复这个问题,可以将感兴趣的数据复制到一个新的切片中(数据的传值是 Go 语言编程的一个哲学,虽然传值有一定的代价,但是换取的好处是切断了对原始数据的依赖):
```go
func FindPhoneNumber(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = regexp.MustCompile("[0-9]+").Find(b)
return append([]byte{}, b...)
}
```
类似的问题,在删除切片元素时可能会遇到。假设切片里存放的是指针对象,那么下面删除末尾的元素后,被删除的元素依然被切片底层数组引用,从而导致不能及时被自动垃圾回收器回收(这要依赖回收器的实现方式):
```go
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后一个元素依然被引用, 可能导致 GC 操作被阻碍
```
保险的方式是先将需要自动内存回收的元素设置为 `nil`,保证自动回收器可以发现需要回收的对象,然后再进行切片的删除操作:
```go
var a []*int{ ... }
a[len(a)-1] = nil // GC 回收最后一个元素内存
a = a[:len(a)-1] // 从切片删除最后一个元素
```
当然,如果切片存在的周期很短的话,可以不用刻意处理这个问题。因为如果切片本身已经可以被 GC 回收的话,切片对应的每个元素自然也就是可以被回收的了。
**切片类型强制转换**
为了安全,当两个切片类型 `[]T` 和 `[]Y` 的底层原始切片类型不同时,Go 语言是无法直接转换类型的。不过安全都是有一定代价的,有时候这种转换是有它的价值的——可以简化编码或者是提升代码的性能。比如在 64 位系统上,需要对一个 `[]float64` 切片进行高速排序,我们可以将它强制转为 `[]int` 整数切片,然后以整数的方式进行排序(因为 `float64` 遵循 IEEE754 浮点数标准特性,当浮点数有序时对应的整数也必然是有序的)。
下面的代码通过两种方法将 `[]float64` 类型的切片转换为 `[]int` 类型的切片:
```go
// +build amd64 arm64
import "sort"
var a = []float64{4, 2, 5, 7, 2, 1, 88, 1}
func SortFloat64FastV1(a []float64) {
// 强制类型转换
var b []int = ((*[1 << 20]int)(unsafe.Pointer(&a[0])))[:len(a):cap(a)]
// 以 int 方式给 float64 排序
sort.Ints(b)
}
func SortFloat64FastV2(a []float64) {
// 通过 reflect.SliceHeader 更新切片头部信息实现转换
var c []int
aHdr := (*reflect.SliceHeader)(unsafe.Pointer(&a))
cHdr := (*reflect.SliceHeader)(unsafe.Pointer(&c))
*cHdr = *aHdr
// 以 int 方式给 float64 排序
sort.Ints(c)
}
```
第一种强制转换是先将切片数据的开始地址转换为一个较大的数组的指针,然后对数组指针对应的数组重新做切片操作。中间需要 `unsafe.Pointer` 来连接两个不同类型的指针传递。需要注意的是,Go语言实现中非0大小数组的长度不得超过 2GB,因此需要针对数组元素的类型大小计算数组的最大长度范围(`[]uint8` 最大 2GB,`[]uint16` 最大 1GB,以此类推,但是 `[]struct{}` 数组的长度可以超过 2GB)。
第二种转换操作是分别取到两个不同类型的切片头信息指针,任何类型的切片头部信息底层都是对应 `reflect.SliceHeader` 结构,然后通过更新结构体方式来更新切片信息,从而实现 `a` 对应的 `[]float64` 切片到 `c` 对应的 `[]int` 类型切片的转换。
通过基准测试,我们可以发现用 `sort.Ints` 对转换后的 `[]int` 排序的性能要比用 `sort.Float64s` 排序的性能好一点。不过需要注意的是,这个方法可行的前提是要保证 `[]float64` 中没有 NaN 和 Inf 等非规范的浮点数(因为浮点数中 NaN 不可排序,正 0 和负 0 相等,但是整数中没有这类情形)。
================================================
FILE: ch1-basic/ch1-04-func-method-interface.md
================================================
# 1.4 函数、方法和接口
函数对应操作序列,是程序的基本组成元素。Go 语言中的函数有具名和匿名之分:具名函数一般对应于包级的函数,是匿名函数的一种特例,当匿名函数引用了外部作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的核心。方法是绑定到一个具体类型的特殊函数,Go 语言中的方法是依托于类型的,必须在编译时静态绑定。接口定义了方法的集合,这些方法依托于运行时的接口对象,因此接口对应的方法是在运行时动态绑定的。Go 语言通过隐式接口机制实现了鸭子面向对象模型。
Go 语言程序的初始化和执行总是从 `main.main` 函数开始的。但是如果 `main` 包导入了其它的包,则会按照顺序将它们包含进 `main` 包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量,再调用包里的 `init` 函数,如果一个包有多个 `init` 函数的话,调用顺序未定义(实现可能是以文件名的顺序调用),同一个文件内的多个 `init` 则是以出现的顺序依次调用(`init` 不是普通函数,可以定义有多个,所以也不能被其它函数调用)。最后,当 `main` 包的所有包级常量、变量被创建和初始化完成,并且 `init` 函数被执行后,才会进入 `main.main` 函数,程序开始正常执行。下图是 Go 程序函数启动顺序的示意图:

*图 1-11 包初始化流程*
要注意的是,在 `main.main` 函数执行之前所有代码都运行在同一个 Goroutine 中,也是运行在程序的主系统线程中。如果某个 `init` 函数内部用 go 关键字启动了新的 Goroutine 的话,新的 Goroutine 和 `main.main` 函数是并发执行的。
## 1.4.1 函数
在 Go 语言中,函数是第一类对象,我们可以将函数保持到变量中。函数主要有具名和匿名之分,包级函数一般都是具名函数,具名函数是匿名函数的一种特例。当然,Go 语言中每个类型还可以有自己的方法,方法其实也是函数的一种。
```go
// 具名函数
func Add(a, b int) int {
return a+b
}
// 匿名函数
var Add = func(a, b int) int {
return a+b
}
```
Go 语言中的函数可以有多个参数和多个返回值,参数和返回值都是以传值的方式和被调用者交换数据。在语法上,函数还支持可变数量的参数,可变数量的参数必须是最后出现的参数,可变数量的参数其实是一个切片类型的参数。
```go
// 多个参数和多个返回值
func Swap(a, b int) (int, int) {
return b, a
}
// 可变数量的参数
// more 对应 []int 切片类型
func Sum(a int, more ...int) int {
for _, v := range more {
a += v
}
return a
}
```
当可变参数是一个空接口类型时,调用者是否解包可变参数会导致不同的结果:
```go
func main() {
var a = []interface{}{123, "abc"}
Print(a...) // 123 abc
Print(a) // [123 abc]
}
func Print(a ...interface{}) {
fmt.Println(a...)
}
```
第一个 `Print` 调用时传入的参数是 `a...`,等价于直接调用 `Print(123, "abc")`。第二个 `Print` 调用传入的是未解包的 `a`,等价于直接调用 `Print([]interface{}{123, "abc"})`。
不仅函数的参数可以有名字,也可以给函数的返回值命名:
```go
func Find(m map[int]int, key int) (value int, ok bool) {
value, ok = m[key]
return
}
```
如果返回值命名了,可以通过名字来修改返回值,也可以通过 `defer` 语句在 `return` 语句之后修改返回值:
```go
func Inc() (v int) {
defer func(){ v++ } ()
return 42
}
```
其中 `defer` 语句延迟执行了一个匿名函数,因为这个匿名函数捕获了外部函数的局部变量 `v`,这种函数我们一般叫闭包。闭包对捕获的外部变量并不是传值方式访问,而是以引用的方式访问。
闭包的这种引用方式访问外部变量的行为可能会导致一些隐含的问题:
```go
func main() {
for i := 0; i < 3; i++ {
defer func(){ println(i) } ()
}
}
// Output:
// 3
// 3
// 3
```
因为是闭包,在 `for` 迭代语句中,每个 `defer` 语句延迟执行的函数引用的都是同一个 `i` 迭代变量,在循环结束后这个变量的值为 3,因此最终输出的都是3。
修复的思路是在每轮迭代中为每个 `defer` 函数生成独有的变量。可以用下面两种方式:
```go
func main() {
for i := 0; i < 3; i++ {
i := i // 定义一个循环体内局部变量 i
defer func(){ println(i) } ()
}
}
func main() {
for i := 0; i < 3; i++ {
// 通过函数传入 i
// defer 语句会马上对调用参数求值
defer func(i int){ println(i) } (i)
}
}
```
第一种方法是在循环体内部再定义一个局部变量,这样每次迭代 `defer` 语句的闭包函数捕获的都是不同的变量,这些变量的值对应迭代时的值。第二种方式是将迭代变量通过闭包函数的参数传入,`defer` 语句会马上对调用参数求值。两种方式都是可以工作的。不过一般来说,在 `for` 循环内部执行 `defer` 语句并不是一个好的习惯,此处仅为示例,不建议使用。
Go 语言中,如果以切片为参数调用函数时,有时候会给人一种参数采用了传引用的方式的假象:因为在被调用函数内部可以修改传入的切片的元素。其实,任何可以通过函数参数修改调用参数的情形,都是因为函数参数中显式或隐式传入了指针参数。函数参数传值的规范更准确说是只针对数据结构中固定的部分传值,例如字符串或切片作为参数时实际传递的是 `reflect.StringHeader` 或`reflect.SliceHeader` 结构体,结构体是以整体复制的方式传递的。字符串或切片对应的结构体中的指针、长度和容量传值,但是并不包含指针间接指向的内容。将切片类型的参数替换为类似 `reflect.SliceHeader` 结构体就很好理解切片传值的含义了:
```go
func twice(x []int) {
for i := range x {
x[i] *= 2
}
}
type IntSliceHeader struct {
Data []int
Len int
Cap int
}
func twice(x IntSliceHeader) {
for i := 0; i < x.Len; i++ {
x.Data[i] *= 2
}
}
```
因为切片中的底层数组部分是通过隐式指针(即`reflect.SliceHeader` 里的`Data`成员)传递(指针本身依然是传值的,但是指针指向的却是同一份的数据),所以被调用函数是可以通过指针修改掉调用参数切片中的数据。除了数据之外,切片结构体还包含了切片长度和切片容量信息,这2个信息也是传值的。如果被调用函数中修改了 `Len` 或 `Cap` 信息的话,就无法反映到调用参数的切片中,这时候我们一般会通过返回修改后的切片来更新之前的切片。这也是为何内置的 `append` 必须要返回一个切片的原因。
Go语言中,函数还可以直接或间接地调用自己,也就是支持递归调用。Go 语言函数的递归调用深度逻辑上没有限制,函数调用的栈是不会出现溢出错误的,因为 Go 语言运行时会根据需要动态地调整函数栈的大小。每个 goroutine 刚启动时只会分配很小的栈(4 或 8KB,具体依赖实现),根据需要动态调整栈的大小,栈最大可以达到 GB 级(依赖具体实现,在目前的实现中,32 位体系结构为 250MB,64 位体系结构为 1GB)。在 Go1.4 以前,Go 的动态栈采用的是分段式的动态栈,通俗地说就是采用一个链表来实现动态栈,每个链表的节点内存位置不会发生变化。但是链表实现的动态栈对某些导致跨越链表不同节点的热点调用的性能影响较大,因为相邻的链表节点它们在内存位置一般不是相邻的,这会增加 CPU 高速缓存命中失败的几率。为了解决热点调用的 CPU 缓存命中率问题,Go1.4 之后改用连续的动态栈实现,也就是采用一个类似动态数组的结构来表示栈。不过连续动态栈也带来了新的问题:当连续栈动态增长时,需要将之前的数据移动到新的内存空间,这会导致之前栈中全部变量的地址发生变化。虽然 Go 语言运行时会自动更新引用了地址变化的栈变量的指针,但最重要的一点是要明白 Go 语言中指针不再是固定不变的了(因此不能随意将指针保持到数值变量中,Go 语言的地址也不能随意保存到不在 GC 控制的环境中,因此使用 CGO 时不能在 C 语言中长期持有 Go 语言对象的地址)。
因为,Go 语言函数的栈会自动调整大小,所以普通 Go 程序员已经很少需要关心栈的运行机制的。在 Go 语言规范中甚至故意没有讲到栈和堆的概念。我们无法知道函数参数或局部变量到底是保存在栈中还是堆中,我们只需要知道它们能够正常工作就可以了。看看下面这个例子:
```go
func f(x int) *int {
return &x
}
func g() int {
x := new(int)
return *x
}
```
第一个函数直接返回了函数参数变量的地址——这似乎是不可以的,因为如果参数变量在栈上的话,函数返回之后栈变量就失效了,返回的地址自然也应该失效了。但是 Go 语言的编译器和运行时比我们聪明的多,它会保证指针指向的变量在合适的地方。第二个函数,内部虽然调用 `new` 函数创建了 `*int` 类型的指针对象,但是依然不知道它具体保存在哪里。对于有 C/C++ 编程经验的程序员需要强调的是:不用关心 Go 语言中函数栈和堆的问题,编译器和运行时会帮我们搞定;同样不要假设变量在内存中的位置是固定不变的,指针随时可能会变化,特别是在你不期望它变化的时候。
## 1.4.2 方法
方法一般是面向对象编程(OOP)的一个特性,在 C++ 语言中方法对应一个类对象的成员函数,是关联到具体对象上的虚表中的。但是 Go 语言的方法却是关联到类型的,这样可以在编译阶段完成方法的静态绑定。一个面向对象的程序会用方法来表达其属性对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情。面向对象编程进入主流开发领域一般认为是从 C++ 开始的,C++ 就是在兼容 C 语言的基础之上支持了 class 等面向对象的特性。然后 Java 编程则号称是纯粹的面向对象语言,因为 Java 中函数是不能独立存在的,每个函数都必然是属于某个类的。
面向对象编程更多的只是一种思想,很多号称支持面向对象编程的语言只是将经常用到的特性内置到语言中了而已。Go 语言的祖先 C 语言虽然不是一个支持面向对象的语言,但是 C 语言的标准库中的 File 相关的函数也用到了的面向对象编程的思想。下面我们实现一组 C 语言风格的 File 函数:
```go
// 文件对象
type File struct {
fd int
}
// 打开文件
func OpenFile(name string) (f *File, err error) {
// ...
}
// 关闭文件
func CloseFile(f *File) error {
// ...
}
// 读文件数据
func ReadFile(f *File, offset int64, data []byte) int {
// ...
}
```
其中 `OpenFile` 类似构造函数用于打开文件对象,`CloseFile` 类似析构函数用于关闭文件对象,`ReadFile` 则类似普通的成员函数,这三个函数都是普通的函数。`CloseFile` 和 `ReadFile` 作为普通函数,需要占用包级空间中的名字资源。不过 `CloseFile` 和 `ReadFile` 函数只是针对 `File` 类型对象的操作,这时候我们更希望这类函数和操作对象的类型紧密绑定在一起。
Go 语言中的做法是,将 `CloseFile` 和 `ReadFile` 函数的第一个参数移动到函数名的开头:
```go
// 关闭文件
func (f *File) CloseFile() error {
// ...
}
// 读文件数据
func (f *File) ReadFile(offset int64, data []byte) int {
// ...
}
```
这样的话,`CloseFile` 和 `ReadFile` 函数就成了 `File` 类型独有的方法了(而不是 `File` 对象方法)。它们也不再占用包级空间中的名字资源,同时 `File` 类型已经明确了它们操作对象,因此方法名字一般简化为 `Close` 和 `Read`:
```go
// 关闭文件
func (f *File) Close() error {
// ...
}
// 读文件数据
func (f *File) Read(offset int64, data []byte) int {
// ...
}
```
将第一个函数参数移动到函数前面,从代码角度看虽然只是一个小的改动,但是从编程哲学角度来看,Go 语言已经是进入面向对象语言的行列了。我们可以给任何自定义类型添加一个或多个方法。每种类型对应的方法必须和类型的定义在同一个包中,因此是无法给 `int` 这类内置类型添加方法的(因为方法的定义和类型的定义不在一个包中)。对于给定的类型,每个方法的名字必须是唯一的,同时方法和函数一样也不支持重载。
方法是由函数演变而来,只是将函数的第一个对象参数移动到了函数名前面了而已。因此我们依然可以按照原始的过程式思维来使用方法。通过叫方法表达式的特性可以将方法还原为普通类型的函数:
```go
// 不依赖具体的文件对象
// func CloseFile(f *File) error
var CloseFile = (*File).Close
// 不依赖具体的文件对象
// func ReadFile(f *File, offset int64, data []byte) int
var ReadFile = (*File).Read
// 文件处理
f, _ := OpenFile("foo.dat")
ReadFile(f, 0, data)
CloseFile(f)
```
在有些场景更关心一组相似的操作:比如 `Read` 读取一些数组,然后调用 `Close` 关闭。此时的环境中,用户并不关心操作对象的类型,只要能满足通用的 `Read` 和 `Close` 行为就可以了。不过在方法表达式中,因为得到的 `ReadFile` 和 `CloseFile` 函数参数中含有 `File` 这个特有的类型参数,这使得 `File` 相关的方法无法和其它不是 `File` 类型但是有着相同 `Read` 和 `Close` 方法的对象无缝适配。这种小困难难不倒我们 Go 语言码农,我们可以通过结合闭包特性来消除方法表达式中第一个参数类型的差异:
```go
// 先打开文件对象
f, _ := OpenFile("foo.dat")
// 绑定到了 f 对象
// func Close() error
var Close = func() error {
return (*File).Close(f)
}
// 绑定到了 f 对象
// func Read(offset int64, data []byte) int
var Read = func(offset int64, data []byte) int {
return (*File).Read(f, offset, data)
}
// 文件处理
Read(0, data)
Close()
```
这刚好是方法值也要解决的问题。我们用方法值特性可以简化实现:
```go
// 先打开文件对象
f, _ := OpenFile("foo.dat")
// 方法值: 绑定到了 f 对象
// func Close() error
var Close = f.Close
// 方法值: 绑定到了 f 对象
// func Read(offset int64, data []byte) int
var Read = f.Read
// 文件处理
Read(0, data)
Close()
```
Go语言不支持传统面向对象中的继承特性,而是以自己特有的组合方式支持了方法的继承。Go 语言中,通过在结构体内置匿名的成员来实现继承:
```go
import "image/color"
type Point struct{ X, Y float64 }
type ColoredPoint struct {
Point
Color color.RGBA
}
```
虽然我们可以将 `ColoredPoint` 定义为一个有三个字段的扁平结构的结构体,但是我们这里将 `Point` 嵌入到 `ColoredPoint` 来提供 `X` 和 `Y` 这两个字段。
```go
var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X) // "1"
cp.Point.Y = 2
fmt.Println(cp.Y) // "2"
```
通过嵌入匿名的成员,我们不仅可以继承匿名成员的内部成员,而且可以继承匿名成员类型所对应的方法。我们一般会将 Point 看作基类,把 ColoredPoint 看作是它的继承类或子类。不过这种方式继承的方法并不能实现 C++ 中虚函数的多态特性。所有继承来的方法的接收者参数依然是那个匿名成员本身,而不是当前的变量。
```go
type Cache struct {
m map[string]string
sync.Mutex
}
func (p *Cache) Lookup(key string) string {
p.Lock()
defer p.Unlock()
return p.m[key]
}
```
`Cache`结构体类型通过嵌入一个匿名的 `sync.Mutex` 来继承它的 `Lock` 和 `Unlock` 方法. 但是在调用 `p.Lock()` 和 `p.Unlock()` 时, `p` 并不是 `Lock` 和 `Unlock` 方法的真正接收者, 而是会将它们展开为 `p.Mutex.Lock()` 和 `p.Mutex.Unlock()` 调用. 这种展开是编译期完成的, 并没有运行时代价.
在传统的面向对象语言(eg.C++ 或 Java)的继承中,子类的方法是在运行时动态绑定到对象的,因此基类实现的某些方法看到的 `this` 可能不是基类类型对应的对象,这个特性会导致基类方法运行的不确定性。而在 Go 语言通过嵌入匿名的成员来“继承”的基类方法,`this` 就是实现该方法的类型的对象,Go 语言中方法是编译时静态绑定的。如果需要虚函数的多态特性,我们需要借助 Go 语言接口来实现。
## 1.4.3 接口
Go 语言之父 *Rob Pike* 曾说过一句名言:那些试图避免白痴行为的语言最终自己变成了白痴语言(Languages that try to disallow idiocy become themselves idiotic)。一般静态编程语言都有着严格的类型系统,这使得编译器可以深入检查程序员有没有作出什么出格的举动。但是,过于严格的类型系统却会使得编程太过繁琐,让程序员把大好的青春都浪费在了和编译器的斗争中。Go 语言试图让程序员能在安全和灵活的编程之间取得一个平衡。它在提供严格的类型检查的同时,通过接口类型实现了对鸭子类型的支持,使得安全动态的编程变得相对容易。
Go 的接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起,通过这种抽象的方式我们可以让对象更加灵活和更具有适应能力。很多面向对象的语言都有相似的接口概念,但 Go 语言中接口类型的独特之处在于它是满足隐式实现的鸭子类型。所谓鸭子类型说的是:只要走起路来像鸭子、叫起来也像鸭子,那么就可以把它当作鸭子。Go 语言中的面向对象就是如此,如果一个对象只要看起来像是某种接口类型的实现,那么它就可以作为该接口类型使用。这种设计可以让你创建一个新的接口类型满足已经存在的具体类型却不用去破坏这些类型原有的定义;当我们使用的类型来自于不受我们控制的包时这种设计尤其灵活有用。Go 语言的接口类型是延迟绑定,可以实现类似虚函数的多态功能。
接口在 Go 语言中无处不在,在“Hello world”的例子中,`fmt.Printf` 函数的设计就是完全基于接口的,它的真正功能由 `fmt.Fprintf` 函数完成。用于表示错误的 `error` 类型更是内置的接口类型。在 C 语言中,`printf` 只能将几种有限的基础数据类型打印到文件对象中。但是 Go 语言灵活接口特性,`fmt.Fprintf` 却可以向任何自定义的输出流对象打印,可以打印到文件或标准输出、也可以打印到网络、甚至可以打印到一个压缩文件;同时,打印的数据也不仅仅局限于语言内置的基础类型,任意隐式满足 `fmt.Stringer` 接口的对象都可以打印,不满足 `fmt.Stringer` 接口的依然可以通过反射的技术打印。`fmt.Fprintf` 函数的签名如下:
```go
func Fprintf(w io.Writer, format string, args ...interface{}) (int, error)
```
其中 `io.Writer` 用于输出的接口,`error` 是内置的错误接口,它们的定义如下:
```go
type io.Writer interface {
Write(p []byte) (n int, err error)
}
type error interface {
Error() string
}
```
我们可以通过定制自己的输出对象,将每个字符转为大写字符后输出:
```go
type UpperWriter struct {
io.Writer
}
func (p *UpperWriter) Write(data []byte) (n int, err error) {
return p.Writer.Write(bytes.ToUpper(data))
}
func main() {
fmt.Fprintln(&UpperWriter{os.Stdout}, "hello, world")
}
```
当然,我们也可以定义自己的打印格式来实现将每个字符转为大写字符后输出的效果。对于每个要打印的对象,如果满足了 `fmt.Stringer` 接口,则默认使用对象的 `String` 方法返回的结果打印:
```go
type UpperString string
func (s UpperString) String() string {
return strings.ToUpper(string(s))
}
type fmt.Stringer interface {
String() string
}
func main() {
fmt.Fprintln(os.Stdout, UpperString("hello, world"))
}
```
Go 语言中,对于基础类型(非接口类型)不支持隐式的转换,我们无法将一个 `int` 类型的值直接赋值给 `int64` 类型的变量,也无法将 `int` 类型的值赋值给底层是 `int` 类型的新定义命名类型的变量。Go 语言对基础类型的类型一致性要求可谓是非常的严格,但是 Go 语言对于接口类型的转换则非常的灵活。对象和接口之间的转换、接口和接口之间的转换都可能是隐式的转换。可以看下面的例子:
```go
var (
a io.ReadCloser = (*os.File)(f) // 隐式转换, *os.File 满足 io.ReadCloser 接口
b io.Reader = a // 隐式转换, io.ReadCloser 满足 io.Reader 接口
c io.Closer = a // 隐式转换, io.ReadCloser 满足 io.Closer 接口
d io.Reader = c.(io.Reader) // 显式转换, io.Closer 不满足 io.Reader 接口
)
```
有时候对象和接口之间太灵活了,导致我们需要人为地限制这种无意之间的适配。常见的做法是定义一个含特殊方法来区分接口。比如 `runtime` 包中的 `Error` 接口就定义了一个特有的 `RuntimeError` 方法,用于避免其它类型无意中适配了该接口:
```go
type runtime.Error interface {
error
// RuntimeError is a no-op function but
// serves to distinguish types that are run time
// errors from ordinary errors: a type is a
// run time error if it has a RuntimeError method.
RuntimeError()
}
```
在 protobuf 中,`Message` 接口也采用了类似的方法,也定义了一个特有的 `ProtoMessage`,用于避免其它类型无意中适配了该接口:
```go
type proto.Message interface {
Reset()
String() string
ProtoMessage()
}
```
不过这种做法只是君子协定,如果有人刻意伪造一个 `proto.Message` 接口也是很容易的。再严格一点的做法是给接口定义一个私有方法。只有满足了这个私有方法的对象才可能满足这个接口,而私有方法的名字是包含包的绝对路径名的,因此只能在包内部实现这个私有方法才能满足这个接口。测试包中的 `testing.TB` 接口就是采用类似的技术:
```go
type testing.TB interface {
Error(args ...interface{})
Errorf(format string, args ...interface{})
...
// A private method to prevent users implementing the
// interface and so future additions to it will not
// violate Go 1 compatibility.
private()
}
```
不过这种通过私有方法禁止外部对象实现接口的做法也是有代价的:首先是这个接口只能包内部使用,外部包正常情况下是无法直接创建满足该接口对象的;其次,这种防护措施也不是绝对的,恶意的用户依然可以绕过这种保护机制。
在前面的方法一节中我们讲到,通过在结构体中嵌入匿名类型成员,可以继承匿名类型的方法。其实这个被嵌入的匿名成员不一定是普通类型,也可以是接口类型。我们可以通过嵌入匿名的 `testing.TB` 接口来伪造私有的 `private` 方法,因为接口方法是延迟绑定,编译时 `private` 方法是否真的存在并不重要。
```go
package main
import (
"fmt"
"testing"
)
type TB struct {
testing.TB
}
func (p *TB) Fatal(args ...interface{}) {
fmt.Println("TB.Fatal disabled!")
}
func main() {
var tb testing.TB = new(TB)
tb.Fatal("Hello, playground")
}
```
我们在自己的 `TB` 结构体类型中重新实现了 `Fatal` 方法,然后通过将对象隐式转换为 `testing.TB` 接口类型(因为内嵌了匿名的 `testing.TB` 对象,因此是满足 `testing.TB` 接口的),然后通过 `testing.TB` 接口来调用我们自己的 `Fatal` 方法。
这种通过嵌入匿名接口或嵌入匿名指针对象来实现继承的做法其实是一种纯虚继承,我们继承的只是接口指定的规范,真正的实现在运行的时候才被注入。比如,我们可以模拟实现一个gRPC的插件:
```go
type grpcPlugin struct {
*generator.Generator
}
func (p *grpcPlugin) Name() string { return "grpc" }
func (p *grpcPlugin) Init(g *generator.Generator) {
p.Generator = g
}
func (p *grpcPlugin) GenerateImports(file *generator.FileDescriptor) {
if len(file.Service) == 0 {
return
}
p.P(`import "google.golang.org/grpc"`)
// ...
}
```
构造的 `grpcPlugin` 类型对象必须满足 `generate.Plugin` 接口(在"github.com/golang/protobuf/protoc-gen-go/generator"包中):
```go
type Plugin interface {
// Name identifies the plugin.
Name() string
// Init is called once after data structures are built but before
// code generation begins.
Init(g *Generator)
// Generate produces the code generated by the plugin for this file,
// except for the imports, by calling the generator's methods
// P, In, and Out.
Generate(file *FileDescriptor)
// GenerateImports produces the import declarations for this file.
// It is called after Generate.
GenerateImports(file *FileDescriptor)
}
```
`generate.Plugin`接口对应的 `grpcPlugin` 类型的 `GenerateImports` 方法中使用的 `p.P(...)` 函数却是通过 `Init` 函数注入的 `generator.Generator` 对象实现。这里的 `generator.Generator` 对应一个具体类型,但是如果 `generator.Generator` 是接口类型的话我们甚至可以传入直接的实现。
Go 语言通过几种简单特性的组合,就轻易就实现了鸭子面向对象和虚拟继承等高级特性,真的是不可思议。
================================================
FILE: ch1-basic/ch1-05-mem.md
================================================
# 1.5 面向并发的内存模型
在早期,CPU 都是以单核的形式顺序执行机器指令。Go 语言的祖先 C 语言正是这种顺序编程语言的代表。顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有且仅有一个 CPU 在顺序执行程序的指令。
随着处理器技术的发展,单核时代以提升处理器频率来提高运行效率的方式遇到了瓶颈,目前各种主流的 CPU 频率基本被锁定在了 3Ghz 附近。单核 CPU 的发展的停滞,给多核 CPU 的发展带来了机遇。相应地,编程语言也开始逐步向并行化的方向发展。Go 语言正是在多核和网络化的时代背景下诞生的原生支持并发的编程语言。
常见的并行编程有多种模型,主要有多线程、消息传递等。从理论上来看,多线程和基于消息的并发编程是等价的。由于多线程并发模型可以自然对应到多核的处理器,主流的操作系统因此也都提供了系统级的多线程支持,同时从概念上讲多线程似乎也更直观,因此多线程编程模型逐步被吸纳到主流的编程语言特性或语言扩展库中。而主流编程语言对基于消息的并发编程模型支持则相比较少,Erlang 语言是支持基于消息传递并发编程模型的代表者,它的并发体之间不共享内存。Go 语言是基于消息并发模型的集大成者,它将基于 CSP 模型的并发编程内置到了语言中,通过一个 go 关键字就可以轻易地启动一个 Goroutine,与 Erlang 不同的是 Go 语言的 Goroutine 之间是共享内存的。
## 1.5.1 Goroutine和系统线程
Goroutine是 Go 语言特有的并发体,是一种轻量级的线程,由 go 关键字启动。在真实的 Go 语言的实现中,goroutine 和系统线程也不是等价的。尽管两者的区别实际上只是一个量的区别,但正是这个量变引发了 Go 语言并发编程质的飞跃。
首先,每个系统级线程都会有一个固定大小的栈(一般默认可能是 2MB),这个栈主要用来保存函数递归调用时参数和局部变量。固定了栈的大小导致了两个问题:一是对于很多只需要很小的栈空间的线程来说是一个巨大的浪费,二是对于少数需要巨大栈空间的线程来说又面临栈溢出的风险。针对这两个问题的解决方案是:要么降低固定的栈大小,提升空间的利用率;要么增大栈的大小以允许更深的函数递归调用,但这两者是没法同时兼得的。相反,一个 Goroutine 会以一个很小的栈启动(可能是 2KB 或 4KB),当遇到深度递归导致当前栈空间不足时,Goroutine 会根据需要动态地伸缩栈的大小(主流实现中栈的最大值可达到1GB)。因为启动的代价很小,所以我们可以轻易地启动成千上万个 Goroutine。
Go的运行时还包含了其自己的调度器,这个调度器使用了一些技术手段,可以在 n 个操作系统线程上多工调度 m 个 Goroutine。Go 调度器的工作和内核的调度是相似的,但是这个调度器只关注单独的 Go 程序中的 Goroutine。Goroutine 采用的是半抢占式的协作调度,只有在当前 Goroutine 发生阻塞时才会导致调度;同时发生在用户态,调度器会根据具体函数只保存必要的寄存器,切换的代价要比系统线程低得多。运行时有一个 `runtime.GOMAXPROCS` 变量,用于控制当前运行正常非阻塞 Goroutine 的系统线程数目。
在 Go 语言中启动一个 Goroutine 不仅和调用函数一样简单,而且 Goroutine 之间调度代价也很低,这些因素极大地促进了并发编程的流行和发展。
## 1.5.2 原子操作
所谓的原子操作就是并发编程中“最小的且不可并行化”的操作。通常,如果多个并发体对同一个共享资源进行的操作是原子的话,那么同一时刻最多只能有一个并发体对该资源进行操作。从线程角度看,在当前线程修改共享资源期间,其它的线程是不能访问该资源的。原子操作对于多线程并发编程模型来说,不会发生有别于单线程的意外情况,共享资源的完整性可以得到保证。
一般情况下,原子操作都是通过“互斥”访问来保证的,通常由特殊的 CPU 指令提供保护。当然,如果仅仅是想模拟下粗粒度的原子操作,我们可以借助于 `sync.Mutex` 来实现:
```go
import (
"sync"
)
var total struct {
sync.Mutex
value int
}
func worker(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i <= 100; i++ {
total.Lock()
total.value += i
total.Unlock()
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go worker(&wg)
go worker(&wg)
wg.Wait()
fmt.Println(total.value)
}
```
在 `worker` 的循环中,为了保证 `total.value += i` 的原子性,我们通过 `sync.Mutex` 加锁和解锁来保证该语句在同一时刻只被一个线程访问。对于多线程模型的程序而言,进出临界区前后进行加锁和解锁都是必须的。如果没有锁的保护,`total` 的最终值将由于多线程之间的竞争而可能会不正确。
用互斥锁来保护一个数值型的共享资源,麻烦且效率低下。标准库的 `sync/atomic` 包对原子操作提供了丰富的支持。我们可以重新实现上面的例子:
```go
import (
"sync"
"sync/atomic"
)
var total uint64
func worker(wg *sync.WaitGroup) {
defer wg.Done()
var i uint64
for i = 0; i <= 100; i++ {
atomic.AddUint64(&total, i)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go worker(&wg)
go worker(&wg)
wg.Wait()
}
```
`atomic.AddUint64` 函数调用保证了 `total` 的读取、更新和保存是一个原子操作,因此在多线程中访问也是安全的。
原子操作配合互斥锁可以实现非常高效的单件模式。互斥锁的代价比普通整数的原子读写高很多,在性能敏感的地方可以增加一个数字型的标志位,通过原子检测标志位状态降低互斥锁的使用次数来提高性能。
```go
type singleton struct {}
var (
instance *singleton
initialized uint32
mu sync.Mutex
)
func Instance() *singleton {
if atomic.LoadUint32(&initialized) == 1 {
return instance
}
mu.Lock()
defer mu.Unlock()
if instance == nil {
defer atomic.StoreUint32(&initialized, 1)
instance = &singleton{}
}
return instance
}
```
我们可以将通用的代码提取出来,就成了标准库中 `sync.Once` 的实现:
```go
type Once struct {
m Mutex
done uint32
}
func (o *Once) Do(f func()) {
if atomic.LoadUint32(&o.done) == 1 {
return
}
o.m.Lock()
defer o.m.Unlock()
if o.done == 0 {
defer atomic.StoreUint32(&o.done, 1)
f()
}
}
```
基于 `sync.Once` 重新实现单件模式:
```go
var (
instance *singleton
once sync.Once
)
func Instance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}
```
`sync/atomic` 包对基本的数值类型及复杂对象的读写都提供了原子操作的支持。`atomic.Value` 原子对象提供了 `Load` 和 `Store` 两个原子方法,分别用于加载和保存数据,返回值和参数都是 `interface{}` 类型,因此可以用于任意的自定义复杂类型。
```go
var config atomic.Value // 保存当前配置信息
// 初始化配置信息
config.Store(loadConfig())
// 启动一个后台线程, 加载更新后的配置信息
go func() {
for {
time.Sleep(time.Second)
config.Store(loadConfig())
}
}()
// 用于处理请求的工作者线程始终采用最新的配置信息
for i := 0; i < 10; i++ {
go func() {
for r := range requests() {
c := config.Load()
// ...
}
}()
}
```
这是一个简化的生产者消费者模型:后台线程生成最新的配置信息;前台多个工作者线程获取最新的配置信息。所有线程共享配置信息资源。
## 1.5.3 顺序一致性内存模型
如果只是想简单地在线程之间进行数据同步的话,原子操作已经为编程人员提供了一些同步保障。不过这种保障有一个前提:顺序一致性的内存模型。要了解顺序一致性,我们先看看一个简单的例子:
```go
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func main() {
go setup()
for !done {}
print(a)
}
```
我们创建了 `setup` 线程,用于对字符串 `a` 的初始化工作,初始化完成之后设置 `done` 标志为 `true`。`main` 函数所在的主线程中,通过 `for !done {}` 检测 `done` 变为 `true` 时,认为字符串初始化工作完成,然后进行字符串的打印工作。
但是 Go 语言并不保证在 `main` 函数中观测到的对 `done` 的写入操作发生在对字符串 `a` 的写入的操作之后,因此程序很可能打印一个空字符串。更糟糕的是,因为两个线程之间没有同步事件,`setup`线程对 `done` 的写入操作甚至无法被 `main` 线程看到,`main`函数有可能陷入死循环中。
在 Go 语言中,同一个 Goroutine 线程内部,顺序一致性内存模型是得到保证的。但是不同的 Goroutine 之间,并不满足顺序一致性内存模型,需要通过明确定义的同步事件来作为同步的参考。如果两个事件不可排序,那么就说这两个事件是并发的。为了最大化并行,Go 语言的编译器和处理器在不影响上述规定的前提下可能会对执行语句重新排序(CPU 也会对一些指令进行乱序执行)。
因此,如果在一个 Goroutine 中顺序执行 `a = 1; b = 2;` 两个语句,虽然在当前的 Goroutine 中可以认为 `a = 1;` 语句先于 `b = 2;` 语句执行,但是在另一个 Goroutine 中 `b = 2;` 语句可能会先于 `a = 1;` 语句执行,甚至在另一个 Goroutine 中无法看到它们的变化(可能始终在寄存器中)。也就是说在另一个 Goroutine 看来, `a = 1; b = 2;`两个语句的执行顺序是不确定的。如果一个并发程序无法确定事件的顺序关系,那么程序的运行结果往往会有不确定的结果。比如下面这个程序:
```go
func main() {
go println("你好, 世界")
}
```
根据 Go 语言规范,`main`函数退出时程序结束,不会等待任何后台线程。因为 Goroutine 的执行和 `main` 函数的返回事件是并发的,谁都有可能先发生,所以什么时候打印,能否打印都是未知的。
用前面的原子操作并不能解决问题,因为我们无法确定两个原子操作之间的顺序。解决问题的办法就是通过同步原语来给两个事件明确排序:
```go
func main() {
done := make(chan int)
go func(){
println("你好, 世界")
done <- 1
}()
<-done
}
```
当 `<-done` 执行时,必然要求 `done <- 1` 也已经执行。根据同一个 Goroutine 依然满足顺序一致性规则,我们可以判断当 `done <- 1` 执行时,`println("你好, 世界")` 语句必然已经执行完成了。因此,现在的程序确保可以正常打印结果。
当然,通过 `sync.Mutex` 互斥量也是可以实现同步的:
```go
func main() {
var mu sync.Mutex
mu.Lock()
go func(){
println("你好, 世界")
mu.Unlock()
}()
mu.Lock()
}
```
可以确定后台线程的 `mu.Unlock()` 必然在 `println("你好, 世界")` 完成后发生(同一个线程满足顺序一致性),`main` 函数的第二个 `mu.Lock()` 必然在后台线程的 `mu.Unlock()` 之后发生(`sync.Mutex` 保证),此时后台线程的打印工作已经顺利完成了。
## 1.5.4 初始化顺序
前面函数章节中我们已经简单介绍过程序的初始化顺序,这是属于 Go 语言面向并发的内存模型的基础规范。
Go程序的初始化和执行总是从 `main.main` 函数开始的。但是如果 `main` 包里导入了其它的包,则会按照顺序将它们包含进 `main` 包里(这里的导入顺序依赖具体实现,一般可能是以文件名或包路径名的字符串顺序导入)。如果某个包被多次导入的话,在执行的时候只会导入一次。当一个包被导入时,如果它还导入了其它的包,则先将其它的包包含进来,然后创建和初始化这个包的常量和变量。然后就是调用包里的 `init` 函数,如果一个包有多个 `init` 函数的话,实现可能是以文件名的顺序调用,同一个文件内的多个 `init` 则是以出现的顺序依次调用(`init`不是普通函数,可以定义有多个,所以不能被其它函数调用)。最终,在 `main` 包的所有包常量、包变量被创建和初始化,并且 `init` 函数被执行后,才会进入 `main.main` 函数,程序开始正常执行。下图是 Go 程序函数启动顺序的示意图:

*图 1-12 包初始化流程*
要注意的是,在 `main.main` 函数执行之前所有代码都运行在同一个 Goroutine 中,也是运行在程序的主系统线程中。如果某个 `init` 函数内部用 go 关键字启动了新的 Goroutine 的话,新的 Goroutine 和 `main.main` 函数是并发执行的。
因为所有的 `init` 函数和 `main` 函数都是在主线程完成,它们也是满足顺序一致性模型的。
## 1.5.5 Goroutine的创建
`go` 语句会在当前 Goroutine 对应函数返回前创建新的 Goroutine。例如:
```go
var a string
func f() {
print(a)
}
func hello() {
a = "hello, world"
go f()
}
```
执行 `go f()` 语句创建 Goroutine 和 `hello` 函数是在同一个 Goroutine 中执行, 根据语句的书写顺序可以确定 Goroutine 的创建发生在 `hello` 函数返回之前, 但是新创建 Goroutine 对应的 `f()` 的执行事件和 `hello` 函数返回的事件则是不可排序的,也就是并发的。调用 `hello` 可能会在将来的某一时刻打印 `"hello, world"`,也很可能是在 `hello` 函数执行完成后才打印。
## 1.5.6 基于 Channel 的通信
Channel 通信是在 Goroutine 之间进行同步的主要方法。在无缓存的 Channel 上的每一次发送操作都有与其对应的接收操作相配对,发送和接收操作通常发生在不同的 Goroutine 上(在同一个 Goroutine 上执行两个操作很容易导致死锁)。**无缓存的 Channel 上的发送操作总在对应的接收操作完成前发生.**
```go
var done = make(chan bool)
var msg string
func aGoroutine() {
msg = "你好, 世界"
done <- true
}
func main() {
go aGoroutine()
<-done
println(msg)
}
```
可保证打印出“你好, 世界”。该程序首先对 `msg` 进行写入,然后在 `done` 管道上发送同步信号,随后从 `done` 接收对应的同步信号,最后执行 `println` 函数。
若在关闭 Channel 后继续从中接收数据,接收者就会收到该 Channel 返回的零值。因此在这个例子中,用 `close(c)` 关闭管道代替 `done <- false` 依然能保证该程序产生相同的行为。
```go
var done = make(chan bool)
var msg string
func aGoroutine() {
msg = "你好, 世界"
close(done)
}
func main() {
go aGoroutine()
<-done
println(msg)
}
```
**对于从无缓冲 Channel 进行的接收,发生在对该 Channel 进行的发送完成之前。**
基于上面这个规则可知,交换两个 Goroutine 中的接收和发送操作也是可以的(但是很危险):
```go
var done = make(chan bool)
var msg string
func aGoroutine() {
msg = "hello, world"
<-done
}
func main() {
go aGoroutine()
done <- true
println(msg)
}
```
也可保证打印出“hello, world”。因为 `main` 线程中 `done <- true` 发送完成前,后台线程 `<-done` 接收已经开始,这保证 `msg = "hello, world"` 被执行了,所以之后 `println(msg)` 的msg已经被赋值过了。简而言之,后台线程首先对 `msg` 进行写入,然后从 `done` 中接收信号,随后 `main` 线程向 `done` 发送对应的信号,最后执行 `println` 函数完成。但是,若该 Channel 为带缓冲的(例如,`done = make(chan bool, 1)`),`main`线程的 `done <- true` 接收操作将不会被后台线程的 `<-done` 接收操作阻塞,该程序将无法保证打印出“hello, world”。
对于带缓冲的Channel,**对于 Channel 的第 `K` 个接收完成操作发生在第 `K+C` 个发送操作完成之前,其中 `C` 是 Channel 的缓存大小。** 如果将 `C` 设置为 0 自然就对应无缓存的 Channel,也即使第 K 个接收完成在第 K 个发送完成之前。因为无缓存的 Channel 只能同步发 1 个,也就简化为前面无缓存 Channel 的规则:**对于从无缓冲 Channel 进行的接收,发生在对该 Channel 进行的发送完成之前。**
我们可以根据控制 Channel 的缓存大小来控制并发执行的 Goroutine 的最大数目, 例如:
```go
var limit = make(chan int, 3)
var work = []func(){
func() { println("1"); time.Sleep(1 * time.Second) },
func() { println("2"); time.Sleep(1 * time.Second) },
func() { println("3"); time.Sleep(1 * time.Second) },
func() { println("4"); time.Sleep(1 * time.Second) },
func() { println("5"); time.Sleep(1 * time.Second) },
}
func main() {
for _, w := range work {
go func(w func()) {
limit <- 1
w()
<-limit
}(w)
}
select{}
}
```
在循环创建 `Goroutine` 过程中,使用了匿名函数并在函数中引用了循环变量 `w`,由于 `w` 是引用传递的而非值传递,因此无法保证 `Goroutine` 在运行时调用的 `w` 与循环创建时的 `w` 是同一个值,为了解决这个问题,我们可以利用函数传参的值复制来为每个 `Goroutine` 单独复制一份 `w`。
循环创建结束后,在 `main` 函数中最后一句 `select{}` 是一个空的管道选择语句,该语句会导致 `main` 线程阻塞,从而避免程序过早退出。还有 `for{}`、`<-make(chan int)` 等诸多方法可以达到类似的效果。因为 `main` 线程被阻塞了,如果需要程序正常退出的话可以通过调用 `os.Exit(0)` 实现。
## 1.5.7 不靠谱的同步
前面我们已经分析过,下面代码无法保证正常打印结果。实际的运行效果也是大概率不能正常输出结果。
```go
func main() {
go println("你好, 世界")
}
```
刚接触 Go 语言的话,可能希望通过加入一个随机的休眠时间来保证正常的输出:
```go
func main() {
go println("hello, world")
time.Sleep(time.Second)
}
```
因为主线程休眠了 1 秒钟,因此这个程序大概率是可以正常输出结果的。因此,很多人会觉得这个程序已经没有问题了。但是这个程序是不稳健的,依然有失败的可能性。我们先假设程序是可以稳定输出结果的。因为 Go 线程的启动是非阻塞的,`main` 线程显式休眠了 1 秒钟退出导致程序结束,我们可以近似地认为程序总共执行了 1 秒多时间。现在假设 `println` 函数内部实现休眠的时间大于 `main` 线程休眠的时间的话,就会导致矛盾:后台线程既然先于 `main` 线程完成打印,那么执行时间肯定是小于 `main` 线程执行时间的。当然这是不可能的。
严谨的并发程序的正确性不应该是依赖于 CPU 的执行速度和休眠时间等不靠谱的因素的。严谨的并发也应该是可以静态推导出结果的:根据线程内顺序一致性,结合 Channel 或 `sync` 同步事件的可排序性来推导,最终完成各个线程各段代码的偏序关系排序。如果两个事件无法根据此规则来排序,那么它们就是并发的,也就是执行先后顺序不可靠的。
解决同步问题的思路是相同的:使用显式的同步。
================================================
FILE: ch1-basic/ch1-06-goroutine.md
================================================
# 1.6 常见的并发模式
Go 语言最吸引人的地方是它内建的并发支持。Go 语言并发体系的理论是 _C.A.R Hoare_ 在 1978 年提出的 CSP(Communicating Sequential Process,通讯顺序进程)。CSP 有着精确的数学模型,并实际应用在了 Hoare 参与设计的 T9000 通用计算机上。从 NewSqueak、Alef、Limbo 到现在的 Go 语言,对于对 CSP 有着 20 多年实战经验的 _Rob Pike_ 来说,他更关注的是将 CSP 应用在通用编程语言上产生的潜力。作为 Go 并发编程核心的 CSP 理论的核心概念只有一个:同步通信。关于同步通信的话题我们在前面一节已经讲过,本节我们将简单介绍下 Go 语言中常见的并发模式。
首先要明确一个概念:并发不是并行。并发更关注的是程序的设计层面,并发的程序完全是可以顺序执行的,只有在真正的多核 CPU 上才可能真正地同时运行。并行更关注的是程序的运行层面,并行一般是简单的大量重复,例如 GPU 中对图像处理都会有大量的并行运算。为更好的编写并发程序,从设计之初 Go 语言就注重如何在编程语言层级上设计一个简洁安全高效的抽象模型,让程序员专注于分解问题和组合方案,而且不用被线程管理和信号互斥这些繁琐的操作分散精力。
在并发编程中,对共享资源的正确访问需要精确的控制,在目前的绝大多数语言中,都是通过加锁等线程同步方案来解决这一困难问题,而 Go 语言却另辟蹊径,它将共享的值通过 Channel 传递(实际上多个独立执行的线程很少主动共享资源)。在任意给定的时刻,最好只有一个 Goroutine 能够拥有该资源。数据竞争从设计层面上就被杜绝了。为了提倡这种思考方式,Go 语言将其并发编程哲学化为一句口号:
> Do not communicate by sharing memory; instead, share memory by communicating.
> 不要通过共享内存来通信,而应通过通信来共享内存。
这是更高层次的并发编程哲学(通过管道来传值是 Go 语言推荐的做法)。虽然像引用计数这类简单的并发问题通过原子操作或互斥锁就能很好地实现,但是通过 Channel 来控制访问能够让你写出更简洁正确的程序。
## 1.6.1 并发版本的 Hello world
我们先以在一个新的 Goroutine 中输出“Hello world”,`main` 等待后台线程输出工作完成之后退出,这样一个简单的并发程序作为热身。
并发编程的核心概念是同步通信,但是同步的方式却有多种。我们先以大家熟悉的互斥量 `sync.Mutex` 来实现同步通信。根据文档,我们不能直接对一个未加锁状态的 `sync.Mutex` 进行解锁,这会导致运行时异常。下面这种方式并不能保证正常工作:
```go
func main() {
var mu sync.Mutex
go func(){
fmt.Println("你好, 世界")
mu.Lock()
}()
mu.Unlock()
}
```
因为 `mu.Lock()` 和 `mu.Unlock()` 并不在同一个 Goroutine 中,所以也就不满足顺序一致性内存模型。同时它们也没有其它的同步事件可以参考,这两个事件不可排序也就是可以并发的。因为可能是并发的事件,所以 `main` 函数中的 `mu.Unlock()` 很有可能先发生,而这个时刻 `mu` 互斥对象还处于未加锁的状态,从而会导致运行时异常。
下面是修复后的代码:
```go
func main() {
var mu sync.Mutex
mu.Lock()
go func(){
fmt.Println("你好, 世界")
mu.Unlock()
}()
mu.Lock()
}
```
修复的方式是在 `main` 函数所在线程中执行两次 `mu.Lock()`,当第二次加锁时会因为锁已经被占用(不是递归锁)而阻塞,`main` 函数的阻塞状态驱动后台线程继续向前执行。当后台线程执行到 `mu.Unlock()` 时解锁,此时打印工作已经完成了,解锁会导致 `main` 函数中的第二个 `mu.Lock()` 阻塞状态取消,此时后台线程和主线程再没有其它的同步事件参考,它们退出的事件将是并发的:在 `main` 函数退出导致程序退出时,后台线程可能已经退出了,也可能没有退出。虽然无法确定两个线程退出的时间,但是打印工作是可以正确完成的。
使用 `sync.Mutex` 互斥锁同步是比较低级的做法。我们现在改用无缓存的管道来实现同步:
```go
func main() {
done := make(chan int)
go func(){
fmt.Println("你好, 世界")
<-done
}()
done <- 1
}
```
根据 Go 语言内存模型规范,对于从无缓冲 Channel 进行的接收,发生在对该 Channel 进行的发送完成之前。因此,后台线程 `<-done` 接收操作完成之后,`main` 线程的 `done <- 1` 发送操作才可能完成(从而退出 main、退出程序),而此时打印工作已经完成了。
上面的代码虽然可以正确同步,但是对管道的缓存大小太敏感:如果管道有缓存的话,就无法保证 main 退出之前后台线程能正常打印了。更好的做法是将管道的发送和接收方向调换一下,这样可以避免同步事件受管道缓存大小的影响:
```go
func main() {
done := make(chan int, 1) // 带缓存的管道
go func(){
fmt.Println("你好, 世界")
done <- 1
}()
<-done
}
```
对于带缓冲的 Channel,对于 Channel 的第 K 个接收完成操作发生在第 K+C 个发送操作完成之前,其中 C 是 Channel 的缓存大小。虽然管道是带缓存的,`main` 线程接收完成是在后台线程发送开始但还未完成的时刻,此时打印工作也是已经完成的。
基于带缓存的管道,我们可以很容易将打印线程扩展到 N 个。下面的例子是开启 10 个后台线程分别打印:
```go
func main() {
done := make(chan int, 10) // 带 10 个缓存
// 开 N 个后台打印线程
for i := 0; i < cap(done); i++ {
go func(){
fmt.Println("你好, 世界")
done <- 1
}()
}
// 等待 N 个后台线程完成
for i := 0; i < cap(done); i++ {
<-done
}
}
```
对于这种要等待 N 个线程完成后再进行下一步的同步操作有一个简单的做法,就是使用 `sync.WaitGroup` 来等待一组事件:
```go
func main() {
var wg sync.WaitGroup
// 开 N 个后台打印线程
for i := 0; i < 10; i++ {
wg.Add(1)
go func() {
fmt.Println("你好, 世界")
wg.Done()
}()
}
// 等待 N 个后台线程完成
wg.Wait()
}
```
其中 `wg.Add(1)` 用于增加等待事件的个数,必须确保在后台线程启动之前执行(如果放到后台线程之中执行则不能保证被正常执行到)。当后台线程完成打印工作之后,调用 `wg.Done()` 表示完成一个事件。`main` 函数的 `wg.Wait()` 是等待全部的事件完成。
## 1.6.2 生产者消费者模型
并发编程中最常见的例子就是生产者消费者模式,该模式主要通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。简单地说,就是生产者生产一些数据,然后放到成果队列中,同时消费者从成果队列中来取这些数据。这样就让生产消费变成了异步的两个过程。当成果队列中没有数据时,消费者就进入饥饿的等待中;而当成果队列中数据已满时,生产者则面临因产品挤压导致 CPU 被剥夺的下岗问题。
Go 语言实现生产者消费者并发很简单:
```go
// 生产者: 生成 factor 整数倍的序列
func Producer(factor int, out chan<- int) {
for i := 0; ; i++ {
out <- i*factor
}
}
// 消费者
func Consumer(in <-chan int) {
for v := range in {
fmt.Println(v)
}
}
func main() {
ch := make(chan int, 64) // 成果队列
go Producer(3, ch) // 生成 3 的倍数的序列
go Producer(5, ch) // 生成 5 的倍数的序列
go Consumer(ch) // 消费生成的队列
// 运行一定时间后退出
time.Sleep(5 * time.Second)
}
```
我们开启了 2 个 `Producer` 生产流水线,分别用于生成 3 和 5 的倍数的序列。然后开启 1 个 `Consumer` 消费者线程,打印获取的结果。我们通过在 `main` 函数休眠一定的时间来让生产者和消费者工作一定时间。正如前面一节说的,这种靠休眠方式是无法保证稳定的输出结果的。
我们可以让 `main` 函数保存阻塞状态不退出,只有当用户输入 `Ctrl-C` 时才真正退出程序:
```go
func main() {
ch := make(chan int, 64) // 成果队列
go Producer(3, ch) // 生成 3 的倍数的序列
go Producer(5, ch) // 生成 5 的倍数的序列
go Consumer(ch) // 消费 生成的队列
// Ctrl+C 退出
sig := make(chan os.Signal, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
fmt.Printf("quit (%v)\n", <-sig)
}
```
我们这个例子中有 2 个生产者,并且 2 个生产者之间并无同步事件可参考,它们是并发的。因此,消费者输出的结果序列的顺序是不确定的,这并没有问题,生产者和消费者依然可以相互配合工作。
## 1.6.3 发布订阅模型
发布订阅(publish-and-subscribe)模型通常被简写为 pub/sub 模型。在这个模型中,消息生产者成为发布者(publisher),而消息消费者则成为订阅者(subscriber),生产者和消费者是 M:N 的关系。在传统生产者和消费者模型中,是将消息发送到一个队列中,而发布订阅模型则是将消息发布给一个主题。
为此,我们构建了一个名为 `pubsub` 的发布订阅模型支持包:
```go
// Package pubsub implements a simple multi-topic pub-sub library.
package pubsub
import (
"sync"
"time"
)
type (
subscriber chan interface{} // 订阅者为一个管道
topicFunc func(v interface{}) bool // 主题为一个过滤器
)
// 发布者对象
type Publisher struct {
m sync.RWMutex // 读写锁
buffer int // 订阅队列的缓存大小
timeout time.Duration // 发布超时时间
subscribers map[subscriber]topicFunc // 订阅者信息
}
// 构建一个发布者对象, 可以设置发布超时时间和缓存队列的长度
func NewPublisher(publishTimeout time.Duration, buffer int) *Publisher {
return &Publisher{
buffer: buffer,
timeout: publishTimeout,
subscribers: make(map[subscriber]topicFunc),
}
}
// 添加一个新的订阅者,订阅全部主题
func (p *Publisher) Subscribe() chan interface{} {
return p.SubscribeTopic(nil)
}
// 添加一个新的订阅者,订阅过滤器筛选后的主题
func (p *Publisher) SubscribeTopic(topic topicFunc) chan interface{} {
ch := make(chan interface{}, p.buffer)
p.m.Lock()
p.subscribers[ch] = topic
p.m.Unlock()
return ch
}
// 退出订阅
func (p *Publisher) Evict(sub chan interface{}) {
p.m.Lock()
defer p.m.Unlock()
delete(p.subscribers, sub)
close(sub)
}
// 发布一个主题
func (p *Publisher) Publish(v interface{}) {
p.m.RLock()
defer p.m.RUnlock()
var wg sync.WaitGroup
for sub, topic := range p.subscribers {
wg.Add(1)
go p.sendTopic(sub, topic, v, &wg)
}
wg.Wait()
}
// 关闭发布者对象,同时关闭所有的订阅者管道。
func (p *Publisher) Close() {
p.m.Lock()
defer p.m.Unlock()
for sub := range p.subscribers {
delete(p.subscribers, sub)
close(sub)
}
}
// 发送主题,可以容忍一定的超时
func (p *Publisher) sendTopic(
sub subscriber, topic topicFunc, v interface{}, wg *sync.WaitGroup,
) {
defer wg.Done()
if topic != nil && !topic(v) {
return
}
select {
case sub <- v:
case <-time.After(p.timeout):
}
}
```
下面的例子中,有两个订阅者分别订阅了全部主题和含有"golang"的主题:
```go
import "path/to/pubsub"
func main() {
p := pubsub.NewPublisher(100*time.Millisecond, 10)
defer p.Close()
all := p.Subscribe()
golang := p.SubscribeTopic(func(v interface{}) bool {
if s, ok := v.(string); ok {
return strings.Contains(s, "golang")
}
return false
})
p.Publish("hello, world!")
p.Publish("hello, golang!")
go func() {
for msg := range all {
fmt.Println("all:", msg)
}
} ()
go func() {
for msg := range golang {
fmt.Println("golang:", msg)
}
} ()
// 运行一定时间后退出
time.Sleep(3 * time.Second)
}
```
在发布订阅模型中,每条消息都会传送给多个订阅者。发布者通常不会知道、也不关心哪一个订阅者正在接收主题消息。订阅者和发布者可以在运行时动态添加,是一种松散的耦合关系,这使得系统的复杂性可以随时间的推移而增长。在现实生活中,像天气预报之类的应用就可以应用这个并发模式。
## 1.6.4 控制并发数
很多用户在适应了 Go 语言强大的并发特性之后,都倾向于编写最大并发的程序,因为这样似乎可以提供最大的性能。在现实中我们行色匆匆,但有时却需要我们放慢脚步享受生活,并发的程序也是一样:有时候我们需要适当地控制并发的程度,因为这样不仅仅可给其它的应用/任务让出/预留一定的 CPU 资源,也可以适当降低功耗缓解电池的压力。
在 Go 语言自带的 godoc 程序实现中有一个 `vfs` 的包对应虚拟的文件系统,在 `vfs` 包下面有一个 `gatefs` 的子包,`gatefs` 子包的目的就是为了控制访问该虚拟文件系统的最大并发数。`gatefs` 包的应用很简单:
```go
import (
"golang.org/x/tools/godoc/vfs"
"golang.org/x/tools/godoc/vfs/gatefs"
)
func main() {
fs := gatefs.New(vfs.OS("/path"), make(chan bool, 8))
// ...
}
```
其中 `vfs.OS("/path")` 基于本地文件系统构造一个虚拟的文件系统,然后 `gatefs.New` 基于现有的虚拟文件系统构造一个并发受控的虚拟文件系统。并发数控制的原理在前面一节已经讲过,就是通过带缓存管道的发送和接收规则来实现最大并发阻塞:
```go
var limit = make(chan int, 3)
func main() {
for _, w := range work {
go func() {
limit <- 1
w()
<-limit
}()
}
select{}
}
```
不过 `gatefs` 对此做一个抽象类型 `gate`,增加了 `enter` 和 `leave` 方法分别对应并发代码的进入和离开。当超出并发数目限制的时候,`enter` 方法会阻塞直到并发数降下来为止。
```go
type gate chan bool
func (g gate) enter() { g <- true }
func (g gate) leave() { <-g }
```
`gatefs` 包装的新的虚拟文件系统就是将需要控制并发的方法增加了 `enter` 和 `leave` 调用而已:
```go
type gatefs struct {
fs vfs.FileSystem
gate
}
func (fs gatefs) Lstat(p string) (os.FileInfo, error) {
fs.enter()
defer fs.leave()
return fs.fs.Lstat(p)
}
```
我们不仅可以控制最大的并发数目,而且可以通过带缓存 Channel 的使用量和最大容量比例来判断程序运行的并发率。当管道为空的时候可以认为是空闲状态,当管道满了时任务是繁忙状态,这对于后台一些低级任务的运行是有参考价值的。
## 1.6.5 赢者为王
采用并发编程的动机有很多:并发编程可以简化问题,比如一类问题对应一个处理线程会更简单;并发编程还可以提升性能,在一个多核 CPU 上开 2 个线程一般会比开 1 个线程快一些。其实对于提升性能而言,程序并不是简单地运行速度快就表示用户体验好的;很多时候程序能快速响应用户请求才是最重要的,当没有用户请求需要处理的时候才合适处理一些低优先级的后台任务。
假设我们想快速地搜索“golang”相关的主题,我们可能会同时打开 Bing、Google 或百度等多个检索引擎。当某个搜索最先返回结果后,就可以关闭其它搜索页面了。因为受网络环境和搜索引擎算法的影响,某些搜索引擎可能很快返回搜索结果,某些搜索引擎也可能等到他们公司倒闭也没有完成搜索。我们可以采用类似的策略来编写这个程序:
```go
func main() {
ch := make(chan string, 32)
go func() {
ch <- searchByBing("golang")
}()
go func() {
ch <- searchByGoogle("golang")
}()
go func() {
ch <- searchByBaidu("golang")
}()
fmt.Println(<-ch)
}
```
首先,我们创建了一个带缓存的管道,管道的缓存数目要足够大,保证不会因为缓存的容量引起不必要的阻塞。然后我们开启了多个后台线程,分别向不同的搜索引擎提交搜索请求。当任意一个搜索引擎最先有结果之后,都会马上将结果发到管道中(因为管道带了足够的缓存,这个过程不会阻塞)。但是最终我们只从管道取第一个结果,也就是最先返回的结果。
通过适当开启一些冗余的线程,尝试用不同途径去解决同样的问题,最终以赢者为王的方式提升了程序的相应性能。
## 1.6.6 素数筛
在“Hello world 的革命”一节中,我们为了演示 Newsqueak 的并发特性,文中给出了并发版本素数筛的实现。并发版本的素数筛是一个经典的并发例子,通过它我们可以更深刻地理解 Go 语言的并发特性。“素数筛”的原理如图:

_图 1-13 素数筛_
我们需要先生成最初的 `2, 3, 4, ...` 自然数序列(不包含开头的 0、1):
```go
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural() chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
ch <- i
}
}()
return ch
}
```
`GenerateNatural` 函数内部启动一个 Goroutine 生产序列,返回对应的管道。
然后是为每个素数构造一个筛子:将输入序列中是素数倍数的数提出,并返回新的序列,是一个新的管道。
```go
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(in <-chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
out <- i
}
}
}()
return out
}
```
`PrimeFilter` 函数也是内部启动一个 Goroutine 生产序列,返回过滤后序列对应的管道。
现在我们可以在 `main` 函数中驱动这个并发的素数筛了:
```go
func main() {
ch := GenerateNatural() // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
prime := <-ch // 新出现的素数
fmt.Printf("%v: %v\n", i+1, prime)
ch = PrimeFilter(ch, prime) // 基于新素数构造的过滤器
}
}
```
我们先是调用 `GenerateNatural()` 生成最原始的从 2 开始的自然数序列。然后开始一个 100 次迭代的循环,希望生成 100 个素数。在每次循环迭代开始的时候,管道中的第一个数必定是素数,我们先读取并打印这个素数。然后基于管道中剩余的数列,并以当前取出的素数为筛子过滤后面的素数。不同的素数筛子对应的管道是串联在一起的。
素数筛展示了一种优雅的并发程序结构。但是因为每个并发体处理的任务粒度太细微,程序整体的性能并不理想。对于细粒度的并发程序,CSP 模型中固有的消息传递的代价太高了(多线程并发模型同样要面临线程启动的代价)。
## 1.6.7 并发的安全退出
有时候我们需要通知 Goroutine 停止它正在干的事情,特别是当它工作在错误的方向上的时候。Go 语言并没有提供在一个直接终止 Goroutine 的方法,由于这样会导致 Goroutine 之间的共享变量处在未定义的状态上。但是如果我们想要退出两个或者任意多个 Goroutine 怎么办呢?
Go 语言中不同 Goroutine 之间主要依靠管道进行通信和同步。要同时处理多个管道的发送或接收操作,我们需要使用 `select` 关键字(这个关键字和网络编程中的 `select` 函数的行为类似)。当 `select` 有多个分支时,会随机选择一个可用的管道分支,如果没有可用的管道分支则选择 `default` 分支,否则会一直保存阻塞状态。
基于 `select` 实现的管道的超时判断:
```go
select {
case v := <-in:
fmt.Println(v)
case <-time.After(time.Second):
return // 超时
}
```
通过 `select` 的 `default` 分支实现非阻塞的管道发送或接收操作:
```go
select {
case v := <-in:
fmt.Println(v)
default:
// 没有数据
}
```
通过 `select` 来阻止 `main` 函数退出:
```go
func main() {
// do some thins
select{}
}
```
当有多个管道均可操作时,`select` 会随机选择一个管道。基于该特性我们可以用 `select` 实现一个生成随机数序列的程序:
```go
func main() {
ch := make(chan int)
go func() {
for {
select {
case ch <- 0:
case ch <- 1:
}
}
}()
for v := range ch {
fmt.Println(v)
}
}
```
我们通过 `select` 和 `default` 分支可以很容易实现一个 Goroutine 的退出控制:
```go
func worker(cancel chan bool) {
for {
select {
default:
fmt.Println("hello")
// 正常工作
case <-cancel:
// 退出
}
}
}
func main() {
cancel := make(chan bool)
go worker(cancel)
time.Sleep(time.Second)
cancel <- true
}
```
但是管道的发送操作和接收操作是一一对应的,如果要停止多个 Goroutine 那么可能需要创建同样数量的管道,这个代价太大了。其实我们可以通过 `close` 关闭一个管道来实现广播的效果,所有从关闭管道接收的操作均会收到一个零值和一个可选的失败标志。
```go
func worker(cancel chan bool) {
for {
select {
default:
fmt.Println("hello")
// 正常工作
case <-cancel:
// 退出
}
}
}
func main() {
cancel := make(chan bool)
for i := 0; i < 10; i++ {
go worker(cancel)
}
time.Sleep(time.Second)
close(cancel)
}
```
我们通过 `close` 来关闭 `cancel` 管道向多个 Goroutine 广播退出的指令。不过这个程序依然不够稳健:当每个 Goroutine 收到退出指令退出时一般会进行一定的清理工作,但是退出的清理工作并不能保证被完成,因为 `main` 线程并没有等待各个工作 Goroutine 退出工作完成的机制。我们可以结合 `sync.WaitGroup` 来改进:
```go
func worker(wg *sync.WaitGroup, cancel chan bool) {
defer wg.Done()
for {
select {
default:
fmt.Println("hello")
case <-cancel:
return
}
}
}
func main() {
cancel := make(chan bool)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(&wg, cancel)
}
time.Sleep(time.Second)
close(cancel)
wg.Wait()
}
```
现在每个工作者并发体的创建、运行、暂停和退出都是在 `main` 函数的安全控制之下了。
## 1.6.8 context 包
在 Go1.7 发布时,标准库增加了一个 `context` 包,用来简化对于处理单个请求的多个 Goroutine 之间与请求域的数据、超时和退出等操作,官方有博文对此做了专门介绍。我们可以用 `context` 包来重新实现前面的线程安全退出或超时的控制:
```go
func worker(ctx context.Context, wg *sync.WaitGroup) error {
defer wg.Done()
for {
select {
default:
fmt.Println("hello")
case <-ctx.Done():
return ctx.Err()
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go worker(ctx, &wg)
}
time.Sleep(time.Second)
cancel()
wg.Wait()
}
```
当并发体超时或 `main` 主动停止工作者 Goroutine 时,每个工作者都可以安全退出。
Go 语言是带内存自动回收特性的,因此内存一般不会泄漏。在前面素数筛的例子中,`GenerateNatural` 和 `PrimeFilter` 函数内部都启动了新的 Goroutine,当 `main` 函数不再使用管道时后台 Goroutine 有泄漏的风险。我们可以通过 `context` 包来避免这个问题,下面是改进的素数筛实现:
```go
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context) chan int {
ch := make(chan int)
go func() {
for i := 2; ; i++ {
select {
case <- ctx.Done():
return
case ch <- i:
}
}
}()
return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int) chan int {
out := make(chan int)
go func() {
for {
if i := <-in; i%prime != 0 {
select {
case <- ctx.Done():
return
case out <- i:
}
}
}
}()
return out
}
func main() {
// 通过 Context 控制后台 Goroutine 状态
ctx, cancel := context.WithCancel(context.Background())
ch := GenerateNatural(ctx) // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
prime := <-ch // 新出现的素数
fmt.Printf("%v: %v\n", i+1, prime)
ch = PrimeFilter(ctx, ch, prime) // 基于新素数构造的过滤器
}
cancel()
}
```
当 main 函数完成工作前,通过调用 `cancel()` 来通知后台 Goroutine 退出,这样就避免了 Goroutine 的泄漏。
然而,上面这个例子只是展示了 `cancel()` 的基础用法,实际上这个例子会导致 Goroutine 死锁,不能正常退出。
我们可以给上面这个例子添加 `sync.WaitGroup` 来复现这个问题。
```go
package main
import (
"context"
"fmt"
"sync"
)
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context, wg *sync.WaitGroup) chan int {
ch := make(chan int)
go func() {
defer wg.Done()
for i := 2; ; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}()
return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int, wg *sync.WaitGroup) chan int {
out := make(chan int)
go func() {
defer wg.Done()
for {
if i := <-in; i%prime != 0 {
select {
case <-ctx.Done():
return
case out <- i:
}
}
}
}()
return out
}
func main() {
wg := sync.WaitGroup{}
// 通过 Context 控制后台 Goroutine 状态
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
ch := GenerateNatural(ctx, &wg) // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
prime := <-ch // 新出现的素数
fmt.Printf("%v: %v\n", i+1, prime)
wg.Add(1)
ch = PrimeFilter(ctx, ch, prime, &wg) // 基于新素数构造的过滤器
}
cancel()
wg.Wait()
}
```
执行上面这个例子很容易就复现了死锁的问题,原因是素数筛中的 `ctx.Done()` 位于 `if i := <-in; i%prime != 0` 判断之内,
而这个判断可能会一直阻塞,导致 Goroutine 无法正常退出。让我们来解决这个问题。
```go
package main
import (
"context"
"fmt"
"sync"
)
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context, wg *sync.WaitGroup) chan int {
ch := make(chan int)
go func() {
defer wg.Done()
for i := 2; ; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}()
return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int, wg *sync.WaitGroup) chan int {
out := make(chan int)
go func() {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case i := <-in:
if i%prime != 0 {
select {
case <-ctx.Done():
return
case out <- i:
}
}
}
}
}()
return out
}
func main() {
wg := sync.WaitGroup{}
// 通过 Context 控制后台 Goroutine 状态
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
ch := GenerateNatural(ctx, &wg) // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
prime := <-ch // 新出现的素数
fmt.Printf("%v: %v\n", i+1, prime)
wg.Add(1)
ch = PrimeFilter(ctx, ch, prime, &wg) // 基于新素数构造的过滤器
}
cancel()
wg.Wait()
}
```
如上所示,我们可以通过将 `i := <-in` 放入 select,在这个 select 内也执行 `<-ctx.Done()` 来解决阻塞导致的死锁。
不过上面这个例子并不优美,让我们换一种方式。
```go
package main
import (
"context"
"fmt"
"sync"
)
// 返回生成自然数序列的管道: 2, 3, 4, ...
func GenerateNatural(ctx context.Context, wg *sync.WaitGroup) chan int {
ch := make(chan int)
go func() {
defer wg.Done()
defer close(ch)
for i := 2; ; i++ {
select {
case <-ctx.Done():
return
case ch <- i:
}
}
}()
return ch
}
// 管道过滤器: 删除能被素数整除的数
func PrimeFilter(ctx context.Context, in <-chan int, prime int, wg *sync.WaitGroup) chan int {
out := make(chan int)
go func() {
defer wg.Done()
defer close(out)
for i := range in {
if i%prime != 0 {
select {
case <-ctx.Done():
return
case out <- i:
}
}
}
}()
return out
}
func main() {
wg := sync.WaitGroup{}
// 通过 Context 控制后台 Goroutine 状态
ctx, cancel := context.WithCancel(context.Background())
wg.Add(1)
ch := GenerateNatural(ctx, &wg) // 自然数序列: 2, 3, 4, ...
for i := 0; i < 100; i++ {
prime := <-ch // 新出现的素数
fmt.Printf("%v: %v\n", i+1, prime)
wg.Add(1)
ch = PrimeFilter(ctx, ch, prime, &wg) // 基于新素数构造的过滤器
}
cancel()
wg.Wait()
}
```
在上面这个例子中主要有以下几点需要关注:
1. 通过 `for range` 循环保证了输入管道被关闭时,循环能退出,不会出现死循环;
2. 通过 `defer close` 保证了无论是输入管道被关闭,还是 ctx 被取消,只要素数筛退出,都会关闭输出管道。
至此,我们终于足够优美地解决了这个死锁问题。
并发是一个非常大的主题,我们这里只是展示几个非常基础的并发编程的例子。官方文档也有很多关于并发编程的讨论,国内也有专门讨论 Go 语言并发编程的书籍。读者可以根据自己的需求查阅相关的文献。
================================================
FILE: ch1-basic/ch1-07-error-and-panic.md
================================================
# 1.7 错误和异常
错误处理是每个编程语言都要考虑的一个重要话题。在 Go 语言的错误处理中,错误是软件包 API 和应用程序用户界面的一个重要组成部分。
在程序中总有一部分函数总是要求必须能够成功的运行。比如 `strconv.Itoa` 将整数转换为字符串,从数组或切片中读写元素,从 `map` 读取已经存在的元素等。这类操作在运行时几乎不会失败,除非程序中有 BUG,或遇到灾难性的、不可预料的情况,比如运行时的内存溢出。如果真的遇到真正异常情况,我们只要简单终止程序就可以了。
排除异常的情况,如果程序运行失败仅被认为是几个预期的结果之一。对于那些将运行失败看作是预期结果的函数,它们会返回一个额外的返回值,通常是最后一个来传递错误信息。如果导致失败的原因只有一个,额外的返回值可以是一个布尔值,通常被命名为 ok。比如,当从一个 `map` 查询一个结果时,可以通过额外的布尔值判断是否成功:
```go
if v, ok := m["key"]; ok {
return v
}
```
但是导致失败的原因通常不止一种,很多时候用户希望了解更多的错误信息。如果只是用简单的布尔类型的状态值将不能满足这个要求。在 C 语言中,默认采用一个整数类型的 `errno` 来表达错误,这样就可以根据需要定义多种错误类型。在 Go 语言中,`syscall.Errno` 就是对应 C 语言中 `errno` 类型的错误。在 `syscall` 包中的接口,如果有返回错误的话,底层也是 `syscall.Errno` 错误类型。
比如我们通过 `syscall` 包的接口来修改文件的模式时,如果遇到错误我们可以通过将 `err` 强制断言为 `syscall.Errno` 错误类型来处理:
```go
err := syscall.Chmod(":invalid path:", 0666)
if err != nil {
log.Fatal(err.(syscall.Errno))
}
```
我们还可以进一步地通过类型查询或类型断言来获取底层真实的错误类型,这样就可以获取更详细的错误信息。不过一般情况下我们并不关心错误在底层的表达方式,我们只需要知道它是一个错误就可以了。当返回的错误值不是 `nil` 时,我们可以通过调用 `error` 接口类型的 `Error` 方法来获得字符串类型的错误信息。
在 Go 语言中,错误被认为是一种可以预期的结果;而异常则是一种非预期的结果,发生异常可能表示程序中存在 BUG 或发生了其它不可控的问题。Go 语言推荐使用 `recover` 函数将内部异常转为错误处理,这使得用户可以真正的关心业务相关的错误处理。
如果某个接口简单地将所有普通的错误当做异常抛出,将会使错误信息杂乱且没有价值。就像在 `main` 函数中直接捕获全部一样,是没有意义的:
```go
func main() {
defer func() {
if r := recover(); r != nil {
log.Fatal(r)
}
}()
...
}
```
捕获异常不是最终的目的。如果异常不可预测,直接输出异常信息是最好的处理方式。
## 1.7.1 错误处理策略
让我们演示一个文件复制的例子:函数需要打开两个文件,然后将其中一个文件的内容复制到另一个文件:
```go
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
dst, err := os.Create(dstName)
if err != nil {
return
}
written, err = io.Copy(dst, src)
dst.Close()
src.Close()
return
}
```
上面的代码虽然能够工作,但是隐藏一个 bug。如果第一个 `os.Open` 调用成功,但是第二个 `os.Create` 调用失败,那么会在没有释放 `src` 文件资源的情况下返回。虽然我们可以通过在第二个返回语句前添加 `src.Close()` 调用来修复这个 BUG;但是当代码变得复杂时,类似的问题将很难被发现和修复。我们可以通过 `defer` 语句来确保每个被正常打开的文件都能被正常关闭:
```go
func CopyFile(dstName, srcName string) (written int64, err error) {
src, err := os.Open(srcName)
if err != nil {
return
}
defer src.Close()
dst, err := os.Create(dstName)
if err != nil {
return
}
defer dst.Close()
return io.Copy(dst, src)
}
```
`defer`语句可以让我们在打开文件时马上思考如何关闭文件。不管函数如何返回,文件关闭语句始终会被执行。同时 `defer` 语句可以保证,即使 `io.Copy` 发生了异常,文件依然可以安全地关闭。
前文我们说到,Go 语言中的导出函数一般不抛出异常,一个未受控的异常可以看作是程序的 BUG。但是对于那些提供类似 Web 服务的框架而言;它们经常需要接入第三方的中间件。因为第三方的中间件是否存在 BUG 是否会抛出异常,Web 框架本身是不能确定的。为了提高系统的稳定性,Web 框架一般会通过 `recover` 来防御性地捕获所有处理流程中可能产生的异常,然后将异常转为普通的错误返回。
让我们以 JSON 解析器为例,说明 recover 的使用场景。考虑到 JSON 解析器的复杂性,即使某个语言解析器目前工作正常,也无法肯定它没有漏洞。因此,当某个异常出现时,我们不会选择让解析器崩溃,而是会将 panic 异常当作普通的解析错误,并附加额外信息提醒用户报告此错误。
```go
func ParseJSON(input string) (s *Syntax, err error) {
defer func() {
if p := recover(); p != nil {
err = fmt.Errorf("JSON: internal error: %v", p)
}
}()
// ...parser...
}
```
标准库中的 `json` 包,在内部递归解析 JSON 数据的时候如果遇到错误,会通过抛出异常的方式来快速跳出深度嵌套的函数调用,然后由最外一级的接口通过 `recover` 捕获 `panic`,然后返回相应的错误信息。
Go 语言库的实现习惯: 即使在包内部使用了 `panic`,但是在导出函数时会被转化为明确的错误值。
## 1.7.2 获取错误的上下文
有时候为了方便上层用户理解;底层实现者会将底层的错误重新包装为新的错误类型返回给用户:
```go
if _, err := html.Parse(resp.Body); err != nil {
return nil, fmt.Errorf("parsing %s as HTML: %v", url,err)
}
```
上层用户在遇到错误时,可以很容易从业务层面理解错误发生的原因。但是鱼和熊掌总是很难兼得,在上层用户获得新的错误的同时,我们也丢失了底层最原始的错误类型(只剩下错误描述信息了)。
为了记录这种错误类型在包装的变迁过程中的信息,我们一般会定义一个辅助的 `WrapError` 函数,用于包装原始的错误,同时保留完整的原始错误类型。为了问题定位的方便,同时也为了能记录错误发生时的函数调用状态,我们很多时候希望在出现致命错误的时候保存完整的函数调用信息。同时,为了支持 RPC 等跨网络的传输,我们可能要需要将错误序列化为类似 JSON 格式的数据,然后再从这些数据中将错误解码恢复出来。
为此,我们可以定义自己的 `github.com/chai2010/errors` 包,里面是以下的错误类型:
```go
type Error interface {
Caller() []CallerInfo
Wraped() []error
Code() int
error
private()
}
type CallerInfo struct {
FuncName string
FileName string
FileLine int
}
```
其中 `Error` 为接口类型,是 `error` 接口类型的扩展,用于给错误增加调用栈信息,同时支持错误的多级嵌套包装,支持错误码格式。为了使用方便,我们可以定义以下的辅助函数:
```go
func New(msg string) error
func NewWithCode(code int, msg string) error
func Wrap(err error, msg string) error
func WrapWithCode(code int, err error, msg string) error
func FromJson(json string) (Error, error)
func ToJson(err error) string
```
`New`用于构建新的错误类型,和标准库中 `errors.New` 功能类似,但是增加了出错时的函数调用栈信息。`FromJson` 用于从 JSON 字符串编码的错误中恢复错误对象。`NewWithCode` 则是构造一个带错误码的错误,同时也包含出错时的函数调用栈信息。`Wrap` 和 `WrapWithCode` 则是错误二次包装函数,用于将底层的错误包装为新的错误,但是保留的原始的底层错误信息。这里返回的错误对象都可以直接调用 `json.Marshal` 将错误编码为 JSON 字符串。
我们可以这样使用包装函数:
```go
import (
"github.com/chai2010/errors"
)
func loadConfig() error {
_, err := ioutil.ReadFile("/path/to/file")
if err != nil {
return errors.Wrap(err, "read failed")
}
// ...
}
func setup() error {
err := loadConfig()
if err != nil {
return errors.Wrap(err, "invalid config")
}
// ...
}
func main() {
if err := setup(); err != nil {
log.Fatal(err)
}
// ...
}
```
上面的例子中,错误被进行了 2 层包装。我们可以这样遍历原始错误经历了哪些包装流程:
```go
for i, e := range err.(errors.Error).Wraped() {
fmt.Printf("wrapped(%d): %v\n", i, e)
}
```
同时也可以获取每个包装错误的函数调用堆栈信息:
```go
for i, x := range err.(errors.Error).Caller() {
fmt.Printf("caller:%d: %s\n", i, x.FuncName)
}
```
如果需要将错误通过网络传输,可以用 `errors.ToJson(err)` 编码为 JSON 字符串:
```go
// 以 JSON 字符串方式发送错误
func sendError(ch chan<- string, err error) {
ch <- errors.ToJson(err)
}
// 接收 JSON 字符串格式的错误
func recvError(ch <-chan string) error {
p, err := errors.FromJson(<-ch)
if err != nil {
log.Fatal(err)
}
return p
}
```
对于基于 http 协议的网络服务,我们还可以给错误绑定一个对应的 http 状态码:
```go
err := errors.NewWithCode(404, "http error code")
fmt.Println(err)
fmt.Println(err.(errors.Error).Code())
```
在 Go 语言中,错误处理也有一套独特的编码风格。检查某个子函数是否失败后,我们通常将处理失败的逻辑代码放在处理成功的代码之前。如果某个错误会导致函数返回,那么成功时的逻辑代码不应放在 `else` 语句块中,而应直接放在函数体中。
```go
f, err := os.Open("filename.ext")
if err != nil {
// 失败的情形, 马上返回错误
}
// 正常的处理流程
```
Go 语言中大部分函数的代码结构几乎相同,首先是一系列的初始检查,用于防止错误发生,之后是函数的实际逻辑。
## 1.7.3 错误的错误返回
Go 语言中的错误是一种接口类型。接口信息中包含了原始类型和原始的值。只有当接口的类型和原始的值都为空的时候,接口的值才对应 `nil`。其实当接口中类型为空的时候,原始值必然也是空的;反之,当接口对应的原始值为空的时候,接口对应的原始类型并不一定为空的。
在下面的例子中,试图返回自定义的错误类型,当没有错误的时候返回 `nil`:
```go
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
```
但是,最终返回的结果其实并非是 `nil`:是一个正常的错误,错误的值是一个 `MyError` 类型的空指针。下面是改进的 `returnsError`:
```go
func returnsError() error {
if bad() {
return (*MyError)(err)
}
return nil
}
```
因此,在处理错误返回值的时候,没有错误的返回值最好直接写为 `nil`。
Go 语言作为一个强类型语言,不同类型之间必须要显式的转换(而且必须有相同的基础类型)。但是,Go 语言中 `interface` 是一个例外:非接口类型到接口类型,或者是接口类型之间的转换都是隐式的。这是为了支持鸭子类型,当然会牺牲一定的安全性。
## 1.7.4 剖析异常
`panic`支持抛出任意类型的异常(而不仅仅是 `error` 类型的错误),`recover` 函数调用的返回值和 `panic` 函数的输入参数类型一致,它们的函数签名如下:
```go
func panic(interface{})
func recover() interface{}
```
Go 语言函数调用的正常流程是函数执行返回语句返回结果,在这个流程中是没有异常的,因此在这个流程中执行 `recover` 异常捕获函数始终是返回 `nil`。另一种是异常流程: 当函数调用 `panic` 抛出异常,函数将停止执行后续的普通语句,但是之前注册的 `defer` 函数调用仍然保证会被正常执行,然后再返回到调用者。对于当前函数的调用者,因为处理异常状态还没有被捕获,和直接调用 `panic` 函数的行为类似。在异常发生时,如果在 `defer` 中执行 `recover` 调用,它可以捕获触发 `panic` 时的参数,并且恢复到正常的执行流程。
在非 `defer` 语句中执行 `recover` 调用是初学者常犯的错误:
```go
func main() {
if r := recover(); r != nil {
log.Fatal(r)
}
panic(123)
if r := recover(); r != nil {
log.Fatal(r)
}
}
```
上面程序中两个 `recover` 调用都不能捕获任何异常。在第一个 `recover` 调用执行时,函数必然是在正常的非异常执行流程中,这时候 `recover` 调用将返回 `nil`。发生异常时,第二个 `recover` 调用将没有机会被执行到,因为 `panic` 调用会导致函数马上执行已经注册 `defer` 的函数后返回。
其实 `recover` 函数调用有着更严格的要求:我们必须在 `defer` 函数中直接调用 `recover`。如果 `defer` 中调用的是 `recover` 函数的包装函数的话,异常的捕获工作将失败!比如,有时候我们可能希望包装自己的 `MyRecover` 函数,在内部增加必要的日志信息然后再调用 `recover`,这是错误的做法:
```go
func main() {
defer func() {
// 无法捕获异常
if r := MyRecover(); r != nil {
fmt.Println(r)
}
}()
panic(1)
}
func MyRecover() interface{} {
log.Println("trace...")
return recover()
}
```
同样,如果是在嵌套的 `defer` 函数中调用 `recover` 也将导致无法捕获异常:
```go
func main() {
defer func() {
defer func() {
// 无法捕获异常
if r := recover(); r != nil {
fmt.Println(r)
}
}()
}()
panic(1)
}
```
2 层嵌套的 `defer` 函数中直接调用 `recover` 和 1 层 `defer` 函数中调用包装的 `MyRecover` 函数一样,都是经过了 2 个函数帧才到达真正的 `recover` 函数,这个时候 Goroutine 的对应上一级栈帧中已经没有异常信息。
如果我们直接在 `defer` 语句中调用 `MyRecover` 函数又可以正常工作了:
```go
func MyRecover() interface{} {
return recover()
}
func main() {
// 可以正常捕获异常
defer MyRecover()
panic(1)
}
```
但是,如果 `defer` 语句直接调用 `recover` 函数,依然不能正常捕获异常:
```go
func main() {
// 无法捕获异常
defer recover()
panic(1)
}
```
必须要和有异常的栈帧只隔一个栈帧,`recover` 函数才能正常捕获异常。换言之,`recover` 函数捕获的是祖父一级调用函数栈帧的异常(刚好可以跨越一层 `defer` 函数)!
当然,为了避免 `recover` 调用者不能识别捕获到的异常, 应该避免用 `nil` 为参数抛出异常:
```go
func main() {
defer func() {
if r := recover(); r != nil { ... }
// 虽然总是返回 nil, 但是可以恢复异常状态
}()
// 警告: 用 nil 为参数抛出异常
panic(nil)
}
```
当希望将捕获到的异常转为错误时,如果希望忠实返回原始的信息,需要针对不同的类型分别处理:
```go
func foo() (err error) {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = fmt.Errorf("Unknown panic: %v", r)
}
}
}()
panic("TODO")
}
```
基于这个代码模板,我们甚至可以模拟出不同类型的异常。通过为定义不同类型的保护接口,我们就可以区分异常的类型了:
```go
func main {
defer func() {
if r := recover(); r != nil {
switch x := r.(type) {
case runtime.Error:
// 这是运行时错误类型异常
case error:
// 普通错误类型异常
default:
// 其他类型异常
}
}
}()
// ...
}
```
不过这样做和 Go 语言简单直接的编程哲学背道而驰了。
================================================
FILE: ch1-basic/ch1-08-ext.md
================================================
## 1.8 补充说明
本书定位是 Go 语言进阶图书,因此读者需要有一定的 Go 语言基础。如果对 Go 语言不太了解,作者推荐通过以下资料开始学习 Go 语言。首先是安装 Go 语言环境,然后通过 `go tool tour` 命令打开“A Tour of Go”教程学习。在学习“A Tour of Go”教程的同时,可以阅读 Go 语言官方团队出版的[《The Go Programming Language》](http://www.gopl.io/)教程。[《The Go Programming Language》](http://www.gopl.io/)在国内 Go 语言社区被称为 Go 语言圣经,它将带你系统地学习 Go 语言。在学习的同时可以尝试用 Go 语言解决一些小问题,如果遇到要查阅 API 的时候可以通过 `godoc` 命令打开自带的文档查询。Go 语言本身不仅仅包含了所有的文档,也包含了所有标准库的实现代码,这是第一手的最权威的 Go 语言资料。我们认为此时你应该已经可以熟练使用 Go 语言了。
================================================
FILE: ch1-basic/readme.md
================================================
# 第 1 章 语言基础
*我不知道,你过去 10 年为什么不快乐。但相信我,抛掉过去的沉重,使用 Go 语言,体会最初的快乐!——469856321*
*搬砖民工也会建成自己的罗马帝国。——小张*
---
本章首先简要介绍 Go 语言的发展历史,并较详细地分析了“Hello World”程序在各个祖先语言中演化过程。然后,对以数组、字符串和切片为代表的基础结构,对以函数、方法和接口所体现的面向过程和鸭子对象的编程,以及 Go 语言特有的并发编程模型和错误处理哲学做了简单介绍。最后,针对 macOS、Windows、Linux 几个主流的开发平台,推荐了几个较友好的 Go 语言编辑器和集成开发环境,因为好的工具可以极大地提高我们的效率。
================================================
FILE: ch2-cgo/ch2-01-hello-cgo.md
================================================
# 2.1 快速入门
本节我们将通过一系列由浅入深的小例子来快速掌握 CGO 的基本用法。
## 2.1.1 最简 CGO 程序
真实的 CGO 程序一般都比较复杂。不过我们可以由浅入深,一个最简的 CGO 程序该是什么样的呢?要构造一个最简 CGO 程序,首先要忽视一些复杂的 CGO 特性,同时要展示 CGO 程序和纯 Go 程序的差别来。下面是我们构建的最简 CGO 程序:
```go
// hello.go
package main
import "C"
func main() {
println("hello cgo")
}
```
代码通过 `import "C"` 语句启用 CGO 特性,主函数只是通过 Go 内置的 println 函数输出字符串,其中并没有任何和 CGO 相关的代码。虽然没有调用 CGO 的相关函数,但是 `go build` 命令会在编译和链接阶段启动 gcc 编译器,这已经是一个完整的 CGO 程序了。
## 2.1.2 基于 C 标准库函数输出字符串
第一章那个 CGO 程序还不够简单,我们现在来看看更简单的版本:
```go
// hello.go
package main
//#include <stdio.h>
import "C"
func main() {
C.puts(C.CString("Hello, World\n"))
}
```
我们不仅仅通过 `import "C"` 语句启用 CGO 特性,同时包含 C 语言的 `<stdio.h>` 头文件。然后通过 CGO 包的 `C.CString` 函数将 Go 语言字符串转为 C 语言字符串,最后调用 CGO 包的 `C.puts` 函数向标准输出窗口打印转换后的 C 字符串。
相比 “Hello, World 的革命” 一节中的 CGO 程序最大的不同是:我们没有在程序退出前释放 `C.CString` 创建的 C 语言字符串;还有我们改用 `puts` 函数直接向标准输出打印,之前是采用 `fputs` 向标准输出打印。
没有释放使用 `C.CString` 创建的 C 语言字符串会导致内存泄漏。但是对于这个小程序来说,这样是没有问题的,因为程序退出后操作系统会自动回收程序的所有资源。
## 2.1.3 使用自己的 C 函数
前面我们使用了标准库中已有的函数。现在我们先自定义一个叫 `SayHello` 的 C 函数来实现打印,然后从 Go 语言环境中调用这个 `SayHello` 函数:
```go
// hello.go
package main
/*
#include <stdio.h>
static void SayHello(const char* s) {
puts(s);
}
*/
import "C"
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
```
除了 `SayHello` 函数是我们自己实现的之外,其它的部分和前面的例子基本相似。
我们也可以将 `SayHello` 函数放到当前目录下的一个 C 语言源文件中(后缀名必须是 `.c`)。因为是编写在独立的 C 文件中,为了允许外部引用,所以需要去掉函数的 `static` 修饰符。
```c
// hello.c
#include <stdio.h>
void SayHello(const char* s) {
puts(s);
}
```
然后在 CGO 部分先声明 `SayHello` 函数,其它部分不变:
```go
// hello.go
package main
//void SayHello(const char* s);
import "C"
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
```
注意,如果之前运行的命令是 `go run hello.go` 或 `go build hello.go` 的话,此处须使用 `go run "your/package"` 或 `go build "your/package"` 才可以。若本就在包路径下的话,也可以直接运行 `go run .` 或 `go build`。
既然 `SayHello` 函数已经放到独立的 C 文件中了,我们自然可以将对应的 C 文件编译打包为静态库或动态库文件供使用。如果是以静态库或动态库方式引用 `SayHello` 函数的话,需要将对应的 C 源文件移出当前目录(CGO 构建程序会自动构建当前目录下的 C 源文件,从而导致 C 函数名冲突)。关于静态库等细节将在稍后章节讲解。
## 2.1.4 C 代码的模块化
在编程过程中,抽象和模块化是将复杂问题简化的通用手段。当代码语句变多时,我们可以将相似的代码封装到一个个函数中;当程序中的函数变多时,我们将函数拆分到不同的文件或模块中。而模块化编程的核心是面向程序接口编程(这里的接口并不是 Go 语言的 interface,而是 API 的概念)。
在前面的例子中,我们可以抽象一个名为 hello 的模块,模块的全部接口函数都在 hello.h 头文件定义:
```c
// hello.h
void SayHello(const char* s);
```
其中只有一个 SayHello 函数的声明。但是作为 hello 模块的用户来说,就可以放心地使用 SayHello 函数,而无需关心函数的具体实现。而作为 SayHello 函数的实现者来说,函数的实现只要满足头文件中函数的声明的规范即可。下面是 SayHello 函数的 C 语言实现,对应 hello.c 文件:
```c
// hello.c
#include "hello.h"
#include <stdio.h>
void SayHello(const char* s) {
puts(s);
}
```
在 hello.c 文件的开头,实现者通过 `#include "hello.h"` 语句包含 SayHello 函数的声明,这样可以保证函数的实现满足模块对外公开的接口。
接口文件 hello.h 是 hello 模块的实现者和使用者共同的约定,但是该约定并没有要求必须使用 C 语言来实现 SayHello 函数。我们也可以用 C++ 语言来重新实现这个 C 语言函数:
```c++
// hello.cpp
#include <iostream>
extern "C" {
#include "hello.h"
}
void SayHello(const char* s) {
std::cout << s;
}
```
在 C++ 版本的 SayHello 函数实现中,我们通过 C++ 特有的 `std::cout` 输出流输出字符串。不过为了保证 C++ 语言实现的 SayHello 函数满足 C 语言头文件 hello.h 定义的函数规范,我们需要通过 `extern "C"` 语句指示该函数的链接符号遵循 C 语言的规则。
在采用面向 C 语言 API 接口编程之后,我们彻底解放了模块实现者的语言枷锁:实现者可以用任何编程语言实现模块,只要最终满足公开的 API 约定即可。我们可以用 C 语言实现 SayHello 函数,也可以使用更复杂的 C++ 语言来实现 SayHello 函数,当然我们也可以用汇编语言甚至 Go 语言来重新实现 SayHello 函数。
## 2.1.5 用 Go 重新实现 C 函数
其实 CGO 不仅仅用于 Go 语言中调用 C 语言函数,还可以用于导出 Go 语言函数给 C 语言函数调用。在前面的例子中,我们已经抽象一个名为 hello 的模块,模块的全部接口函数都在 hello.h 头文件定义:
```c
// hello.h
void SayHello(/*const*/ char* s);
```
现在我们创建一个 hello.go 文件,用 Go 语言重新实现 C 语言接口的 SayHello 函数:
```go
// hello.go
package main
import "C"
import "fmt"
//export SayHello
func SayHello(s *C.char) {
fmt.Print(C.GoString(s))
}
```
我们通过 CGO 的 `//export SayHello` 指令将 Go 语言实现的函数 `SayHello` 导出为 C 语言函数。为了适配 CGO 导出的 C 语言函数,我们禁止了在函数的声明语句中的 const 修饰符。需要注意的是,这里其实有两个版本的 `SayHello` 函数:一个 Go 语言环境的;另一个是 C 语言环境的。cgo 生成的 C 语言版本 SayHello 函数最终会通过桥接代码调用 Go 语言版本的 SayHello 函数。
通过面向 C 语言接口的编程技术,我们不仅仅解放了函数的实现者,同时也简化的函数的使用者。现在我们可以将 SayHello 当作一个标准库的函数使用(和 puts 函数的使用方式类似):
```go
package main
//#include "hello.h"
import "C"
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
```
一切似乎都回到了开始的 CGO 代码,但是代码内涵更丰富了。
## 2.1.6 面向 C 接口的 Go 编程
在开始的例子中,我们的全部 CGO 代码都在一个 Go 文件中。然后,通过面向 C 接口编程的技术将 SayHello 分别拆分到不同的 C 文件,而 main 依然是 Go 文件。再然后,是用 Go 函数重新实现了 C 语言接口的 SayHello 函数。但是对于目前的例子来说只有一个函数,要拆分到三个不同的文件确实有些繁琐了。
正所谓合久必分、分久必合,我们现在尝试将例子中的几个文件重新合并到一个 Go 文件。下面是合并后的成果:
```go
package main
//void SayHello(char* s);
import "C"
import (
"fmt"
)
func main() {
C.SayHello(C.CString("Hello, World\n"))
}
//export SayHello
func SayHello(s *C.char) {
fmt.Print(C.GoString(s))
}
```
现在版本的 CGO 代码中 C 语言代码的比例已经很少了,但是我们依然可以进一步以 Go 语言的思维来提炼我们的 CGO 代码。通过分析可以发现 `SayHello` 函数的参数如果可以直接使用 Go 字符串是最直接的。在 Go1.10 中 CGO 新增加了一个 `_GoString_` 预定义的 C 语言类型,用来表示 Go 语言字符串。下面是改进后的代码:
```go
// +build go1.10
package main
//void SayHello(_GoString_ s);
import "C"
import (
"fmt"
)
func main() {
C.SayHello("Hello, World\n")
}
//export SayHello
func SayHello(s string) {
fmt.Print(s)
}
```
虽然看起来全部是 Go 语言代码,但是执行的时候是先从 Go 语言的 `main` 函数,到 CGO 自动生成的 C 语言版本 `SayHello` 桥接函数,最后又回到了 Go 语言环境的 `SayHello` 函数。这个代码包含了 CGO 编程的精华,读者需要深入理解。
*思考题: main 函数和 SayHello 函数是否在同一个 Goroutine 里执行?*
================================================
FILE: ch2-cgo/ch2-02-basic.md
================================================
# 2.2 CGO 基础
要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下是要安装 GCC,在 windows 下是需要安装 MinGW 工具。同时需要保证环境变量 `CGO_ENABLED` 被设置为 1,这表示 CGO 是被启用的状态。在本地构建时 `CGO_ENABLED` 默认是启用的,当交叉构建时 CGO 默认是禁止的。比如要交叉构建 ARM 环境运行的 Go 程序,需要手工设置好 C/C++ 交叉构建的工具链,同时开启 `CGO_ENABLED` 环境变量。然后通过 `import "C"` 语句启用 CGO 特性。
## 2.2.1 `import "C"` 语句
如果在 Go 代码中出现了 `import "C"` 语句则表示使用了 CGO 特性,紧跟在这行语句前面的注释是一种特殊语法,里面包含的是正常的 C 语言代码。当确保 CGO 启用的情况下,还可以在当前目录中包含 C/C++ 对应的源文件。
举个最简单的例子:
```Go
package main
/*
#include <stdio.h>
void printint(int v) {
printf("printint: %d\n", v);
}
*/
import "C"
func main() {
v := 42
C.printint(C.int(v))
}
```
这个例子展示了 cgo 的基本使用方法。开头的注释中写了要调用的 C 函数和相关的头文件,头文件被 include 之后里面的所有的 C 语言元素都会被加入到”C” 这个虚拟的包中。import "C" 导入语句推荐单独一行,不与其他包一同 import(在 Go 早期版本中必须单独 import)。向 C 函数传递参数也很简单,就直接转化成对应 C 语言类型传递就可以。如上例中 `C.int(v)` 用于将一个 Go 中的 int 类型值强制类型转换转化为 C 语言中的 int 类型值,然后调用 C 语言定义的 printint 函数进行打印。
需要注意的是,Go 是强类型语言,所以 cgo 中传递的参数类型必须与声明的类型完全一致,而且传递前必须用”C” 中的转化函数转换成对应的 C 类型,不能直接传入 Go 中类型的变量。同时通过虚拟的 C 包导入的 C 语言符号并不需要是大写字母开头,它们不受 Go 语言的导出规则约束。
cgo 将当前包引用的 C 语言符号都放到了虚拟的 C 包中,同时当前包依赖的其它 Go 语言包内部可能也通过 cgo 引入了相似的虚拟 C 包,但是不同的 Go 语言包引入的虚拟的 C 包之间的类型是不能通用的。这个约束对于要自己构造一些 cgo 辅助函数时有可能会造成一点的影响。
比如我们希望在 Go 中定义一个 C 语言字符指针对应的 CChar 类型,然后增加一个 GoString 方法返回 Go 语言字符串:
```go
package cgo_helper
//#include <stdio.h>
import "C"
type CChar C.char
func (p *CChar) GoString() string {
return C.GoString((*C.char)(p))
}
func PrintCString(cs *C.char) {
C.puts(cs)
}
```
现在我们可能会想在其它的 Go 语言包中也使用这个辅助函数:
```go
package main
//static const char* cs = "hello";
import "C"
import "./cgo_helper"
func main() {
cgo_helper.PrintCString(C.cs)
}
```
这段代码是不能正常工作的,因为当前 main 包引入的 `C.cs` 变量的类型是当前 `main` 包的 cgo 构造的虚拟的 C 包下的 `*char` 类型(具体点是 `*C.char`,更具体点是 `*main.C.char`),它和 cgo_helper 包引入的 `*C.char` 类型(具体点是 `*cgo_helper.C.char`)是不同的。在 Go 语言中方法是依附于类型存在的,不同 Go 包中引入的虚拟的 C 包的类型却是不同的(`main.C` 不等 `cgo_helper.C`),这导致从它们延伸出来的 Go 类型也是不同的类型(`*main.C.char` 不等 `*cgo_helper.C.char`),这最终导致了前面代码不能正常工作。
有 Go 语言使用经验的用户可能会建议参数转型后再传入。但是这个方法似乎也是不可行的,因为 `cgo_helper.PrintCString` 的参数是它自身包引入的 `*C.char` 类型,在外部是无法直接获取这个类型的。换言之,一个包如果在公开的接口中直接使用了 `*C.char` 等类似的虚拟 C 包的类型,其它的 Go 包是无法直接使用这些类型的,除非这个 Go 包同时也提供了 `*C.char` 类型的构造函数。因为这些诸多因素,如果想在 go test 环境直接测试这些 cgo 导出的类型也会有相同的限制。
<!-- 测试代码;需要确实是否有问题 -->
## 2.2.2 `#cgo` 语句
在 `import "C"` 语句前的注释中可以通过 `#cgo` 语句设置编译阶段和链接阶段的相关参数。编译阶段的参数主要用于定义相关宏和指定头文件检索路径。链接阶段的参数主要是指定库文件检索路径和要链接的库文件。
```go
// #cgo CFLAGS: -DPNG_DEBUG=1 -I./include
// #cgo LDFLAGS: -L/usr/local/lib -lpng
// #include <png.h>
import "C"
```
上面的代码中,CFLAGS 部分,`-D` 部分定义了宏 PNG_DEBUG,值为 1;`-I` 定义了头文件包含的检索目录。LDFLAGS 部分,`-L` 指定了链接时库文件检索目录,`-l` 指定了链接时需要链接 png 库。
因为 C/C++ 遗留的问题,C 头文件检索目录可以是相对目录,但是库文件检索目录则需要绝对路径。在库文件的检索目录中可以通过 `${SRCDIR}` 变量表示当前包目录的绝对路径:
```
// #cgo LDFLAGS: -L${SRCDIR}/libs -lfoo
```
上面的代码在链接时将被展开为:
```
// #cgo LDFLAGS: -L/go/src/foo/libs -lfoo
```
`#cgo` 语句主要影响 CFLAGS、CPPFLAGS、CXXFLAGS、FFLAGS 和 LDFLAGS 几个编译器环境变量。LDFLAGS 用于设置链接时的参数,除此之外的几个变量用于改变编译阶段的构建参数 (CFLAGS 用于针对 C 语言代码设置编译参数)。
对于在 cgo 环境混合使用 C 和 C++ 的用户来说,可能有三种不同的编译选项:其中 CFLAGS 对应 C 语言特有的编译选项、CXXFLAGS 对应是 C++ 特有的编译选项、CPPFLAGS 则对应 C 和 C++ 共有的编译选项。但是在链接阶段,C 和 C++ 的链接选项是通用的,因此这个时候已经不再有 C 和 C++ 语言的区别,它们的目标文件的类型是相同的。
`#cgo` 指令还支持条件选择,当满足某个操作系统或某个 CPU 架构类型时后面的编译或链接选项生效。比如下面是分别针对 windows 和非 windows 下平台的编译和链接选项:
```
// #cgo windows CFLAGS: -DX86=1
// #cgo !windows LDFLAGS: -lm
```
其中在 windows 平台下,编译前会预定义 X86 宏为 1;在非 windows 平台下,在链接阶段会要求链接 math 数学库。这种用法对于在不同平台下只有少数编译选项差异的场景比较适用。
如果在不同的系统下 cgo 对应着不同的 c 代码,我们可以先使用 `#cgo` 指令定义不同的 C 语言的宏,然后通过宏来区分不同的代码:
```go
package main
/*
#cgo windows CFLAGS: -DCGO_OS_WINDOWS=1
#cgo darwin CFLAGS: -DCGO_OS_DARWIN=1
#cgo linux CFLAGS: -DCGO_OS_LINUX=1
#if defined(CGO_OS_WINDOWS)
const char* os = "windows";
#elif defined(CGO_OS_DARWIN)
const char* os = "darwin";
#elif defined(CGO_OS_LINUX)
const char* os = "linux";
#else
# error(unknown os)
#endif
*/
import "C"
func main() {
print(C.GoString(C.os))
}
```
这样我们就可以用 C 语言中常用的技术来处理不同平台之间的差异代码。
## 2.2.3 build tag 条件编译
build tag 是在 Go 或 cgo 环境下的 C/C++ 文件开头的一种特殊的注释。条件编译类似于前面通过 `#cgo` 指令针对不同平台定义的宏,只有在对应平台的宏被定义之后才会构建对应的代码。但是通过 `#cgo` 指令定义宏有个限制,它只能是基于 Go 语言支持的 windows、darwin 和 linux 等已经支持的操作系统。如果我们希望定义一个 DEBUG 标志的宏,`#cgo` 指令就无能为力了。而 Go 语言提供的 build tag 条件编译特性则可以简单做到。
比如下面的源文件只有在设置 debug 构建标志时才会被构建:
```go
// +build debug
package main
var buildMode = "debug"
```
可以用以下命令构建:
```
go build -tags="debug"
go build -tags="windows debug"
```
我们可以通过 `-tags` 命令行参数同时指定多个 build 标志,它们之间用空格分隔。
当有多个 build tag 时,我们将多个标志通过逻辑操作的规则来组合使用。比如以下的构建标志表示只有在”linux/386“或”darwin 平台下非 cgo 环境 “才进行构建。
```go
// +build linux,386 darwin,!cgo
```
其中 `linux,386` 中 linux 和 386 用逗号链接表示 AND 的意思;而 `linux,386` 和 `darwin,!cgo` 之间通过空白分割来表示 OR 的意思。
================================================
FILE: ch2-cgo/ch2-03-cgo-types.md
================================================
# 2.3 类型转换
最初 CGO 是为了达到方便从 Go 语言函数调用 C 语言函数(用 C 语言实现 Go 语言声明的函数)以复用 C 语言资源这一目的而出现的(因为 C 语言还会涉及回调函数,自然也会涉及到从 C 语言函数调用 Go 语言函数(用 Go 语言实现 C 语言声明的函数))。现在,它已经演变为 C 语言和 Go 语言双向通讯的桥梁。要想利用好 CGO 特性,自然需要了解此二语言类型之间的转换规则,这是本节要讨论的问题。
## 2.3.1 数值类型
在 Go 语言中访问 C 语言的符号时,一般是通过虚拟的 “C” 包访问,比如 `C.int` 对应 C 语言的 `int` 类型。有些 C 语言的类型是由多个关键字组成,但通过虚拟的 “C” 包访问 C 语言类型时名称部分不能有空格字符,比如 `unsigned int` 不能直接通过 `C.unsigned int` 访问。因此 CGO 为 C 语言的基础数值类型都提供了相应转换规则,比如 `C.uint` 对应 C 语言的 `unsigned int`。
Go 语言中数值类型和 C 语言数据类型基本上是相似的,以下是它们的对应关系表 2-1 所示。
C 语言类型 | CGO 类型 | Go 语言类型
---------------------- | ----------- | ---------
char | C.char | byte
singed char | C.schar | int8
unsigned char | C.uchar | uint8
short | C.short | int16
unsigned short | C.ushort | uint16
int | C.int | int32
unsigned int | C.uint | uint32
long | C.long | int32
unsigned long | C.ulong | uint32
long long int | C.longlong | int64
unsigned long long int | C.ulonglong | uint64
float | C.float | float32
double | C.double | float64
size_t | C.size_t | uint
*表 2-1 Go 语言和 C 语言类型对比*
需要注意的是,虽然在 C 语言中 `int`、`short` 等类型没有明确定义内存大小,但是在 CGO 中它们的内存大小是确定的。在 CGO 中,C 语言的 `int` 和 `long` 类型都是对应 4 个字节的内存大小,`size_t` 类型可以当作 Go 语言 `uint` 无符号整数类型对待。
CGO 中,虽然 C 语言的 `int` 固定为 4 字节的大小,但是 Go 语言自己的 `int` 和 `uint` 却在 32 位和 64 位系统下分别对应 4 个字节和 8 个字节大小。如果需要在 C 语言中访问 Go 语言的 `int` 类型,可以通过 `GoInt` 类型访问,`GoInt` 类型在 CGO 工具生成的 `_cgo_export.h` 头文件中定义。其实在 `_cgo_export.h` 头文件中,每个基本的 Go 数值类型都定义了对应的 C 语言类型,它们一般都是以单词 Go 为前缀。下面是 64 位环境下,`_cgo_export.h` 头文件生成的 Go 数值类型的定义,其中 `GoInt` 和 `GoUint` 类型分别对应 `GoInt64` 和 `GoUint64`:
```c
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef float GoFloat32;
typedef double GoFloat64;
```
除了 `GoInt` 和 `GoUint` 之外,我们并不推荐直接访问 `GoInt32`、`GoInt64` 等类型。更好的做法是通过 C 语言的 C99 标准引入的 `<stdint.h>` 头文件。为了提高 C 语言的可移植性,在 `<stdint.h>` 文件中,不但每个数值类型都提供了明确内存大小,而且和 Go 语言的类型命名更加一致。Go 语言类型 `<stdint.h>` 头文件类型对比如表 2-2 所示。
C 语言类型 | CGO 类型 | Go 语言类型
-------- | ---------- | ---------
int8_t | C.int8_t | int8
uint8_t | C.uint8_t | uint8
int16_t | C.int16_t | int16
uint16_t | C.uint16_t | uint16
int32_t | C.int32_t | int32
uint32_t | C.uint32_t | uint32
int64_t | C.int64_t | int64
uint64_t | C.uint64_t | uint64
*表 2-2 `<stdint.h>` 类型对比*
前文说过,如果 C 语言的类型是由多个关键字组成,则无法通过虚拟的 “C” 包直接访问(比如 C 语言的 `unsigned short` 不能直接通过 `C.unsigned short` 访问)。但是,在 `<stdint.h>` 中通过使用 C 语言的 `typedef` 关键字将 `unsigned short` 重新定义为 `uint16_t` 这样一个单词的类型后,我们就可以通过 `C.uint16_t` 访问原来的 `unsigned short` 类型了。对于比较复杂的 C 语言类型,推荐使用 `typedef` 关键字提供一个规则的类型命名,这样更利于在 CGO 中访问。
## 2.3.2 Go 字符串和切片
在 CGO 生成的 `_cgo_export.h` 头文件中还会为 Go 语言的字符串、切片、字典、接口和管道等特有的数据类型生成对应的 C 语言类型:
```c
typedef struct {const char *p; GoInt n;} GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct {void *t; void *v;} GoInterface;
typedef struct {void *data; GoInt len; GoInt cap;} GoSlice;
```
不过需要注意的是,其中只有字符串和切片在 CGO 中有一定的使用价值,因为 CGO 为他们的某些 GO 语言版本的操作函数生成了 C 语言版本,因此二者可以在 Go 调用 C 语言函数时马上使用; 而 CGO 并未针对其他的类型提供相关的辅助函数,且 Go 语言特有的内存模型导致我们无法保持这些由 Go 语言管理的内存指针,所以它们 C 语言环境并无使用的价值。
在导出的 C 语言函数中我们可以直接使用 Go 字符串和切片。假设有以下两个导出函数:
```go
//export helloString
func helloString(s string) {}
//export helloSlice
func helloSlice(s []byte) {}
```
CGO 生成的 `_cgo_export.h` 头文件会包含以下的函数声明:
```c
extern void helloString(GoString p0);
extern void helloSlice(GoSlice p0);
```
不过需要注意的是,如果使用了 GoString 类型则会对 `_cgo_export.h` 头文件产生依赖,而这个头文件是动态输出的。
Go1.10 针对 Go 字符串增加了一个 `_GoString_` 预定义类型,可以降低在 cgo 代码中可能对 `_cgo_export.h` 头文件产生的循环依赖的风险。我们可以调整 helloString 函数的 C 语言声明为:
```c
extern void helloString(_GoString_ p0);
```
因为 `_GoString_` 是预定义类型,我们无法通过此类型直接访问字符串的长度和指针等信息。Go1.10 同时也增加了以下两个函数用于获取字符串结构中的长度和指针信息:
```c
size_t _GoStringLen(_GoString_ s);
const char *_GoStringPtr(_GoString_ s);
```
更严谨的做法是为 C 语言函数接口定义严格的头文件,然后基于稳定的头文件实现代码。
## 2.3.3 结构体、联合、枚举类型
C 语言的结构体、联合、枚举类型不能作为匿名成员被嵌入到 Go 语言的结构体中。在 Go 语言中,我们可以通过 `C.struct_xxx` 来访问 C 语言中定义的 `struct xxx` 结构体类型。结构体的内存布局按照 C 语言的通用对齐规则,在 32 位 Go 语言环境 C 语言结构体也按照 32 位对齐规则,在 64 位 Go 语言环境按照 64 位的对齐规则。对于指定了特殊对齐规则的结构体,无法在 CGO 中访问。
结构体的简单用法如下:
```go
/*
struct A {
int i;
float f;
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a.i)
fmt.Println(a.f)
}
```
如果结构体的成员名字中碰巧是 Go 语言的关键字,可以通过在成员名开头添加下划线来访问:
```go
/*
struct A {
int type; // type 是 Go 语言的关键字
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a._type) // _type 对应 type
}
```
但是如果有 2 个成员:一个是以 Go 语言关键字命名,另一个刚好是以下划线和 Go 语言关键字命名,那么以 Go 语言关键字命名的成员将无法访问(被屏蔽):
```go
/*
struct A {
int type; // type 是 Go 语言的关键字
float _type; // 将屏蔽 CGO 对 type 成员的访问
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a._type) // _type 对应 _type
}
```
C 语言结构体中位字段对应的成员无法在 Go 语言中访问,如果需要操作位字段成员,需要通过在 C 语言中定义辅助函数来完成。对应零长数组的成员,无法在 Go 语言中直接访问数组的元素,但其中零长的数组成员所在位置的偏移量依然可以通过 `unsafe.Offsetof(a.arr)` 来访问。
```go
/*
struct A {
int size: 10; // 位字段无法访问
float arr[]; // 零长的数组也无法访问
};
*/
import "C"
import "fmt"
func main() {
var a C.struct_A
fmt.Println(a.size) // 错误: 位字段无法访问
fmt.Println(a.arr) // 错误: 零长的数组也无法访问
}
```
在 C 语言中,我们无法直接访问 Go 语言定义的结构体类型。
对于联合类型,我们可以通过 `C.union_xxx` 来访问 C 语言中定义的 `union xxx` 类型。但是 Go 语言中并不支持 C 语言联合类型,它们会被转为对应大小的字节数组。
```go
/*
#include <stdint.h>
union B1 {
int i;
float f;
};
union B2 {
int8_t i8;
int64_t i64;
};
*/
import "C"
import "fmt"
func main() {
var b1 C.union_B1;
fmt.Printf("%T\n", b1) // [4]uint8
var b2 C.union_B2;
fmt.Printf("%T\n", b2) // [8]uint8
}
```
如果需要操作 C 语言的联合类型变量,一般有三种方法:第一种是在 C 语言中定义辅助函数;第二种是通过 Go 语言的 "encoding/binary" 手工解码成员 (需要注意大端小端问题);第三种是使用 `unsafe` 包强制转型为对应类型 (这是性能最好的方式)。下面展示通过 `unsafe` 包访问联合类型成员的方式:
```go
/*
#include <stdint.h>
union B {
int i;
float f;
};
*/
import "C"
import "fmt"
func main() {
var b C.union_B;
fmt.Println("b.i:", *(*C.int)(unsafe.Pointer(&b)))
fmt.Println("b.f:", *(*C.float)(unsafe.Pointer(&b)))
}
```
虽然 `unsafe` 包访问最简单、性能也最好,但是对于有嵌套联合类型的情况处理会导致问题复杂化。对于复杂的联合类型,推荐通过在 C 语言中定义辅助函数的方式处理。
对于枚举类型,我们可以通过 `C.enum_xxx` 来访问 C 语言中定义的 `enum xxx` 结构体类型。
```go
/*
enum C {
ONE,
TWO,
};
*/
import "C"
import "fmt"
func main() {
var c C.enum_C = C.TWO
fmt.Println(c)
fmt.Println(C.ONE)
fmt.Println(C.TWO)
}
```
在 C 语言中,枚举类型底层对应 `int` 类型,支持负数类型的值。我们可以通过 `C.ONE`、`C.TWO` 等直接访问定义的枚举值。
## 2.3.4 数组、字符串和切片
在 C 语言中,数组名其实对应于一个指针,指向特定类型特定长度的一段内存,但是这个指针不能被修改;当把数组名传递给一个函数时,实际上传递的是数组第一个元素的地址。为了讨论方便,我们将一段特定长度的内存统称为数组。C 语言的字符串是一个 char 类型的数组,字符串的长度需要根据表示结尾的 NULL 字符的位置确定。C 语言中没有切片类型。
在 Go 语言中,数组是一种值类型,而且数组的长度是数组类型的一个部分。Go 语言字符串对应一段长度确定的只读 byte 类型的内存。Go 语言的切片则是一个简化版的动态数组。
Go 语言和 C 语言的数组、字符串和切片之间的相互转换可以简化为 Go 语言的切片和 C 语言中指向一定长度内存的指针之间的转换。
CGO 的 C 虚拟包提供了以下一组函数,用于 Go 语言和 C 语言之间数组和字符串的双向转换:
```go
// Go string to C string
// The C string is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CString(string) *C.char
// Go []byte slice to C array
// The C array is allocated in the C heap using malloc.
// It is the caller's responsibility to arrange for it to be
// freed, such as by calling C.free (be sure to include stdlib.h
// if C.free is needed).
func C.CBytes([]byte) unsafe.Pointer
// C string to Go string
func C.GoString(*C.char) string
// C data with explicit length to Go string
func C.GoStringN(*C.char, C.int) string
// C data with explicit length to Go []byte
func C.GoBytes(unsafe.Pointer, C.int) []byte
```
其中 `C.CString` 针对输入的 Go 字符串,克隆一个 C 语言格式的字符串;返回的字符串由 C 语言的 `malloc` 函数分配,不使用时需要通过 C 语言的 `free` 函数释放。`C.CBytes` 函数的功能和 `C.CString` 类似,用于从输入的 Go 语言字节切片克隆一个 C 语言版本的字节数组,同样返回的数组需要在合适的时候释放。`C.GoString` 用于将从 NULL 结尾的 C 语言字符串克隆一个 Go 语言字符串。`C.GoStringN` 是另一个字符数组克隆函数。`C.GoBytes` 用于从 C 语言数组,克隆一个 Go 语言字节切片。
该组辅助函数都是以克隆的方式运行。当 Go 语言字符串和切片向 C 语言转换时,克隆的内存由 C 语言的 `malloc` 函数分配,最终可以通过 `free` 函数释放。当 C 语言字符串或数组向 Go 语言转换时,克隆的内存由 Go 语言分配管理。通过该组转换函数,转换前和转换后的内存依然在各自的语言环境中,它们并没有跨越 Go 语言和 C 语言。克隆方式实现转换的优点是接口和内存管理都很简单,缺点是克隆需要分配新的内存和复制操作都会导致额外的开销。
在 `reflect` 包中有字符串和切片的定义:
```go
type StringHeader struct {
Data uintptr
Len int
}
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
```
如果不希望单独分配内存,可以在 Go 语言中直接访问 C 语言的内存空间:
```go
/*
#include <string.h>
char arr[10];
char *s = "Hello";
*/
import "C"
import (
"reflect"
"unsafe"
)
func main() {
// 通过 reflect.SliceHeader 转换
var arr0 []byte
var arr0Hdr = (*reflect.SliceHeader)(unsafe.Pointer(&arr0))
arr0Hdr.Data = uintptr(unsafe.Pointer(&C.arr[0]))
arr0Hdr.Len = 10
arr0Hdr.Cap = 10
// 通过切片语法转换
arr1 := (*[31]byte)(unsafe.Pointer(&C.arr[0]))[:10:10]
var s0 string
var s0Hdr = (*reflect.StringHeader)(unsafe.Pointer(&s0))
s0Hdr.Data = uintptr(unsafe.Pointer(C.s))
s0Hdr.Len = int(C.strlen(C.s))
sLen := int(C.strlen(C.s))
s1 := string((*[31]byte)(unsafe.Pointer(C.s))[:sLen:sLen])
}
```
因为 Go 语言的字符串是只读的,用户需要自己保证 Go 字符串在使用期间,底层对应的 C 字符串内容不会发生变化、内存不会被提前释放掉。
在 CGO 中,会为字符串和切片生成和上面结构对应的 C 语言版本的结构体:
```c
typedef struct {const char *p; GoInt n;} GoString;
typedef struct {void *data; GoInt len; GoInt cap;} GoSlice;
```
在 C 语言中可以通过 `GoString` 和 `GoSlice` 来访问 Go 语言的字符串和切片。如果是 Go 语言中数组类型,可以将数组转为切片后再行转换。如果字符串或切片对应的底层内存空间由 Go 语言的运行时管理,那么在 C 语言中不能长时间保存 Go 内存对象。
关于 CGO 内存模型的细节在稍后章节中会详细讨论。
## 2.3.5 指针间的转换
在 C 语言中,不同类型的指针是可以显式或隐式转换的,如果是隐式只是会在编译时给出一些警告信息。但是 Go 语言对于不同类型的转换非常严格,任何 C 语言中可能出现的警告信息在 Go 语言中都可能是错误!指针是 C 语言的灵魂,指针间的自由转换也是 cgo 代码中经常要解决的第一个重要的问题。
在 Go 语言中两个指针的类型完全一致则不需要转换可以直接通用。如果一个指针类型是用 type 命令在另一个指针类型基础之上构建的,换言之两个指针底层是相同完全结构的指针,那么我我们可以通过直接强制转换语法进行指针间的转换。但是 cgo 经常要面对的是 2 个完全不同类型的指针间的转换,原则上这种操作在纯 Go 语言代码是严格禁止的。
cgo 存在的一个目的就是打破 Go 语言的禁止,恢复 C 语言应有的指针的自由转换和指针运算。以下代码演示了如何将 X 类型的指针转化为 Y 类型的指针:
```go
var p *X
var q *Y
q = (*Y)(unsafe.Pointer(p)) // *X => *Y
p = (*X)(unsafe.Pointer(q)) // *Y => *X
```
为了实现 X 类型指针到 Y 类型指针的转换,我们需要借助 `unsafe.Pointer` 作为中间桥接类型实现不同类型指针之间的转换。`unsafe.Pointer` 指针类型类似 C 语言中的 `void*` 类型的指针。
下面是指针间的转换流程的示意图:

*图 2-1 X 类型指针转 Y 类型指针*
任何类型的指针都可以通过强制转换为 `unsafe.Pointer` 指针类型去掉原有的类型信息,然后再重新赋予新的指针类型而达到指针间的转换的目的。
## 2.3.6 数值和指针的转换
不同类型指针间的转换看似复杂,但是在 cgo 中已经算是比较简单的了。在 C 语言中经常遇到用普通数值表示指针的场景,也就是说如何实现数值和指针的转换也是 cgo 需要面对的一个问题。
为了严格控制指针的使用,Go 语言禁止将数值类型直接转为指针类型!不过,Go 语言针对 `unsafe.Pointr` 指针类型特别定义了一个 uintptr 类型。我们可以 uintptr 为中介,实现数值类型到 `unsafe.Pointr` 指针类型到转换。再结合前面提到的方法,就可以实现数值和指针的转换了。
下面流程图演示了如何实现 int32 类型到 C 语言的 `char*` 字符串指针类型的相互转换:

*图 2-2 int32 和 `char*` 指针转换*
转换分为几个阶段,在每个阶段实现一个小目标:首先是 int32 到 uintptr 类型,然后是 uintptr 到 `unsafe.Pointr` 指针类型,最后是 `unsafe.Pointr` 指针类型到 `*C.char` 类型。
## 2.3.7 切片间的转换
在 C 语言中数组也一种指针,因此两个不同类型数组之间的转换和指针间转换基本类似。但是在 Go 语言中,数组或数组对应的切片都不再是指针类型,因此我们也就无法直接实现不同类型的切片之间的转换。
不过 Go 语言的 reflect 包提供了切片类型的底层结构,再结合前面讨论到不同类型之间的指针转换技术就可以实现 `[]X` 和 `[]Y` 类型的切片转换:
```go
var p []X
var q []Y
pHdr := (*reflect.SliceHeader)(unsafe.Pointer(&p))
qHdr := (*reflect.SliceHeader)(unsafe.Pointer(&q))
pHdr.Data = qHdr.Data
pHdr.Len = qHdr.Len * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
pHdr.Cap = qHdr.Cap * unsafe.Sizeof(q[0]) / unsafe.Sizeof(p[0])
```
不同切片类型之间转换的思路是先构造一个空的目标切片,然后用原有的切片底层数据填充目标切片。如果 X 和 Y 类型的大小不同,需要重新设置 Len 和 Cap 属性。需要注意的是,如果 X 或 Y 是空类型,上述代码中可能导致除 0 错误,实际代码需要根据情况酌情处理。
下面演示了切片间的转换的具体流程:

*图 2-3 X 类型切片转 Y 类型切片*
针对 CGO 中常用的功能,作者封装了 "github.com/chai2010/cgo" 包,提供基本的转换功能,具体的细节可以参考实现代码。
================================================
FILE: ch2-cgo/ch2-04-func.md
================================================
# 2.4 函数调用
函数是 C 语言编程的核心,通过 CGO 技术我们不仅仅可以在 Go 语言中调用 C 语言函数,也可以将 Go 语言函数导出为 C 语言函数。
## 2.4.1 Go 调用 C 函数
对于一个启用 CGO 特性的程序,CGO 会构造一个虚拟的 C 包。通过这个虚拟的 C 包可以调用 C 语言函数。
```go
/*
static int add(int a, int b) {
return a+b;
}
*/
import "C"
func main() {
C.add(1, 1)
}
```
以上的 CGO 代码首先定义了一个当前文件内可见的 add 函数,然后通过 `C.add`。
## 2.4.2 C 函数的返回值
对于有返回值的 C 函数,我们可以正常获取返回值。
```go
/*
static int div(int a, int b) {
return a/b;
}
*/
import "C"
import "fmt"
func main() {
v := C.div(6, 3)
fmt.Println(v)
}
```
上面的 div 函数实现了一个整数除法的运算,然后通过返回值返回除法的结果。
不过对于除数为 0 的情形并没有做特殊处理。如果希望在除数为 0 的时候返回一个错误,其他时候返回正常的结果。因为 C 语言不支持返回多个结果,因此 `<errno.h>` 标准库提供了一个 `errno` 宏用于返回错误状态。我们可以近似地将 `errno` 看成一个线程安全的全局变量,可以用于记录最近一次错误的状态码。
改进后的 div 函数实现如下:
```c
#include <errno.h>
int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
```
CGO 也针对 `<errno.h>` 标准库的 `errno` 宏做的特殊支持:在 CGO 调用 C 函数时如果有两个返回值,那么第二个返回值将对应 `errno` 错误状态。
```go
/*
#include <errno.h>
static int div(int a, int b) {
if(b == 0) {
errno = EINVAL;
return 0;
}
return a/b;
}
*/
import "C"
import "fmt"
func main() {
v0, err0 := C.div(2, 1)
fmt.Println(v0, err0)
v1, err1 := C.div(1, 0)
fmt.Println(v1, err1)
}
```
运行这个代码将会产生以下输出:
```
2 <nil>
0 invalid argument
```
我们可以近似地将 div 函数看作为以下类型的函数:
```go
func C.div(a, b C.int) (C.int, [error])
```
第二个返回值是可忽略的 error 接口类型,底层对应 `syscall.Errno` 错误类型。
## 2.4.3 void 函数的返回值
C 语言函数还有一种没有返回值类型的函数,用 void 表示返回值类型。一般情况下,我们无法获取 void 类型函数的返回值,因为没有返回值可以获取。前面的例子中提到,cgo 对 errno 做了特殊处理,可以通过第二个返回值来获取 C 语言的错误状态。对于 void 类型函数,这个特性依然有效。
以下的代码是获取没有返回值函数的错误状态码:
```go
//static void noreturn() {}
import "C"
import "fmt"
func main() {
_, err := C.noreturn()
fmt.Println(err)
}
```
此时,我们忽略了第一个返回值,只获取第二个返回值对应的错误码。
我们也可以尝试获取第一个返回值,它对应的是 C 语言的 void 对应的 Go 语言类型:
```go
//static void noreturn() {}
import "C"
import "fmt"
func main() {
v, _ := C.noreturn()
fmt.Printf("%#v", v)
}
```
运行这个代码将会产生以下输出:
```
main._Ctype_void{}
```
我们可以看出 C 语言的 void 类型对应的是当前的 main 包中的 `_Ctype_void` 类型。其实也将 C 语言的 noreturn 函数看作是返回 `_Ctype_void` 类型的函数,这样就可以直接获取 void 类型函数的返回值:
```go
//static void noreturn() {}
import "C"
import "fmt"
func main() {
fmt.Println(C.noreturn())
}
```
运行这个代码将会产生以下输出:
```
[]
```
其实在 CGO 生成的代码中,`_Ctype_void` 类型对应一个 0 长的数组类型 `[0]byte`,因此 `fmt.Println` 输出的是一个表示空数值的方括弧。
以上有效特性虽然看似有些无聊,但是通过这些例子我们可以精确掌握 CGO 代码的边界,可以从更深层次的设计的角度来思考产生这些奇怪特性的原因。
## 2.4.4 C 调用 Go 导出函数
CGO 还有一个强大的特性:将 Go 函数导出为 C 语言函数。这样的话我们可以定义好 C 语言接口,然后通过 Go 语言实现。在本章的第一节快速入门部分我们已经展示过 Go 语言导出 C 语言函数的例子。
下面是用 Go 语言重新实现本节开始的 add 函数:
```go
import "C"
//export add
func add(a, b C.int) C.int {
return a+b
}
```
add 函数名以小写字母开头,对于 Go 语言来说是包内的私有函数。但是从 C 语言角度来看,导出的 add 函数是一个可全局访问的 C 语言函数。如果在两个不同的 Go 语言包内,都存在一个同名的要导出为 C 语言函数的 add 函数,那么在最终的链接阶段将会出现符号重名的问题。
CGO 生成的 `_cgo_export.h` 文件会包含导出后的 C 语言函数的声明。我们可以在纯 C 源文件中包含 `_cgo_export.h` 文件来引用导出的 add 函数。如果希望在当前的 CGO 文件中马上使用导出的 C 语言 add 函数,则无法引用 `_cgo_export.h` 文件。因为 `_cgo_export.h` 文件的生成需要依赖当前文件可以正常构建,而如果当前文件内部循环依赖还未生成的 `_cgo_export.h` 文件将会导致 cgo 命令错误。
```c
#include "_cgo_export.h"
void foo() {
add(1, 1);
}
```
当导出 C 语言接口时,需要保证函数的参数和返回值类型都是 C 语言友好的类型,同时返回值不得直接或间接包含 Go 语言内存空间的指针。
================================================
FILE: ch2-cgo/ch2-05-internal.md
================================================
# 2.5 内部机制
对于刚刚接触 CGO 用户来说,CGO 的很多特性类似魔法。CGO 特性主要是通过一个叫 cgo 的命令行工具来辅助输出 Go 和 C 之间的桥接代码。本节我们尝试从生成的代码分析 Go 语言和 C 语言函数直接相互调用的流程。
## 2.5.1 CGO 生成的中间文件
要了解 CGO 技术的底层秘密首先需要了解 CGO 生成了哪些中间文件。我们可以在构建一个 cgo 包时增加一个 `-work` 输出中间生成文件所在的目录并且在构建完成时保留中间文件。如果是比较简单的 cgo 代码我们也可以直接通过手工调用 `go tool cgo` 命令来查看生成的中间文件。
在一个 Go 源文件中,如果出现了 `import "C"` 指令则表示将调用 cgo 命令生成对应的中间文件。下图是 cgo 生成的中间文件的简单示意图:

*图 2-4 cgo 生成的中间文件*
包中有 4 个 Go 文件,其中 nocgo 开头的文件中没有 `import "C"` 指令,其它的 2 个文件则包含了 cgo 代码。cgo 命令会为每个包含了 cgo 代码的 Go 文件创建 2 个中间文件,比如 main.go 会分别创建 main.cgo1.go 和 main.cgo2.c 两个中间文件。然后会为整个包创建一个 `_cgo_gotypes.go` Go 文件,其中包含 Go 语言部分辅助代码。此外还会创建一个 `_cgo_export.h` 和 `_cgo_export.c` 文件,对应 Go 语言导出到 C 语言的类型和函数。
## 2.5.2 Go 调用 C 函数
Go 调用 C 函数是 CGO 最常见的应用场景,我们将从最简单的例子入手分析 Go 调用 C 函数的详细流程。
具体代码如下(main.go):
```go
package main
//int sum(int a, int b) { return a+b; }
import "C"
func main() {
println(C.sum(1, 1))
}
```
首先构建并运行该例子没有错误。然后通过 cgo 命令行工具在_obj 目录生成中间文件:
```
$ go tool cgo main.go
```
查看_obj 目录生成中间文件:
```
$ ls _obj | awk '{print $NF}'
_cgo_.o
_cgo_export.c
_cgo_export.h
_cgo_flags
_cgo_gotypes.go
_cgo_main.c
main.cgo1.go
main.cgo2.c
```
其中 `_cgo_.o`、`_cgo_flags` 和 `_cgo_main.c` 文件和我们的代码没有直接的逻辑关联,可以暂时忽略。
我们先查看 `main.cgo1.go` 文件,它是 main.go 文件展开虚拟 C 包相关函数和变量后的 Go 代码:
```go
package main
//int sum(int a, int b) { return a+b; }
import _ "unsafe"
func main() {
println((_Cfunc_sum)(1, 1))
}
```
其中 `C.sum(1, 1)` 函数调用被替换成了 `(_Cfunc_sum)(1, 1)`。每一个 `C.xxx` 形式的函数都会被替换为 `_Cfunc_xxx` 格式的纯 Go 函数,其中前缀 `_Cfunc_` 表示这是一个 C 函数,对应一个私有的 Go 桥接函数。
`_Cfunc_sum` 函数在 cgo 生成的 `_cgo_gotypes.go` 文件中定义:
```go
//go:cgo_unsafe_args
func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
_cgo_runtime_cgocall(_cgo_506f45f9fa85_Cfunc_sum, uintptr(unsafe.Pointer(&p0)))
if _Cgo_always_false {
_Cgo_use(p0)
_Cgo_use(p1)
}
return
}
```
`_Cfunc_sum` 函数的参数和返回值 `_Ctype_int` 类型对应 `C.int` 类型,命名的规则和 `_Cfunc_xxx` 类似,不同的前缀用于区分函数和类型。
其中 `_cgo_runtime_cgocall` 对应 `runtime.cgocall` 函数,函数的声明如下:
```go
func runtime.cgocall(fn, arg unsafe.Pointer) int32
```
第一个参数是 C 语言函数的地址,第二个参数是存放 C 语言函数对应的参数结构体的地址。
在这个例子中,被传入 C 语言函数 `_cgo_506f45f9fa85_Cfunc_sum` 也是 cgo 生成的中间函数。函数在 `main.cgo2.c` 定义:
```c
void _cgo_506f45f9fa85_Cfunc_sum(void *v) {
struct {
int p0;
int p1;
int r;
char __pad12[4];
} __attribute__((__packed__)) *a = v;
char *stktop = _cgo_topofstack();
__typeof__(a->r) r;
_cgo_tsan_acquire();
r = sum(a->p0, a->p1);
_cgo_tsan_release();
a = (void*)((char*)a + (_cgo_topofstack() - stktop));
a->r = r;
}
```
这个函数参数只有一个 void 泛型的指针,函数没有返回值。真实的 sum 函数的函数参数和返回值均通过唯一的参数指针类实现。
`_cgo_506f45f9fa85_Cfunc_sum` 函数的指针指向的结构为:
```c
struct {
int p0;
int p1;
int r;
char __pad12[4];
} __attribute__((__packed__)) *a = v;
```
其中 p0 成员对应 sum 的第一个参数,p1 成员对应 sum 的第二个参数,r 成员,`__pad12` 用于填充结构体保证对齐 CPU 机器字的整倍数。
然后从参数指向的结构体获取调用参数后开始调用真实的 C 语言版 sum 函数,并且将返回值保持到结构体内返回值对应的成员。
因为 Go 语言和 C 语言有着不同的内存模型和函数调用规范。其中 `_cgo_topofstack` 函数相关的代码用于 C 函数调用后恢复调用栈。`_cgo_tsan_acquire` 和 `_cgo_tsan_release` 则是用于扫描 CGO 相关的函数则是对 CGO 相关函数的指针做相关检查。
`C.sum` 的整个调用流程图如下:

*图 2-5 调用 C 函数*
其中 `runtime.cgocall` 函数是实现 Go 语言到 C 语言函数跨界调用的关键。更详细的细节可以参考 https://golang.org/src/cmd/cgo/doc.go 内部的代码注释和 `runtime.cgocall` 函数的实现。
## 2.5.3 C 调用 Go 函数
在简单分析了 Go 调用 C 函数的流程后,我们现在来分析 C 反向调用 Go 函数的流程。同样,我们现构造一个 Go 语言版本的 sum 函数,文件名同样为 `main.go`:
```
package main
//int sum(int a, int b);
import "C"
//export sum
func sum(a, b C.int) C.int {
return a + b
}
func main() {}
```
CGO 的语法细节不再赘述。为了在 C 语言中使用 sum 函数,我们需要将 Go 代码编译为一个 C 静态库:
```
$ go build -buildmode=c-archive -o sum.a main.go
```
如果没有错误的话,以上编译命令将生成一个 `sum.a` 静态库和 `sum.h` 头文件。其中 `sum.h` 头文件将包含 sum 函数的声明,静态库中将包含 sum 函数的实现。
要分析生成的 C 语言版 sum 函数的调用流程,同样需要分析 cgo 生成的中间文件:
```
$ go tool cgo main.go
```
_obj 目录还是生成类似的中间文件。为了查看方便,我们刻意忽略了无关的几个文件:
```
$ ls _obj | awk '{print $NF}'
_cgo_export.c
_cgo_export.h
_cgo_gotypes.go
main.cgo1.go
main.cgo2.c
```
其中 `_cgo_export.h` 文件的内容和生成 C 静态库时产生的 `sum.h` 头文件是同一个文件,里面同样包含 sum 函数的声明。
既然 C 语言是主调用者,我们需要先从 C 语言版 sum 函数的实现开始分析。C 语言版本的 sum 函数在生成的 `_cgo_export.c` 文件中(该文件包含的是 Go 语言导出函数对应的 C 语言函数实现):
```c
int sum(int p0, int p1)
{
__SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done();
struct {
int p0;
int p1;
int r0;
char __pad0[4];
} __attribute__((__packed__)) a;
a.p0 = p0;
a.p1 = p1;
_cgo_tsan_release();
crosscall2(_cgoexp_8313eaf44386_sum, &a, 16, _cgo_ctxt);
_cgo_tsan_acquire();
_cgo_release_context(_cgo_ctxt);
return a.r0;
}
```
sum 函数的内容采用和前面类似的技术,将 sum 函数的参数和返回值打包到一个结构体中,然后通过 `runtime/cgo.crosscall2` 函数将结构体传给 `_cgoexp_8313eaf44386_sum` 函数执行。
`runtime/cgo.crosscall2` 函数采用汇编语言实现,它对应的函数声明如下:
```go
func runtime/cgo.crosscall2(
fn func(a unsafe.Pointer, n int32, ctxt uintptr),
a unsafe.Pointer, n int32,
ctxt uintptr,
)
```
其中关键的是 fn 和 a,fn 是中间代理函数的指针,a 是对应调用参数和返回值的结构体指针。
中间的 `_cgoexp_8313eaf44386_sum` 代理函数在 `_cgo_gotypes.go` 文件:
```go
func _cgoexp_8313eaf44386_sum(a unsafe.Pointer, n int32, ctxt uintptr) {
fn := _cgoexpwrap_8313eaf44386_sum
_cgo_runtime_cgocallback(**(**unsafe.Pointer)(unsafe.Pointer(&fn)), a, uintptr(n), ctxt);
}
func _cgoexpwrap_8313eaf44386_sum(p0 _Ctype_int, p1 _Ctype_int) (r0 _Ctype_int) {
return sum(p0, p1)
}
```
内部将 sum 的包装函数 `_cgoexpwrap_8313eaf44386_sum` 作为函数指针,然后由 `_cgo_runtime_cgocallback` 函数完成 C 语言到 Go 函数的回调工作。
`_cgo_runtime_cgocallback` 函数对应 `runtime.cgocallback` 函数,函数的类型如下:
```go
func runtime.cgocallback(fn, frame unsafe.Pointer, framesize, ctxt uintptr)
```
参数分别是函数指针,函数参数和返回值对应结构体的指针,函数调用帧大小和上下文参数。
整个调用流程图如下:

*图 2-6 调用导出的 Go 函数*
其中 `runtime.cgocallback` 函数是实现 C 语言到 Go 语言函数跨界调用的关键。更详细的细节可以参考相关函数的实现。
================================================
FILE: ch2-cgo/ch2-06-qsort.md
================================================
# 2.6 实战: 封装 qsort
qsort 快速排序函数是 C 语言的高阶函数,支持用于自定义排序比较函数,可以对任意类型的数组进行排序。本节我们尝试基于 C 语言的 qsort 函数封装一个 Go 语言版本的 qsort 函数。
## 2.6.1 认识 qsort 函数
qsort 快速排序函数由 `<stdlib.h>` 标准库提供,函数的声明如下:
```c
void qsort(
void* base, size_t num, size_t size,
int (*cmp)(const void*, const void*)
);
```
其中 base 参数是要排序数组的首个元素的地址,num 是数组中元素的个数,size 是数组中每个元素的大小。最关键是 cmp 比较函数,用于对数组中任意两个元素进行排序。cmp 排序函数的两个指针参数分别是要比较的两个元素的地址,如果第一个参数对应元素大于第二个参数对应的元素将返回结果大于 0,如果两个元素相等则返回 0,如果第一个元素小于第二个元素则返回结果小于 0。
下面的例子是用 C 语言的 qsort 对一个 int 类型的数组进行排序:
```c
#include <stdio.h>
#include <stdlib.h>
#define DIM(x) (sizeof(x)/sizeof((x)[0]))
static int cmp(const void* a, const void* b) {
const int* pa = (int*)a;
const int* pb = (int*)b;
return *pa - *pb;
}
int main() {
int values[] = { 42, 8, 109, 97, 23, 25};
int i;
qsort(values, DIM(values), sizeof(values[0]), cmp);
for(i = 0; i < DIM(values); i++) {
printf ("%d",values[i]);
}
return 0;
}
```
其中 `DIM(values)` 宏用于计算数组元素的个数,`sizeof(values[0])` 用于计算数组元素的大小。
cmp 是用于排序时比较两个元素大小的回调函数。为了避免对全局名字空间的污染,我们将 cmp 回调函数定义为仅当前文件内可访问的静态函数。
## 2.6.2 将 qsort 函数从 Go 包导出
为了方便 Go 语言的非 CGO 用户使用 qsort 函数,我们需要将 C 语言的 qsort 函数包装为一个外部可以访问的 Go 函数。
用 Go 语言将 qsort 函数重新包装为 `qsort.Sort` 函数:
```go
package qsort
//typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
import "C"
import "unsafe"
func Sort(
base unsafe.Pointer, num, size C.size_t,
cmp C.qsort_cmp_func_t,
) {
C.qsort(base, num, size, cmp)
}
```
因为 Go 语言的 CGO 语言不好直接表达 C 语言的函数类型,因此在 C 语言空间将比较函数类型重新定义为一个 `qsort_cmp_func_t` 类型。
虽然 Sort 函数已经导出了,但是对于 qsort 包之外的用户依然不能直接使用该函数——Sort 函数的参数还包含了虚拟的 C 包提供的类型。
在 CGO 的内部机制一节中我们已经提过,虚拟的 C 包下的任何名称其实都会被映射为包内的私有名字。比如 `C.size_t` 会被展开为 `_Ctype_size_t`,`C.qsort_cmp_func_t` 类型会被展开为 `_Ctype_qsort_cmp_func_t`。
被 CGO 处理后的 Sort 函数的类型如下:
```go
func Sort(
base unsafe.Pointer, num, size _Ctype_size_t,
cmp _Ctype_qsort_cmp_func_t,
)
```
这样将会导致包外部用于无法构造 `_Ctype_size_t` 和 `_Ctype_qsort_cmp_func_t` 类型的参数而无法使用 Sort 函数。因此,导出的 Sort 函数的参数和返回值要避免对虚拟 C 包的依赖。
重新调整 Sort 函数的参数类型和实现如下:
```go
/*
#include <stdlib.h>
typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
*/
import "C"
import "unsafe"
type CompareFunc C.qsort_cmp_func_t
func Sort(base unsafe.Pointer, num, size int, cmp CompareFunc) {
C.qsort(base, C.size_t(num), C.size_t(size), C.qsort_cmp_func_t(cmp))
}
```
我们将虚拟 C 包中的类型通过 Go 语言类型代替,在内部调用 C 函数时重新转型为 C 函数需要的类型。因此外部用户将不再依赖 qsort 包内的虚拟 C 包。
以下代码展示的 Sort 函数的使用方式:
```go
package main
//extern int go_qsort_compare(void* a, void* b);
import "C"
import (
"fmt"
"unsafe"
qsort "."
)
//export go_qsort_compare
func go_qsort_compare(a, b unsafe.Pointer) C.int {
pa, pb := (*C.int)(a), (*C.int)(b)
return C.int(*pa - *pb)
}
func main() {
values := []int32{42, 9, 101, 95, 27, 25}
qsort.Sort(unsafe.Pointer(&values[0]),
len(values), int(unsafe.Sizeof(values[0])),
qsort.CompareFunc(C.go_qsort_compare),
)
fmt.Println(values)
}
```
为了使用 Sort 函数,我们需要将 Go 语言的切片取首地址、元素个数、元素大小等信息作为调用参数,同时还需要提供一个 C 语言规格的比较函数。
其中 go_qsort_compare 是用 Go 语言实现的,并导出到 C 语言空间的函数,用于 qsort 排序时的比较函数。
目前已经实现了对 C 语言的 qsort 初步包装,并且可以通过包的方式被其它用户使用。但是 `qsort.Sort` 函数已经有很多不便使用之处:用户要提供 C 语言的比较函数,这对许多 Go 语言用户是一个挑战。下一步我们将继续改进 qsort 函数的包装函数,尝试通过闭包函数代替 C 语言的比较函数。
消除用户对 CGO 代码的直接依赖。
## 2.6.3 改进:闭包函数作为比较函数
在改进之前我们先回顾下 Go 语言 sort 包自带的排序函数的接口:
```go
func Slice(slice interface{}, less func(i, j int) bool)
```
标准库的 sort.Slice 因为支持通过闭包函数指定比较函数,对切片的排序非常简单:
```go
import "sort"
func main() {
values := []int32{42, 9, 101, 95, 27, 25}
sort.Slice(values, func(i, j int) bool {
return values[i] < values[j]
})
fmt.Println(values)
}
```
我们也尝试将 C 语言的 qsort 函数包装为以下格式的 Go 语言函数:
```go
package qsort
func Sort(base unsafe.Pointer, num, size int, cmp func(a, b unsafe.Pointer) int)
```
闭包函数无法导出为 C 语言函数,因此无法直接将闭包函数传入 C 语言的 qsort 函数。
为此我们可以用 Go 构造一个可以导出为 C 语言的代理函数,然后通过一个全局变量临时保存当前的闭包比较函数。
代码如下:
```go
var go_qsort_compare_info struct {
fn func(a, b unsafe.Pointer) int
sync.Mutex
}
//export _cgo_qsort_compare
func _cgo_qsort_compare(a, b unsafe.Pointer) C.int {
return C.int(go_qsort_compare_info.fn(a, b))
}
```
其中导出的 C 语言函数 `_cgo_qsort_compare` 是公用的 qsort 比较函数,内部通过 `go_qsort_compare_info.fn` 来调用当前的闭包比较函数。
新的 Sort 包装函数实现如下:
```go
/*
#include <stdlib.h>
typedef int (*qsort_cmp_func_t)(const void* a, const void* b);
extern int _cgo_qsort_compare(void* a, void* b);
*/
import "C"
func Sort(base unsafe.Pointer, num, size int, cmp func(a, b unsafe.Pointer) int) {
go_qsort_compare_info.Lock()
defer go_qsort_compare_info.Unlock()
go_qsort_compare_info.fn = cmp
C.qsort(base, C.size_t(num), C.size_t(size),
C.qsort_cmp_func_t(C._cgo_qsort_compare),
)
}
```
每次排序前,对全局的 go_qsort_compare_info 变量加锁,同时将当前的闭包函数保存到全局变量,然后调用 C 语言的 qsort 函数。
基于新包装的函数,我们可以简化之前的排序代码:
```go
func main() {
values := []int32{42, 9, 101, 95, 27, 25}
qsort.Sort(unsafe.Pointer(&values[0]), len(values), int(unsafe.Sizeof(values[0])),
func(a, b unsafe.Pointer) int {
pa, pb := (*int32)(a), (*int32)(b)
return int(*pa - *pb)
},
)
fmt.Println(values)
}
```
现在排序不再需要通过 CGO 实现 C 语言版本的比较函数了,可以传入 Go 语言闭包函数作为比较函数。
但是导入的排序函数依然依赖 unsafe 包,这是违背 Go 语言编程习惯的。
## 2.6.4 改进:消除用户对 unsafe 包的依赖
前一个版本的 qsort.Sort 包装函数已经比最初的 C 语言版本的 qsort 易用很多,但是依然保留了很多 C 语言底层数据结构的细节。
现在我们将继续改进包装函数,尝试消除对 unsafe 包的依赖,并实现一个类似标准库中 sort.Slice 的排序函数。
新的包装函数声明如下:
```go
package qsort
func Slice(slice interface{}, less func(a, b int) bool)
```
首先,我们将 slice 作为接口类型参数传入,这样可以适配不同的切片类型。
然后切片的首个元素的地址、元素个数和元素大小可以通过 reflect 反射包从切片中获取。
为了保存必要的排序上下文信息,我们需要在全局包变量增加要排序数组的地址、元素个数和元素大小等信息,比较函数改为 less:
```go
var go_qsort_compare_info struct {
base unsafe.Pointer
elemnum int
elemsize int
less func(a, b int) bool
sync.Mutex
}
```
同样比较函数需要根据元素指针、排序数组的开始地址和元素的大小计算出元素对应数组的索引下标,
然后根据 less 函数的比较结果返回 qsort 函数需要格式的比较结果。
```go
//export _cgo_qsort_compare
func _cgo_qsort_compare(a, b unsafe.Pointer) C.int {
var (
// array memory is locked
base = uintptr(go_qsort_compare_info.base)
elemsize = uintptr(go_qsort_compare_info.elemsize)
)
i := int((uintptr(a) - base) / elemsize)
j := int((uintptr(b) - base) / elemsize)
switch {
case go_qsort_compare_info.less(i, j): // v[i] < v[j]
return -1
case go_qsort_compare_info.less(j, i): // v[i] > v[j]
return +1
default:
return 0
}
}
```
新的 Slice 函数的实现如下:
```go
func Slice(slice interface{}, less func(a, b int) bool) {
sv := reflect.ValueOf(slice)
if sv.Kind() != reflect.Slice {
panic(fmt.Sprintf("qsort called with non-slice value of type %T", slice))
}
if sv.Len() == 0 {
return
}
go_qsort_compare_info.Lock()
defer go_qsort_compare_info.Unlock()
defer func() {
go_qsort_compare_info.base = nil
go_qsort_compare_info.elemnum = 0
go_qsort_compare_info.elemsize = 0
go_qsort_compare_info.less = nil
}()
// baseMem = unsafe.Pointer(sv.Index(0).Addr().Pointer())
// baseMem maybe moved, so must saved after call C.fn
go_qsort_compare_info.base = unsafe.Pointer(sv.Index(0).Addr().Pointer())
go_qsort_compare_info.elemnum = sv.Len()
go_qsort_compare_info.elemsize = int(sv.Type().Elem().Size())
go_qsort_compare_info.less = less
C.qsort(
go_qsort_compare_info.base,
C.size_t(go_qsort_compare_info.elemnum),
C.size_t(go_qsort_compare_info.elemsize),
C.qsort_cmp_func_t(C._cgo_qsort_compare),
)
}
```
首先需要判断传入的接口类型必须是切片类型。然后通过反射获取 qsort 函数需要的切片信息,并调用 C 语言的 qsort 函数。
基于新包装的函数我们可以采用和标准库相似的方式排序切片:
```go
import (
"fmt"
qsort "."
)
func main() {
values := []int64{42, 9, 101, 95, 27, 25}
qsort.Slice(values, func(i, j int) bool {
return values[i] < values[j]
})
fmt.Println(values)
}
```
为了避免在排序过程中,排序数组的上下文信息 `go_qsort_compare_info` 被修改,我们进行了全局加锁。
因此目前版本的 qsort.Slice 函数是无法并发执行的,读者可以自己尝试改进这个限制。
================================================
FILE: ch2-cgo/ch2-07-memory.md
================================================
# 2.7 CGO 内存模型
CGO 是架接 Go 语言和 C 语言的桥梁,它使二者在二进制接口层面实现了互通,但是我们要注意因两种语言的内存模型的差异而可能引起的问题。如果在 CGO 处理的跨语言函数调用时涉及到了指针的传递,则可能会出现 Go 语言和 C 语言共享某一段内存的场景。我们知道 C 语言的内存在分配之后就是稳定的,但是 Go 语言因为函数栈的动态伸缩可能导致栈中内存地址的移动 (这是 Go 和 C 内存模型的最大差异)。如果 C 语言持有的是移动之前的 Go 指针,那么以旧指针访问 Go 对象时会导致程序崩溃。
## 2.7.1 Go 访问 C 内存
C 语言空间的内存是稳定的,只要不是被人为提前释放,那么在 Go 语言空间可以放心大胆地使用。在 Go 语言访问 C 语言内存是最简单的情形,我们在之前的例子中已经见过多次。
因为 Go 语言实现的限制,我们无法在 Go 语言中创建大于 2GB 内存的切片(具体请参考 makeslice 实现代码)。不过借助 cgo 技术,我们可以在 C 语言环境创建大于 2GB 的内存,然后转为 Go 语言的切片使用:
```go
package main
/*
#include <stdlib.h>
void* makeslice(size_t memsize) {
return malloc(memsize);
}
*/
import "C"
import "unsafe"
func makeByteSlice(n int) []byte {
p := C.makeslice(C.size_t(n))
return ((*[1 << 31]byte)(p))[0:n:n]
}
func freeByteSlice(p []byte) {
C.free(unsafe.Pointer(&p[0]))
}
func main() {
s := makeByteSlice(1<<32+1)
s[len(s)-1] = 255
print(s[len(s)-1])
freeByteSlice(s)
}
```
例子中我们通过 makeByteSlice 来创建大于 4G 内存大小的切片,从而绕过了 Go 语言实现的限制(需要代码验证)。而 freeByteSlice 辅助函数则用于释放从 C 语言函数创建的切片。
因为 C 语言内存空间是稳定的,基于 C 语言内存构造的切片也是绝对稳定的,不会因为 Go 语言栈的变化而被移动。
## 2.7.2 C 临时访问传入的 Go 内存
cgo 之所以存在的一大因素是为了方便在 Go 语言中接纳吸收过去几十年来使用 C/C++ 语言软件构建的大量的软件资源。C/C++ 很多库都是需要通过指针直接处理传入的内存数据的,因此 cgo 中也有很多需要将 Go 内存传入 C 语言函数的应用场景。
假设一个极端场景:我们将一块位于某 goroutine 的栈上的 Go 语言内存传入了 C 语言函数后,在此 C 语言函数执行期间,此 goroutinue 的栈因为空间不足的原因发生了扩展,也就是导致了原来的 Go 语言内存被移动到了新的位置。但是此时此刻 C 语言函数并不知道该 Go 语言内存已经移动了位置,仍然用之前的地址来操作该内存——这将将导致内存越界。以上是一个推论(真实情况有些差异),也就是说 C 访问传入的 Go 内存可能是不安全的!
当然有 RPC 远程过程调用的经验的用户可能会考虑通过完全传值的方式处理:借助 C 语言内存稳定的特性,在 C 语言空间先开辟同样大小的内存,然后将 Go 的内存填充到 C 的内存空间;返回的内存也是如此处理。下面的例子是这种思路的具体实现:
```go
package main
/*
#include <stdlib.h>
#include <stdio.h>
void printString(const char* s) {
printf("%s", s);
}
*/
import "C"
import "unsafe"
func printString(s string) {
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
C.printString(cs)
}
func main() {
s := "hello"
printString(s)
}
```
在需要将 Go 的字符串传入 C 语言时,先通过 `C.CString` 将 Go 语言字符串对应的内存数据复制到新创建的 C 语言内存空间上。上面例子的处理思路虽然是安全的,但是效率极其低下(因为要多次分配内存并逐个复制元素),同时也极其繁琐。
为了简化并高效处理此种向 C 语言传入 Go 语言内存的问题,cgo 针对该场景定义了专门的规则:在 CGO 调用的 C 语言函数返回前,cgo 保证传入的 Go 语言内存在此期间不会发生移动,C 语言函数可以大胆地使用 Go 语言的内存!
根据新的规则我们可以直接传入 Go 字符串的内存:
```go
package main
/*
#include<stdio.h>
void printString(const char* s, int n) {
int i;
for(i = 0; i < n; i++) {
putchar(s[i]);
}
putchar('\n');
}
*/
import "C"
func printString(s string) {
p := (*reflect.StringHeader)(unsafe.Pointer(&s))
C.printString((*C.char)(unsafe.Pointer(p.Data)), C.int(len(s)))
}
func main() {
s := "hello"
printString(s)
}
```
现在的处理方式更加直接,且避免了分配额外的内存。完美的解决方案!
任何完美的技术都有被滥用的时候,CGO 的这种看似完美的规则也是存在隐患的。我们假设调用的 C 语言函数需要长时间运行,那么将会导致被他引用的 Go 语言内存在 C 语言返回前不能被移动,从而可能间接地导致这个 Go 内存栈对应的 goroutine 不能动态伸缩栈内存,也就是可能导致这个 goroutine 被阻塞。因此,在需要长时间运行的 C 语言函数(特别是在纯 CPU 运算之外,还可能因为需要等待其它的资源而需要不确定时间才能完成的函数),需要谨慎处理传入的 Go 语言内存。
不过需要小心的是在取得 Go 内存后需要马上传入 C 语言函数,不能保存到临时变量后再间接传入 C 语言函数。因为 CGO 只能保证在 C 函数调用之后被传入的 Go 语言内存不会发生移动,它并不能保证在传入 C 函数之前内存不发生变化。
以下代码是错误的:
```go
// 错误的代码
tmp := uintptr(unsafe.Pointer(&x))
pb := (*int16)(unsafe.Pointer(tmp))
*pb = 42
```
因为 tmp 并不是指针类型,在它获取到 Go 对象地址之后 x 对象可能会被移动,但是因为不是指针类型,所以不会被 Go 语言运行时更新成新内存的地址。在非指针类型的 tmp 保持 Go 对象的地址,和在 C 语言环境保持 Go 对象的地址的效果是一样的:如果原始的 Go 对象内存发生了移动,Go 语言运行时并不会同步更新它们。
## 2.7.3 C 长期持有 Go 指针对象
作为一个 Go 程序员在使用 CGO 时潜意识会认为总是 Go 调用 C 函数。其实 CGO 中,C 语言函数也可以回调 Go 语言实现的函数。特别是我们可以用 Go 语言写一个动态库,导出 C 语言规范的接口给其它用户调用。当 C 语言函数调用 Go 语言函数的时候,C 语言函数就成了程序的调用方,Go 语言函数返回的 Go 对象内存的生命周期也就自然超出了 Go 语言运行时的管理。简言之,我们不能在 C 语言函数中直接使用 Go 语言对象的内存。
虽然 Go 语言禁止在 C 语言函数中长期持有 Go 指针对象,但是这种需求是切实存在的。如果需要在 C 语言中访问 Go 语言内存对象,我们可以将 Go 语言内存对象在 Go 语言空间映射为一个 int 类型的 id,然后通过此 id 来间接访问和控制 Go 语言对象。
以下代码用于将 Go 对象映射为整数类型的 ObjectId,用完之后需要手工调用 free 方法释放该对象 ID:
```go
package main
import "sync"
type ObjectId int32
var refs struct {
sync.Mutex
objs map[ObjectId]interface{}
next ObjectId
}
func init() {
refs.Lock()
defer refs.Unlock()
refs.objs = make(map[ObjectId]interface{})
refs.next = 1000
}
func NewObjectId(obj interface{}) ObjectId {
refs.Lock()
defer refs.Unlock()
id := refs.next
refs.next++
refs.objs[id] = obj
return id
}
func (id ObjectId) IsNil() bool {
return id == 0
}
func (id ObjectId) Get() interface{} {
refs.Lock()
defer refs.Unlock()
return refs.objs[id]
}
func (id *ObjectId) Free() interface{} {
refs.Lock()
defer refs.Unlock()
obj := refs.objs[*id]
delete(refs.objs, *id)
*id = 0
return obj
}
```
我们通过一个 map 来管理 Go 语言对象和 id 对象的映射关系。其中 NewObjectId 用于创建一个和对象绑定的 id,而 id 对象的方法可用于解码出原始的 Go 对象,也可以用于结束 id 和原始 Go 对象的绑定。
下面一组函数以 C 接口规范导出,可以被 C 语言函数调用:
```go
package main
/*
extern char* NewGoString(char*);
extern void FreeGoString(char*);
extern void PrintGoString(char*);
static void printString(const char* s) {
char* gs = NewGoString(s);
PrintGoString(gs);
FreeGoString(gs);
}
*/
import "C"
//export NewGoString
func NewGoString(s *C.char) *C.char {
gs := C.GoString(s)
id := NewObjectId(gs)
return (*C.char)(unsafe.Pointer(uintptr(id)))
}
//export FreeGoString
func FreeGoString(p *C.char) {
id := ObjectId(uintptr(unsafe.Pointer(p)))
id.Free()
}
//export PrintGoString
func PrintGoString(s *C.char) {
id := ObjectId(uintptr(unsafe.Pointer(p)))
gs := id.Get().(string)
print(gs)
}
func main() {
C.printString("hello")
}
```
在 printString 函数中,我们通过 NewGoString 创建一个对应的 Go 字符串对象,返回的其实是一个 id,不能直接使用。我们借助 PrintGoString 函数将 id 解析为 Go 语言字符串后打印。该字符串在 C 语言函数中完全跨越了 Go 语言的内存管理,在 PrintGoString 调用前即使发生了栈伸缩导致的 Go 字符串地址发生变化也依然可以正常工作,因为该字符串对应的 id 是稳定的,在 Go 语言空间通过 id 解码得到的字符串也就是有效的。
## 2.7.4 导出 C 函数不能返回 Go 内存
在 Go 语言中,Go 是从一个固定的虚拟地址空间分配内存。而 C 语言分配的内存则不能使用 Go 语言保留的虚拟内存空间。在 CGO 环境,Go 语言运行时默认会检查导出返回的内存是否是由 Go 语言分配的,如果是则会抛出运行时异常。
下面是 CGO 运行时异常的例子:
```go
/*
extern int* getGoPtr();
static void Main() {
int* p = getGoPtr();
*p = 42;
}
*/
import "C"
func main() {
C.Main()
}
//export getGoPtr
func getGoPtr() *C.int {
return new(C.int)
}
```
其中 getGoPtr 返回的虽然是 C 语言类型的指针,但是内存本身是从 Go 语言的 new 函数分配,也就是由 Go 语言运行时统一管理的内存。然后我们在 C 语言的 Main 函数中调用了 getGoPtr 函数,此时默认将发送运行时异常:
```
$ go run main.go
panic: runtime error: cgo result has Go pointer
goroutine 1 [running]:
main._cgoexpwrap_cfb3840e3af2_getGoPtr.func1(0xc420051dc0)
command-line-arguments/_obj/_cgo_gotypes.go:60 +0x3a
main._cgoexpwrap_cfb3840e3af2_getGoPtr(0xc420016078)
command-line-arguments/_obj/_cgo_gotypes.go:62 +0x67
main._Cfunc_Main()
command-line-arguments/_obj/_cgo_gotypes.go:43 +0x41
main.main()
/Users/chai/go/src/github.com/chai2010 \
/advanced-go-programming-book/examples/ch2-xx \
/return-go-ptr/main.go:17 +0x20
exit status 2
```
异常说明 cgo 函数返回的结果中含有 Go 语言分配的指针。指针的检查操作发生在 C 语言版的 getGoPtr 函数中,它是由 cgo 生成的桥接 C 语言和 Go 语言的函数。
下面是 cgo 生成的 C 语言版本 getGoPtr 函数的具体细节(在 cgo 生成的 `_cgo_export.c` 文件定义):
```c
int* getGoPtr()
{
__SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done();
struct {
int* r0;
} __attribute__((__packed__)) a;
_cgo_tsan_release();
crosscall2(_cgoexp_95d42b8e6230_getGoPtr, &a, 8, _cgo_ctxt);
_cgo_tsan_acquire();
_cgo_release_context(_cgo_ctxt);
return a.r0;
}
```
其中 `_cgo_tsan_acquire` 是从 LLVM 项目移植过来的内存指针扫描函数,它会检查 cgo 函数返回的结果是否包含 Go 指针。
需要说明的是,cgo 默认对返回结果的指针的检查是有代价的,特别是 cgo 函数返回的结果是一个复杂的数据结构时将花费更多的时间。如果已经确保了 cgo 函数返回的结果是安全的话,可以通过设置环境变量 `GODEBUG=cgocheck=0` 来关闭指针检查行为。
```
$ GODEBUG=cgocheck=0 go run main.go
```
关闭 cgocheck 功能后再运行上面的代码就不会出现上面的异常的。但是要注意的是,如果 C 语言使用期间对应的内存被 Go 运行时释放了,将会导致更严重的崩溃问题。cgocheck 默认的值是 1,对应一个简化版本的检测,如果需要完整的检测功能可以将 cgocheck 设置为 2。
关于 cgo 运行时指针检测的功能详细说明可以参考 Go 语言的官方文档。
================================================
FILE: ch2-cgo/ch2-08-class.md
================================================
# 2.8 C++ 类包装
CGO 是 C 语言和 Go 语言之间的桥梁,原则上无法直接支持 C++ 的类。CGO 不支持 C++ 语法的根本原因是 C++ 至今为止还没有一个二进制接口规范 (ABI)。一个 C++ 类的构造函数在编译为目标文件时如何生成链接符号名称、方法在不同平台甚至是 C++ 的不同版本之间都是不一样的。但是 C++ 是兼容 C 语言,所以我们可以通过增加一组 C 语言函数接口作为 C++ 类和 CGO 之间的桥梁,这样就可以间接地实现 C++ 和 Go 之间的互联。当然,因为 CGO 只支持 C 语言中值类型的数据类型,所以我们是无法直接使用 C++ 的引用参数等特性的。
## 2.8.1 C++ 类到 Go 语言对象
实现 C++ 类到 Go 语言对象的包装需要经过以下几个步骤:首先是用纯 C 函数接口包装该 C++ 类;其次是通过 CGO 将纯 C 函数接口映射到 Go 函数;最后是做一个 Go 包装对象,将 C++ 类到方法用 Go 对象的方法实现。
### 2.8.1.1 准备一个 C++ 类
为了演示简单,我们基于 `std::string` 做一个最简单的缓存类 MyBuffer。除了构造函数和析构函数之外,只有两个成员函数分别是返回底层的数据指针和缓存的大小。因为是二进制缓存,所以我们可以在里面中放置任意数据。
```c++
// my_buffer.h
#include <string>
struct MyBuffer {
std::string* s_;
MyBuffer(int size) {
this->s_ = new std::string(size, char('\0'));
}
~MyBuffer() {
delete this->s_;
}
int Size() const {
return this->s_->size();
}
char* Data() {
return (char*)this->s_->data();
}
};
```
我们在构造函数中指定缓存的大小并分配空间,在使用完之后通过析构函数释放内部分配的内存空间。下面是简单的使用方式:
```c++
int main() {
auto pBuf = new MyBuffer(1024);
auto data = pBuf->Data();
auto size = pBuf->Size();
delete pBuf;
}
```
为了方便向 C 语言接口过渡,在此处我们故意没有定义 C++ 的拷贝构造函数。我们必须以 new 和 delete 来分配和释放缓存对象,而不能以值风格的方式来使用。
### 2.8.1.2 用纯 C 函数接口封装 C++ 类
如果要将上面的 C++ 类用 C 语言函数接口封装,我们可以从使用方式入手。我们可以将 new 和 delete 映射为 C 语言函数,将对象的方法也映射为 C 语言函数。
在 C 语言中我们期望 MyBuffer 类可以这样使用:
```c
int main() {
MyBuffer* pBuf = NewMyBuffer(1024);
char* data = MyBuffer_Data(pBuf);
auto size = MyBuffer_Size(pBuf);
DeleteMyBuffer(pBuf);
}
```
先从 C 语言接口用户的角度思考需要什么样的接口,然后创建 `my_buffer_capi.h` 头文件接口规范:
```c++
// my_buffer_capi.h
typedef struct MyBuffer_T MyBuffer_T;
MyBuffer_T* NewMyBuffer(int size);
void DeleteMyBuffer(MyBuffer_T* p);
char* MyBuffer_Data(MyBuffer_T* p);
int MyBuffer_Size(MyBuffer_T* p);
```
然后就可以基于 C++ 的 MyBuffer 类定义这些 C 语言包装函数。我们创建对应的 `my_buffer_capi.cc` 文件如下:
```c++
// my_buffer_capi.cc
#include "./my_buffer.h"
extern "C" {
#include "./my_buffer_capi.h"
}
struct MyBuffer_T: MyBuffer {
MyBuffer_T(int size): MyBuffer(size) {}
~MyBuffer_T() {}
};
MyBuffer_T* NewMyBuffer(int size) {
auto p = new MyBuffer_T(size);
return p;
}
void DeleteMyBuffer(MyBuffer_T* p) {
delete p;
}
char* MyBuffer_Data(MyBuffer_T* p) {
return p->Data();
}
int MyBuffer_Size(MyBuffer_T* p) {
return p->Size();
}
```
因为头文件 `my_buffer_capi.h` 是用于 CGO,必须是采用 C 语言规范的名字修饰规则。在 C++ 源文件包含时需要用 `extern "C"` 语句说明。另外 MyBuffer_T 的实现只是从 MyBuffer 继承的类,这样可以简化包装代码的实现。同时和 CGO 通信时必须通过 `MyBuffer_T` 指针,我们无法将具体的实现暴露给 CGO,因为实现中包含了 C++ 特有的语法,CGO 无法识别 C++ 特性。
将 C++ 类包装为纯 C 接口之后,下一步的工作就是将 C 函数转为 Go 函数。
### 2.8.1.3 将纯 C 接口函数转为 Go 函数
将纯 C 函数包装为对应的 Go 函数的过程比较简单。需要注意的是,因为我们的包中包含 C++11 的语法,因此需要通过 `#cgo CXXFLAGS: -std=c++11` 打开 C++11 的选项。
```go
// my_buffer_capi.go
package main
/*
#cgo CXXFLAGS: -std=c++11
#include "my_buffer_capi.h"
*/
import "C"
type cgo_MyBuffer_T C.MyBuffer_T
func cgo_NewMyBuffer(size int) *cgo_MyBuffer_T {
p := C.NewMyBuffer(C.int(size))
return (*cgo_MyBuffer_T)(p)
}
func cgo_DeleteMyBuffer(p *cgo_MyBuffer_T) {
C.DeleteMyBuffer((*C.MyBuffer_T)(p))
}
func cgo_MyBuffer_Data(p *cgo_MyBuffer_T) *C.char {
return C.MyBuffer_Data((*C.MyBuffer_T)(p))
}
func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int {
return C.MyBuffer_Size((*C.MyBuffer_T)(p))
}
```
为了区分,我们在 Go 中的每个类型和函数名称前面增加了 `cgo_` 前缀,比如 cgo_MyBuffer_T 是对应 C 中的 MyBuffer_T 类型。
为了处理简单,在包装纯 C 函数到 Go 函数时,除了 cgo_MyBuffer_T 类型外,对输入参数和返回值的基础类型,我们依然是用的 C 语言的类型。
### 2.8.1.4 包装为 Go 对象
在将纯 C 接口包装为 Go 函数之后,我们就可以很容易地基于包装的 Go 函数构造出 Go 对象来。因为 cgo_MyBuffer_T 是从 C 语言空间导入的类型,它无法定义自己的方法,因此我们构造了一个新的 MyBuffer 类型,里面的成员持有 cgo_MyBuffer_T 指向的 C 语言缓存对象。
```go
// my_buffer.go
package main
import "unsafe"
type MyBuffer struct {
cptr *cgo_MyBuffer_T
}
func NewMyBuffer(size int) *MyBuffer {
return &MyBuffer{
cptr: cgo_NewMyBuffer(size),
}
}
func (p *MyBuffer) Delete() {
cgo_DeleteMyBuffer(p.cptr)
}
func (p *MyBuffer) Data() []byte {
data := cgo_MyBuffer_Data(p.cptr)
size := cgo_MyBuffer_Size(p.cptr)
return ((*[1 << 31]byte)(unsafe.Pointer(data)))[0:int(size):int(size)]
}
```
同时,因为 Go 语言的切片本身含有长度信息,我们将 cgo_MyBuffer_Data 和 cgo_MyBuffer_Size 两个函数合并为 `MyBuffer.Data` 方法,它返回一个对应底层 C 语言缓存空间的切片。
现在我们就可以很容易在 Go 语言中使用包装后的缓存对象了(底层是基于 C++ 的 `std::string` 实现):
```go
package main
//#include <stdio.h>
import "C"
import "unsafe"
func main() {
buf := NewMyBuffer(1024)
defer buf.Delete()
copy(buf.Data(), []byte("hello\x00"))
C.puts((*C.char)(unsafe.Pointer(&(buf.Data()[0]))))
}
```
例子中,我们创建了一个 1024 字节大小的缓存,然后通过 copy 函数向缓存填充了一个字符串。为了方便 C 语言字符串函数处理,我们在填充字符串的默认用'\0'表示字符串结束。最后我们直接获取缓存的底层数据指针,用 C 语言的 puts 函数打印缓存的内容。
## 2.8.2 Go 语言对象到 C++ 类
要实现 Go 语言对象到 C++ 类的包装需要经过以下几个步骤:首先是将 Go 对象映射为一个 id;然后基于 id 导出对应的 C 接口函数;最后是基于 C 接口函数包装为 C++ 对象。
### 2.8.2.1 构造一个 Go 对象
为了便于演示,我们用 Go 语言构建了一个 Person 对象,每个 Person 可以有名字和年龄信息:
```go
package main
type Person struct {
name string
age int
}
func NewPerson(name string, age int) *Person {
return &Person{
name: name,
age: age,
}
}
func (p *Person) Set(name string, age int) {
p.name = name
p.age = age
}
func (p *Person) Get() (name string, age int) {
return p.name, p.age
}
```
Person 对象如果想要在 C/C++ 中访问,需要通过 cgo 导出 C 接口来访问。
### 2.8.2.2 导出 C 接口
我们前面仿照 C++ 对象到 C 接口的过程,也抽象一组 C 接口描述 Person 对象。创建一个 `person_capi.h` 文件,对应 C 接口规范文件:
```c
// person_capi.h
#include <stdint.h>
typedef uintptr_t person_handle_t;
person_handle_t person_new(char* name, int age);
void person_delete(person_handle_t p);
void person_set(person_handle_t p, char* name, int age);
char* person_get_name(person_handle_t p, char* buf, int size);
int person_get_age(person_handle_t p);
```
然后是在 Go 语言中实现这一组 C 函数。
需要注意的是,通过 CGO 导出 C 函数时,输入参数和返回值类型都不支持 const 修饰,同时也不支持可变参数的函数类型。同时如内存模式一节所述,我们无法在 C/C++ 中直接长期访问 Go 内存对象。因此我们使用前一节所讲述的技术将 Go 对象映射为一个整数 id。
下面是 `person_capi.go` 文件,对应 C 接口函数的实现:
```go
// person_capi.go
package main
//#include "./person_capi.h"
import "C"
import "unsafe"
//export person_new
func person_new(name *C.char, age C.int) C.person_handle_t {
id := NewObjectId(NewPerson(C.GoString(name), int(age)))
return C.person_handle_t(id)
}
//export person_delete
func person_delete(h C.person_handle_t) {
ObjectId(h).Free()
}
//export person_set
func person_set(h C.person_handle_t, name *C.char, age C.int) {
p := ObjectId(h).Get().(*Person)
p.Set(C.GoString(name), int(age))
}
//export person_get_name
func person_get_name(h C.person_handle_t, buf *C.char, size C.int) *C.char {
p := ObjectId(h).Get().(*Person)
name, _ := p.Get()
n := int(size) - 1
bufSlice := ((*[1 << 31]byte)(unsafe.Pointer(buf)))[0:n:n]
n = copy(bufSlice, []byte(name))
bufSlice[n] = 0
return buf
}
//export person_get_age
func person_get_age(h C.person_handle_t) C.int {
p := ObjectId(h).Get().(*Person)
_, age := p.Get()
return C.int(age)
}
```
在创建 Go 对象后,我们通过 NewObjectId 将 Go 对应映射为 id。然后将 id 强制转义为 person_handle_t 类型返回。其它的接口函数则是根据 person_handle_t 所表示的 id,让根据 id 解析出对应的 Go 对象。
### 2.8.2.3 封装 C++ 对象
有了 C 接口之后封装 C++ 对象就比较简单了。常见的做法是新建一个 Person 类,里面包含一个 person_handle_t 类型的成员对应真实的 Go 对象,然后在 Person 类的构造函数中通过 C 接口创建 Go 对象,在析构函数中通过 C 接口释放 Go 对象。下面是采用这种技术的实现:
```c++
extern "C" {
#include "./person_capi.h"
}
struct Person {
person_handle_t goobj_;
Person(const char* name, int age) {
this->goobj_ = person_new((char*)name, age);
}
~Person() {
person_delete(this->goobj_);
}
void Set(char* name, int age) {
person_set(this->goobj_, name, age);
}
char* GetName(char* buf, int size) {
return person_get_name(this->goobj_ buf, size);
}
int GetAge() {
return person_get_age(this->goobj_);
}
}
```
包装后我们就可以像普通 C++ 类那样使用了:
```c++
#include "person.h"
#include <stdio.h>
int main() {
auto p = new Person("gopher", 10);
char buf[64];
char* name = p->GetName(buf, sizeof(buf)-1);
int age = p->GetAge();
printf("%s, %d years old.\n", name, age);
delete p;
return 0;
}
```
### 2.8.2.4 封装 C++ 对象改进
在前面的封装 C++ 对象的实现中,每次通过 new 创建一个 Person 实例需要进行两次内存分配:一次是针对 C++ 版本的 Person,再一次是针对 Go 语言版本的 Person。其实 C++ 版本的 Person 内部只有一个 person_handle_t 类型的 id,用于映射 Go 对象。我们完全可以将 person_handle_t 直接当中 C++ 对象来使用。
下面时改进后的包装方式:
```c++
extern "C" {
#include "./person_capi.h"
}
struct Person {
static Person* New(const char* name, int age) {
return (Person*)person_new((char*)name, age);
}
void Delete() {
person_delete(person_handle_t(this));
}
void Set(char* name, int age) {
person_set(person_handle_t(this), name, age);
}
char* GetName(char* buf, int size) {
return person_get_name(person_handle_t(this), buf, size);
}
int GetAge() {
return person_get_age(person_handle_t(this));
}
};
```
我们在 Person 类中增加了一个叫 New 静态成员函数,用于创建新的 Person 实例。在 New 函数中通过调用 person_new 来创建 Person 实例,返回的是 `person_handle_t` 类型的 id,我们将其强制转型作为 `Person*` 类型指针返回。在其它的成员函数中,我们通过将 this 指针再反向转型为 `person_handle_t` 类型,然后通过 C 接口调用对应的函数。
到此,我们就达到了将 Go 对象导出为 C 接口,然后基于 C 接口再包装为 C++ 对象以便于使用的目的。
## 2.8.3 彻底解放 C++ 的 this 指针
熟悉 Go 语言的用法会发现 Go 语言中方法是绑定到类型的。比如我们基于 int 定义一个新的 Int 类型,就可以有自己的方法:
```go
type Int int
func (p Int) Twice() int {
return int(p)*2
}
func main() {
var x = Int(42)
fmt.Println(int(x))
fmt.Println(x.Twice())
}
```
这样就可以在不改变原有数据底层内存结构的前提下,自由切换 int 和 Int 类型来使用变量。
而在 C++ 中要实现类似的特性,一般会采用以下实现:
```c++
class Int {
int v_;
Int(v int) { this.v_ = v; }
int Twice() const{ return this.v_*2;}
};
int main() {
Int v(42);
printf("%d\n", v); // error
printf("%d\n", v.Twice());
}
```
新包装后的 Int 类虽然增加了 Twice 方法,但是失去了自由转回 int 类型的权利。这时候不仅连 printf 都无法输出 Int 本身的值,而且也失去了 int 类型运算的所有特性。这就是 C++ 构造函数的邪恶之处:以失去原有的一切特性的代价换取 class 的施舍。
造成这个问题的根源是 C++ 中 this 被固定为 class 的指针类型了。我们重新回顾下 this 在 Go 语言中的本质:
```go
func (this Int) Twice() int
func Int_Twice(this Int) int
```
在 Go 语言中,和 this 有着相似功能的类型接收者参数其实只是一个普通的函数参数,我们可以自由选择值或指针类型。
如果以 C 语言的角度来思考,this 也只是一个普通的 `void*` 类型的指针,我们可以随意自由地将 this 转换为其它类型。
```c++
struct Int {
int Twice() {
const int* p = (int*)(this);
return (*p) * 2;
}
};
int main() {
int x = 42;
printf("%d\n", x);
printf("%d\n", ((Int*)(&x))->Twice());
return 0;
}
```
这样我们就可以通过将 int 类型指针强制转为 Int 类型指针,代替通过默认的构造函数后 new 来构造 Int 对象。
在 Twice 函数的内部,以相反的操作将 this 指针转回 int 类型的指针,就可以解析出原有的 int 类型的值了。
这时候 Int 类型只是编译时的一个壳子,并不会在运行时占用额外的空间。
因此 C++ 的方法其实也可以用于普通非 class 类型,C++ 到普通成员函数其实也是可以绑定到类型的。
只有纯虚方法是绑定到对象,那就是接口。
================================================
FILE: ch2-cgo/ch2-09-static-shared-lib.md
================================================
# 2.9 静态库和动态库
CGO 在使用 C/C++ 资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在 `import "C"` 之前的注释部分包含 C 代码,或者在当前包中包含 C/C++ 源文件。链接静态库和动态库的方式比较类似,都是通过在 LDFLAGS 选项指定要链接的库方式链接。本节我们主要关注在 CGO 中如何使用静态库和动态库相关的问题。
## 2.9.1 使用 C 静态库
如果 CGO 中引入的 C/C++ 资源有代码而且代码规模也比较小,直接使用源码是最理想的方式,但很多时候我们并没有源代码,或者从 C/C++ 源代码开始构建的过程异常复杂,这种时候使用 C 静态库也是一个不错的选择。静态库因为是静态链接,最终的目标程序并不会产生额外的运行时依赖,也不会出现动态库特有的跨运行时资源管理的错误。不过静态库对链接阶段会有一定要求:静态库一般包含了全部的代码,里面会有大量的符号,如果不同静态库之间出现了符号冲突则会导致链接的失败。
我们先用纯 C 语言构造一个简单的静态库。我们要构造的静态库名叫 number,库中只有一个 number_add_mod 函数,用于表示数论中的模加法运算。number 库的文件都在 number 目录下。
`number/number.h` 头文件只有一个纯 C 语言风格的函数声明:
```c
int number_add_mod(int a, int b, int mod);
```
`number/number.c` 对应函数的实现:
```c
#include "number.h"
int number_add_mod(int a, int b, int mod) {
return (a+b)%mod;
}
```
因为 CGO 使用的是 GCC 命令来编译和链接 C 和 Go 桥接的代码。因此静态库也必须是 GCC 兼容的格式。
通过以下命令可以生成一个叫 libnumber.a 的静态库:
```
$ cd ./number
$ gcc -c -o number.o number.c
$ ar rcs libnumber.a number.o
```
生成 libnumber.a 静态库之后,我们就可以在 CGO 中使用该资源了。
创建 main.go 文件如下:
```go
package main
//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.number_add_mod(10, 5, 12))
}
```
其中有两个 #cgo 命令,分别是编译和链接参数。CFLAGS 通过 `-I./number` 将 number 库对应头文件所在的目录加入头文件检索路径。LDFLAGS 通过 `-L${SRCDIR}/number` 将编译后 number 静态库所在目录加为链接库检索路径,`-lnumber` 表示链接 libnumber.a 静态库。需要注意的是,在链接部分的检索路径不能使用相对路径(C/C++ 代码的链接程序所限制),我们必须通过 cgo 特有的 `${SRCDIR}` 变量将源文件对应的当前目录路径展开为绝对路径(因此在 windows 平台中绝对路径不能有空白符号)。
因为我们有 number 库的全部代码,所以我们可以用 go generate 工具来生成静态库,或者是通过 Makefile 来构建静态库。因此发布 CGO 源码包时,我们并不需要提前构建 C 静态库。
因为多了一个静态库的构建步骤,这种使用了自定义静态库并已经包含了静态库全部代码的 Go 包无法直接用 go get 安装。不过我们依然可以通过 go get 下载,然后用 go generate 触发静态库构建,最后才是 go install 来完成安装。
为了支持 go get 命令直接下载并安装,我们 C 语言的 `#include` 语法可以将 number 库的源文件链接到当前的包。
创建 `z_link_number_c.c` 文件如下:
```c
#include "./number/number.c"
```
然后在执行 go get 或 go build 之类命令的时候,CGO 就是自动构建 number 库对应的代码。这种技术是在不改变静态库源代码组织结构的前提下,将静态库转化为了源代码方式引用。这种 CGO 包是最完美的。
如果使用的是第三方的静态库,我们需要先下载安装静态库到合适的位置。然后在 #cgo 命令中通过 CFLAGS 和 LDFLAGS 来指定头文件和库的位置。对于不同的操作系统甚至同一种操作系统的不同版本来说,这些库的安装路径可能都是不同的,那么如何在代码中指定这些可能变化的参数呢?
在 Linux 环境,有一个 pkg-config 命令可以查询要使用某个静态库或动态库时的编译和链接参数。我们可以在 #cgo 命令中直接使用 pkg-config 命令来生成编译和链接参数。而且还可以通过 PKG_CONFIG 环境变量定制 pkg-config 命令。因为不同的操作系统对 pkg-config 命令的支持不尽相同,通过该方式很难兼容不同的操作系统下的构建参数。不过对于 Linux 等特定的系统,pkg-config 命令确实可以简化构建参数的管理。关于 pkg-config 的使用细节在此我们不深入展开,大家可以自行参考相关文档。
## 2.9.2 使用 C 动态库
动态库出现的初衷是对于相同的库,多个进程可以共享同一个,以节省内存和磁盘资源。但是在磁盘和内存已经白菜价的今天,这两个作用已经显得微不足道了,那么除此之外动态库还有哪些存在的价值呢?从库开发角度来说,动态库可以隔离不同动态库之间的关系,减少链接时出现符号冲突的风险。而且对于 windows 等平台,动态库是跨越 VC 和 GCC 不同编译器平台的唯一的可行方式。
对于 CGO 来说,使用动态库和静态库是一样的,因为动态库也必须要有一个小的静态导出库用于链接动态库(Linux 下可以直接链接 so 文件,但是在 Windows 下必须为 dll 创建一个 `.a` 文件用于链接)。我们还是以前面的 number 库为例来说明如何以动态库方式使用。
对于在 macOS 和 Linux 系统下的 gcc 环境,我们可以用以下命令创建 number 库的的动态库:
```
$ cd number
$ gcc -shared -o libnumber.so number.c
```
因为动态库和静态库的基础名称都是 libnumber,只是后缀名不同而已。因此 Go 语言部分的代码和静态库版本完全一样:
```go
package main
//#cgo CFLAGS: -I./number
//#cgo LDFLAGS: -L${SRCDIR}/number -lnumber
//
//#include "number.h"
import "C"
import "fmt"
func main() {
fmt.Println(C.number_add_mod(10, 5, 12))
}
```
编译时 GCC 会自动找到 libnumber.a 或 libnumber.so 进行链接。
对于 windows 平台,我们还可以用 VC 工具来生成动态库(windows 下有一些复杂的 C++ 库只能用 VC 构建)。我们需要先为 number.dll 创建一个 def 文件,用于控制要导出到动态库的符号。
number.def 文件的内容如下:
```
LIBRARY number.dll
EXPORTS
number_add_mod
```
其中第一行的 LIBRARY 指明动态库的文件名,然后的 EXPORTS 语句之后是要导出的符号名列表。
现在我们可以用以下命令来创建动态库(需要进入 VC 对应的 x64 命令行环境)。
```
$ cl /c number.c
$ link /DLL /OUT:number.dll number.obj number.def
```
这时候会为 dll 同时生成一个 number.lib 的导出库。但是在 CGO 中我们无法使用 lib 格式的链接库。
要生成 `.a` 格式的导出库需要通过 mingw 工具箱中的 dlltool 命令完成:
```
$ dlltool -dllname number.dll --def number.def --output-lib libnumber.a
```
生成了 libnumber.a 文件之后,就可以通过 `-lnumber` 链接参数进行链接了。
需要注意的是,在运行时需要将动态库放到系统能够找到的位置。对于 windows 来说,可以将动态库和可执行程序放到同一个目录,或者将动态库所在的目录绝对路径添加到 PATH 环境变量中。对于 macOS 来说,需要设置 DYLD_LIBRARY_PATH 环境变量。而对于 Linux 系统来说,需要设置 LD_LIBRARY_PATH 环境变量。
## 2.9.3 导出 C 静态库
CGO 不仅可以使用 C 静态库,也可以将 Go 实现的函数导出为 C 静态库。我们现在用 Go 实现前面的 number 库的模加法函数。
创建 number.go,内容如下:
```go
package main
import "C"
func main() {}
//export number_add_mod
func number_add_mod(a, b, mod C.int) C.int {
return (a + b) % mod
}
```
根据 CGO 文档的要求,我们需要在 main 包中导出 C 函数。对于 C 静态库构建方式来说,会忽略 main 包中的 main 函数,只是简单导出 C 函数。采用以下命令构建:
```
$ go build -buildmode=c-archive -o number.a
```
在生成 number.a 静态库的同时,cgo 还会生成一个 number.h 文件。
number.h 文件的内容如下(为了便于显示,内容做了精简):
```c
#ifdef __cplusplus
extern "C" {
#endif
extern int number_add_mod(int p0, int p1, int p2);
#ifdef __cplusplus
}
#endif
```
其中 `extern "C"` 部分的语法是为了同时适配 C 和 C++ 两种语言。核心内容是声明了要导出的 number_add_mod 函数。
然后我们创建一个 `_test_main.c` 的 C 文件用于测试生成的 C 静态库(用下划线作为前缀名是让为了让 go build 构建 C 静态库时忽略这个文件):
```c
#include "number.h"
#include <stdio.h>
int main() {
int a = 10;
int b = 5;
int c = 12;
int x = number_add_mod(a, b, c);
printf("(%d+%d)%%%d = %d\n", a, b, c, x);
return 0;
}
```
通过以下命令编译并运行:
```
$ gcc -o a.out _test_main.c number.a
$ ./a.out
```
使用 CGO 创建静态库的过程非常简单。
## 2.9.4 导出 C 动态库
CGO 导出动态库的过程和静态库类似,只是将构建模式改为 `c-shared`,输出文件名改为 `number.so` 而已:
```
$ go build -buildmode=c-shared -o number.so
```
`_test_main.c` 文件内容不变,然后用以下命令编译并运行:
```
$ gcc -o a.out _test_main.c number.so
$ ./a.out
```
## 2.9.5 导出非 main 包的函数
通过 `go help buildmode` 命令可以查看 C 静态库和 C 动态库的构建说明:
```
-buildmode=c-archive
Build the listed main package, plus all packages it imports,
into a C archive file. The only callable symbols will be those
functions exported using a cgo //export comment. Requires
exactly one main package to be listed.
-buildmode=c-shared
Build the listed main package, plus all packages it imports,
into a C shared library. The only callable symbols will
be those functions exported using a cgo //export comment.
Requires exactly one main package to be listed.
```
文档说明导出的 C 函数必须是在 main 包导出,然后才能在生成的头文件包含声明的语句。但是很多时候我们可能更希望将不同类型的导出函数组织到不同的 Go 包中,然后统一导出为一个静态库或动态库。
要实现从是从非 main 包导出 C 函数,或者是多个包导出 C 函数(因为只能有一个 main 包),我们需要自己提供导出 C 函数对应的头文件(因为 CGO 无法为非 main 包的导出函数生成头文件)。
假设我们先创建一个 number 子包,用于提供模加法函数:
```go
package number
import "C"
//export number_add_mod
func number_add_mod(a, b, mod C.int) C.int {
return (a + b) % mod
}
```
然后是当前的 main 包:
```go
package main
import "C"
import (
"fmt"
_ "./number"
)
func main() {
println("Done")
}
//export goPrintln
func goPrintln(s *C.char) {
fmt.Println("goPrintln:", C.GoString(s))
}
```
其中我们导入了 number 子包,在 number 子包中有导出的 C 函数 number_add_mod,同时我们在 main 包也导出了 goPrintln 函数。
通过以下命令创建 C 静态库:
```
$ go build -buildmode=c-archive -o main.a
```
这时候在生成 main.a 静态库的同时,也会生成一个 main.h 头文件。但是 main.h 头文件中只有 main 包中导出的 goPrintln 函数的声明,并没有 number 子包导出函数的声明。其实 number_add_mod 函数在生成的 C 静态库中是存在的,我们可以直接使用。
创建 `_test_main.c` 测试文件如下:
```c
#include <stdio.h>
void goPrintln(char*);
int number_add_mod(int a, int b, int mod);
int main() {
int a = 10;
int b = 5;
int c = 12;
int x = number_add_mod(a, b, c);
printf("(%d+%d)%%%d = %d\n", a, b, c, x);
goPrintln("done");
return 0;
}
```
我们并没有包含 CGO 自动生成的 main.h 头文件,而是通过手工方式声明了 goPrintln 和 number_add_mod 两个导出函数。这样我们就实现了从多个 Go 包导出 C 函数了。
================================================
FILE: ch2-cgo/ch2-10-link.md
================================================
# 2.10 编译和链接参数
编译和链接参数是每一个 C/C++ 程序员需要经常面对的问题。构建每一个 C/C++ 应用均需要经过编译和链接两个步骤,CGO 也是如此。
本节我们将简要讨论 CGO 中经常用到的编译和链接参数的用法。
## 2.10.1 编译参数:CFLAGS/CPPFLAGS/CXXFLAGS
编译参数主要是头文件的检索路径,预定义的宏等参数。理论上来说 C 和 C++ 是完全独立的两个编程语言,它们可以有着自己独立的编译参数。
但是因为 C++ 语言对 C 语言做了深度兼容,甚至可以将 C++ 理解为 C 语言的超集,因此 C 和 C++ 语言之间又会共享很多编译参数。
因此 CGO 提供了 CFLAGS/CPPFLAGS/CXXFLAGS 三种参数,其中 CFLAGS 对应 C 语言编译参数 (以 `.c` 后缀名)、
CPPFLAGS 对应 C/C++ 代码编译参数 (*.c,*.cc,*.cpp,*.cxx)、CXXFLAGS 对应纯 C++ 编译参数 (*.cc,*.cpp,*.cxx)。
## 2.10.2 链接参数:LDFLAGS
链接参数主要包含要链接库的检索目录和要链接库的名字。因为历史遗留问题,链接库不支持相对路径,我们必须为链接库指定绝对路径。
cgo 中的 ${SRCDIR} 为当前目录的绝对路径。经过编译后的 C 和 C++ 目标文件格式是一样的,因此 LDFLAGS 对应 C/C++ 共同的链接参数。
## 2.10.3 pkg-config
为不同 C/C++ 库提供编译和链接参数是一项非常繁琐的工作,因此 cgo 提供了对应 `pkg-config` 工具的支持。
我们可以通过 `#cgo pkg-config xxx` 命令来生成 xxx 库需要的编译和链接参数,其底层通过调用
`pkg-config xxx --cflags` 生成编译参数,通过 `pkg-config xxx --libs` 命令生成链接参数。
需要注意的是 `pkg-config` 工具生成的编译和链接参数是 C/C++ 公用的,无法做更细的区分。
`pkg-config` 工具虽然方便,但是有很多非标准的 C/C++ 库并没有实现对其支持。
这时候我们可以手工为 `pkg-config` 工具创建对应库的编译和链接参数实现支持。
比如有一个名为 xxx 的 C/C++ 库,我们可以手工创建 `/usr/local/lib/pkgconfig/xxx.pc` 文件:
```
Name: xxx
Cflags:-I/usr/local/include
Libs:-L/usr/local/lib –lxxx2
```
其中 Name 是库的名字,Cflags 和 Libs 行分别对应 xxx 使用库需要的编译和链接参数。如果 `pc` 文件在其它目录,
可以通过 PKG_CONFIG_PATH 环境变量指定 `pkg-config` 工具的检索目录。
而对应 cgo 来说,我们甚至可以通过 PKG_CONFIG 环境变量可指定自定义的 pkg-config 程序。
如果是自己实现 CGO 专用的 pkg-config 程序,只要处理 `--cflags` 和 `--libs` 两个参数即可。
下面的程序是 macos 系统下生成 Python3 的编译和链接参数:
```go
// py3-config.go
func main() {
for _, s := range os.Args {
if s == "--cflags" {
out, _ := exec.Command("python3-config", "--cflags").CombinedOutput()
out = bytes.Replace(out, []byte("-arch"), []byte{}, -1)
out = bytes.Replace(out, []byte("i386"), []byte{}, -1)
out = bytes.Replace(out, []byte("x86_64"), []byte{}, -1)
fmt.Print(string(out))
return
}
if s == "--libs" {
out, _ := exec.Command("python3-config", "--ldflags").CombinedOutput()
fmt.Print(string(out))
return
}
}
}
```
然后通过以下命令构建并使用自定义的 `pkg-config` 工具:
```
$ go build -o py3-config py3-config.go
$ PKG_CONFIG=./py3-config go build -buildmode=c-shared -o gopkg.so main.go
```
具体的细节可以参考 Go 实现 Python 模块章节。
## 2.10.4 go get 链
在使用 `go get` 获取 Go 语言包的同时会获取包依赖的包。比如 A 包依赖 B 包,B 包依赖 C 包,C 包依赖 D 包:
`pkgA -> pkgB -> pkgC -> pkgD -> ...`。再 go get 获取 A 包之后会依次线获取 BCD 包。
如果在获取 B 包之后构建失败,那么将导致链条的断裂,从而导致 A 包的构建失败。
链条断裂的原因有很多,其中常见的原因有:
- 不支持某些系统, 编译失败
- 依赖 cgo, 用户没有安装 gcc
- 依赖 cgo, 但是依赖的库没有安装
- 依赖 pkg-config, windows 上没有安装
- 依赖 pkg-config, 没有找到对应的 bc 文件
- 依赖 自定义的 pkg-config, 需要额外的配置
- 依赖 swig, 用户没有安装 swig, 或版本不对
仔细分析可以发现,失败的原因中和 CGO 相关的问题占了绝大多数。这并不是偶然现象,
自动化构建 C/C++ 代码一直是一个世界难题,到目前位置也没有出现一个大家认可的统一的 C/C++ 管理工具。
因为用了 cgo,比如 gcc 等构建工具是必须安装的,同时尽量要做到对主流系统的支持。
如果依赖的 C/C++ 包比较小并且有源代码的前提下,可以优先选择从代码构建。
比如 `github.com/chai2010/webp` 包通过为每个 C/C++ 源文件在当前包建立关键文件实现零配置依赖:
```
// z_libwebp_src_dec_alpha.c
#include "./internal/libwebp/src/dec/alpha.c"
```
因此在编译 `z_libwebp_src_dec_alpha.c` 文件时,会编译 libweb 原生的代码。
其中的依赖是相对目录,对于不同的平台支持可以保持最大的一致性。
## 2.10.5 多个非 main 包中导出 C 函数
官方文档说明导出的 Go 函数要放 main 包,但是真实情况是其它包的 Go 导出函数也是有效的。
因为导出后的 Go 函数就可以当作 C 函数使用,所以必须有效。但是不同包导出的 Go 函数将在同一个全局的名字空间,因此需要小心避免重名的问题。
如果是从不同的包导出 Go 函数到 C 语言空间,那么 cgo 自动生成的 `_cgo_export.h` 文件将无法包含全部导出的函数声明,
我们必须通过手写头文件的方式声明导出的全部函数。
================================================
FILE: ch2-cgo/ch2-11-ext.md
================================================
## 2.11 补充说明
CGO 是 C 语言和 Go 语言混合编程的技术,因此要想熟练地使用 CGO 需要了解这两门语言。C 语言推荐两本书:第一本是 C 语言之父编写的《C 程序设计语言》;第二本是讲述 C 语言模块化编程的《C 语言接口与实现: 创建可重用软件的技术》。Go 语言推荐官方出版的《The Go Programming Language》和 Go 语言自带的全部文档和全部代码。
为何要花费巨大的精力学习 CGO 是一个问题。任何技术和语言都有它自身的优点和不足,Go 语言不是银弹,它无法解决全部问题。而通过 CGO 可以继承 C/C++ 将近半个世纪的软件遗产,通过 CGO 可以用 Go 给其它系统写 C 接口的共享库,通过 CGO 技术可以让 Go 语言编写的代码可以很好地融入现有的软件生态——而现在的软件正式建立在 C/C++ 语言之上的。因此说 CGO 是一个保底的后备技术,它是 Go 的一个重量级的替补技术,值得任何一个严肃的 Go 语言开发人员学习。
================================================
FILE: ch2-cgo/readme.md
================================================
# 第 2 章 CGO 编程
*过去的经验往往是走向未来的枷锁,因为在过气技术中投入的沉没成本会阻碍人们拥抱新技术。——chai2010*
*曾经一度因未能习得 C++ 令人眼花缭乱的新标准而痛苦不已;Go 语言 “少既是多” 大道至简的理念让我重拾信心,寻回了久违的编程乐趣。——Ending*
C/C++ 经过几十年的发展,已经积累了庞大的软件资产,它们很多久经考验而且性能已经足够优化。Go 语言必须能够站在 C/C++ 这个巨人的肩膀之上,有了海量的 C/C++ 软件资产兜底之后,我们才可以放心愉快地用 Go 语言编程。C 语言作为一个通用语言,很多库会选择提供一个 C 兼容的 API,然后用其他不同的编程语言实现。Go 语言通过自带的一个叫 CGO 的工具来支持 C 语言函数调用,同时我们可以用 Go 语言导出 C 动态库接口给其它语言使用。本章主要讨论 CGO 编程中涉及的一些问题。
================================================
FILE: ch3-asm/ch3-01-basic.md
================================================
# 3.1 快速入门
Go 汇编程序始终是幽灵一样的存在。我们将通过分析简单的 Go 程序输出的汇编代码,然后照猫画虎用汇编实现一个简单的输出程序。
## 3.1.1 实现和声明
Go 汇编语言并不是一个独立的语言,因为 Go 汇编程序无法独立使用。Go 汇编代码必须以 Go 包的方式组织,同时包中至少要有一个 Go 语言文件用于指明当前包名等基本包信息。如果 Go 汇编代码中定义的变量和函数要被其它 Go 语言代码引用,还需要通过 Go 语言代码将汇编中定义的符号声明出来。用于变量的定义和函数的定义 Go 汇编文件类似于 C 语言中的 `.c` 文件,而用于导出汇编中定义符号的 Go 源文件类似于 C 语言的 `.h` 文件。
## 3.1.2 定义整数变量
为了简单,我们先用 Go 语言定义并赋值一个整数变量,然后查看生成的汇编代码。
首先创建一个 `pkg.go` 文件,内容如下:
```go
package pkg
var Id = 9527
```
代码中只定义了一个 int 类型的包级变量,并进行了初始化。然后用以下命令查看的 Go 语言程序对应的伪汇编代码:
```
$ go tool compile -S pkg.go
"".Id SNOPTRDATA size=8
0x0000 37 25 00 00 00 00 00 00 '.......
```
其中 `go tool compile` 命令用于调用 Go 语言提供的底层命令工具,其中 `-S` 参数表示输出汇编格式。输出的汇编比较简单,其中 `"".Id` 对应 Id 变量符号,变量的内存大小为 8 个字节。变量的初始化内容为 `37 25 00 00 00 00 00 00`,对应十六进制格式的 0x2537,对应十进制为 9527。SNOPTRDATA 是相关的标志,其中 NOPTR 表示数据中不包含指针数据。
以上的内容只是目标文件对应的汇编,和 Go 汇编语言虽然相似当并不完全等价。Go 语言官网自带了一个 Go 汇编语言的入门教程,地址在:https://golang.org/doc/asm 。
Go 汇编语言提供了 DATA 命令用于初始化包变量,DATA 命令的语法如下:
```
DATA symbol+offset(SB)/width, value
```
其中 symbol 为变量在汇编语言中对应的标识符,offset 是符号开始地址的偏移量,width 是要初始化内存的宽度大小,value 是要初始化的值。其中当前包中 Go 语言定义的符号 symbol,在汇编代码中对应 `·symbol`,其中 “·” 中点符号为一个特殊的 unicode 符号。
我们采用以下命令可以给 Id 变量初始化为十六进制的 0x2537,对应十进制的 9527(常量需要以美元符号 $ 开头表示):
```
DATA ·Id+0(SB)/1,$0x37
DATA ·Id+1(SB)/1,$0x25
```
变量定义好之后需要导出以供其它代码引用。Go 汇编语言提供了 GLOBL 命令用于将符号导出:
```
GLOBL symbol(SB), width
```
其中 symbol 对应汇编中符号的名字,width 为符号对应内存的大小。用以下命令将汇编中的 ·Id 变量导出:
```
GLOBL ·Id, $8
```
现在已经初步完成了用汇编定义一个整数变量的工作。
为了便于其它包使用该 Id 变量,我们还需要在 Go 代码中声明该变量,同时也给变量指定一个合适的类型。修改 `pkg.go` 的内容如下:
```go
package pkg
var Id int
```
现状 Go 语言的代码不再是定义一个变量,语义变成了声明一个变量(声明一个变量时不能再进行初始化操作)。而 Id 变量的定义工作已经在汇编语言中完成了。
我们将完整的汇编代码放到 `pkg_amd64.s` 文件中:
```
#include "textflag.h"
GLOBL ·Id(SB),NOPTR,$8
DATA ·Id+0(SB)/1,$0x37
DATA ·Id+1(SB)/1,$0x25
DATA ·Id+2(SB)/1,$0x00
DATA ·Id+3(SB)/1,$0x00
DATA ·Id+4(SB)/1,$0x00
DATA ·Id+5(SB)/1,$0x00
DATA ·Id+6(SB)/1,$0x00
DATA ·Id+7(SB)/1,$0x00
```
文件名 `pkg_amd64.s` 的后缀名表示 AMD64 环境下的汇编代码文件。
虽然 pkg 包是用汇编实现,但是用法和之前的 Go 语言版本完全一样:
```go
package main
import pkg "pkg 包的路径"
func main() {
println(pkg.Id)
}
```
对于 Go 包的用户来说,用 Go 汇编语言或 Go 语言实现并无任何区别。
## 3.1.3 定义字符串变量
在前一个例子中,我们通过汇编定义了一个整数变量。现在我们提高一点难度,尝试通过汇编定义一个字符串变量。虽然从 Go 语言角度看,定义字符串和整数变量的写法基本相同,但是字符串底层却有着比单个整数更复杂的数据结构。
实验的流程和前面的例子一样,还是先用 Go 语言实现类似的功能,然后观察分析生成的汇编代码,最后用 Go 汇编语言仿写。首先创建 `pkg.go` 文件,用 Go 语言定义字符串:
```go
package pkg
var Name = "gopher"
```
然后用以下命令查看的 Go 语言程序对应的伪汇编代码:
```
$ go tool compile -S pkg.go
go.string."gopher" SRODATA dupok size=6
0x0000 67 6f 70 68 65 72 gopher
"".Name SDATA size=16
0x0000 00 00 00 00 00 00 00 00 06 00 00 00 00 00 00 00 ................
rel 0+8 t=1 go.string."gopher"+0
```
输出中出现了一个新的符号 go.string."gopher",根据其长度和内容分析可以猜测是对应底层的 "gopher" 字符串数据。因为 Go 语言的字符串并不是值类型,Go 字符串其实是一种只读的引用类型。如果多个代码中出现了相同的 "gopher" 只读字符串时,程序链接后可以引用的同一个符号 go.string."gopher"。因此,该符号有一个 SRODATA 标志表示这个数据在只读内存段,dupok 表示出现多个相同标识符的数据时只保留一个就可以了。
而真正的 Go 字符串变量 `Name` 对应的大小却只有 16 个字节了。其实 `Name` 变量并没有直接对应 “gopher” 字符串,而是对应 16 字节大小的 reflect.StringHeader 结构体:
```go
type reflect.StringHeader struct {
Data uintptr
Len int
}
```
从汇编角度看,`Name` 变量其实对应的是 `reflect.StringHeader` 结构体类型。前 8 个字节对应底层真实字符串数据的指针,也就是符号 go.string."gopher" 对应的地址。后 8 个字节对应底层真实字符串数据的有效长度,这里是 6 个字节。
现在创建 pkg_amd64.s 文件,尝试通过汇编代码重新定义并初始化 `Name` 字符串:
```
GLOBL ·NameData(SB),$8
DATA ·NameData(SB)/8,$"gopher"
GLOBL ·Name(SB),$16
DATA ·Name+0(SB)/8,$·NameData(SB)
DATA ·Name+8(SB)/8,$6
```
因为在 Go 汇编语言中,go.string."gopher" 不是一个合法的符号,因此我们无法通过手工创建(这是给编译器保留的部分特权,因为手工创建类似符号可能打破编译器输出代码的某些规则)。因此我们新创建了一个 ·NameData 符号表示底层的字符串数据。然后定义 ·Name 符号内存大小为 16 字节,其中前 8 个字节用 ·NameData 符号对应的地址初始化,后 8 个字节为常量 6 表示字符串长度。
当用汇编定义好字符串变量并导出之后,还需要在 Go 语言中声明该字符串变量。然后就可以用 Go 语言代码测试 `Name` 变量了:
```go
package main
import pkg "path/to/pkg"
func main() {
println(pkg.Name)
}
```
不幸的是这次运行产生了以下错误:
```
pkgpath.NameData: missing Go type information for global symbol: size 8
```
错误提示汇编中定义的 NameData 符号没有类型信息。其实 Go 汇编语言中定义的数据并没有所谓的类型,每个符号只不过是对应一块内存而已,因此 NameData 符号也是没有类型的。但是 Go 语言是带垃圾回收器的语言,Go 汇编语言工作在这个自动垃圾回收体系框架内。当 Go 语言的垃圾回收器在扫描到 NameData 变量的时候,无法知晓该变量内部是否包含指针,因此就出现了这种错误。错误的根本原因并不是 NameData 没有类型,而是 NameData 变量没有标注是否会含有指针信息。
通过给 NameData 变量增加一个 NOPTR 标志,表示其中不会包含指针数据可以修复该错误:
```
#include "textflag.h"
GLOBL ·NameData(SB),NOPTR,$8
```
通过给 ·NameData 增加 NOPTR 标志的方式表示其中不含指针数据。我们也可以通过给 ·NameData 变量在 Go 语言中增加一个不含指针并且大小为 8 个字节的类型来修改该错误:
```go
package pkg
var NameData [8]byte
var Name string
```
我们将 NameData 声明为长度为 8 的字节数组。编译器可以通过类型分析出该变量不会包含指针,因此汇编代码中可以省略 NOPTR 标志。现在垃圾回收器在遇到该变量的时候就会停止内部数据的扫描。
在这个实现中,Name 字符串底层其实引用的是 NameData 内存对应的 “gopher” 字符串数据。因此,如果 NameData 发生变化,Name 字符串的数据也会跟着变化。
```go
func main() {
println(pkg.Name)
pkg.NameData[0] = '?'
println(pkg.Name)
}
```
当然这和字符串的只读定义是冲突的,正常的代码需要避免出现这种情况。最好的方法是不要导出内部的 NameData 变量,这样可以避免内部数据被无意破坏。
在用汇编定义字符串时我们可以换一种思维:将底层的字符串数据和字符串头结构体定义在一起,这样可以避免引入 NameData 符号:
```
GLOBL ·Name(SB),$24
DATA ·Name+0(SB)/8,$·Name+16(SB)
DATA ·Name+8(SB)/8,$6
DATA ·Name+16(SB)/8,$"gopher"
```
在新的结构中,Name 符号对应的内存从 16 字节变为 24 字节,多出的 8 个字节存放底层的 “gopher” 字符串。·Name 符号前 16 个字节依然对应 reflect.StringHeader 结构体:Data 部分对应 `$·Name+16(SB)`,表示数据的地址为 Name 符号往后偏移 16 个字节的位置;Len 部分依然对应 6 个字节的长度。这是 C 语言程序员经常使用的技巧。
## 3.1.4 定义 main 函数
前面的例子已经展示了如何通过汇编定义整型和字符串类型变量。我们现在将尝试用汇编实现函数,然后输出一个字符串。
先创建 main.go 文件,创建并初始化字符串变量,同时声明 main 函数:
```go
package main
var helloworld = "你好, 世界"
func main()
```
然后创建 main_amd64.s 文件,里面对应 main 函数的实现:
```
TEXT ·main(SB), $16-0
MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP)
MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP)
CALL runtime·printstring(SB)
CALL runtime·printnl(SB)
RET
```
`TEXT ·main(SB), $16-0` 用于定义 `main` 函数,其中 `$16-0` 表示 `main` 函数的帧大小是 16 个字节(对应 string 头部结构体的大小,用于给 `runtime·printstring` 函数传递参数),`0` 表示 `main` 函数没有参数和返回值。`main` 函数内部通过调用运行时内部的 `runtime·printstring(SB)` 函数来打印字符串。然后调用 `runtime·printnl` 打印换行符号。
Go 语言函数在函数调用时,完全通过栈传递调用参数和返回值。先通过 MOVQ 指令,将 helloworld 对应的字符串头部结构体的 16 个字节复制到栈指针 SP 对应的 16 字节的空间,然后通过 CALL 指令调用对应函数。最后使用 RET 指令表示当前函数返回。
## 3.1.5 特殊字符
Go 语言函数或方法符号在编译为目标文件后,目标文件中的每个符号均包含对应包的绝对导入路径。因此目标文件的符号可能非常复杂,比如 “path/to/pkg.(*SomeType).SomeMethod” 或“go.string."abc"”等名字。目标文件的符号名中不仅仅包含普通的字母,还可能包含点号、星号、小括弧和双引号等诸多特殊字符。而 Go 语言的汇编器是从 plan9 移植过来的二把刀,并不能处理这些特殊的字符,导致了用 Go 汇编语言手工实现 Go 诸多特性时遇到种种限制。
Go 汇编语言同样遵循 Go 语言少即是多的哲学,它只保留了最基本的特性:定义变量和全局函数。其中在变量和全局函数等名字中引入特殊的分隔符号支持 Go 语言等包体系。为了简化 Go 汇编器的词法扫描程序的实现,特别引入了 Unicode 中的中点 `·` 和大写的除法 `/`,对应的 Unicode 码点为 `U+00B7` 和 `U+2215`。汇编器编译后,中点 `·` 会被替换为 ASCII 中的点 “.”,大写的除法会被替换为 ASCII 码中的除法 “/”,比如 `math/rand·Int` 会被替换为 `math/rand.Int`。这样可以将中点和浮点数中的小数点、大写的除法和表达式中的除法符号分开,可以简化汇编程序词法分析部分的实现。
即使暂时抛开 Go 汇编语言设计取舍的问题,在不同的操作系统不同等输入法中如何输入中点 `·` 和除法 `/` 两个字符就是一个挑战。这两个字符在 https://golang.org/doc/asm 文档中均有描述,因此直接从该页面复制是最简单可靠的方式。
如果是 macOS 系统,则有以下几种方法输入中点 `·`:在不开输入法时,可直接用 option+shift+9 输入;如果是自带的简体拼音输入法,输入左上角 `~` 键对应 `·`,如果是自带的 Unicode 输入法,则可以输入对应的 Unicode 码点。其中 Unicode 输入法可能是最安全可靠等输入方式。
## 3.1.6 没有分号
Go 汇编语言中分号可以用于分隔同一行内的多个语句。下面是用分号混乱排版的汇编代码:
```
TEXT ·main(SB), $16-0; MOVQ ·helloworld+0(SB), AX; MOVQ ·helloworld+8(SB), BX;
MOVQ AX, 0(SP);MOVQ BX, 8(SP);CALL runtime·printstring(SB);
CALL runtime·printnl(SB);
RET;
```
和 Go 语言一样,也可以省略行尾的分号。当遇到末尾时,汇编器会自动插入分号。下面是省略分号后的代码:
```
TEXT ·main(SB), $16-0
MOVQ ·helloworld+0(SB), AX; MOVQ AX, 0(SP)
MOVQ ·helloworld+8(SB), BX; MOVQ BX, 8(SP)
CALL runtime·printstring(SB)
CALL runtime·printnl(SB)
RET
```
和 Go 语言一样,语句之间多个连续的空白字符和一个空格是等价的。
================================================
FILE: ch3-asm/ch3-02-arch.md
================================================
# 3.2 计算机结构
汇编语言是直面计算机的编程语言,因此理解计算机结构是掌握汇编语言的前提。当前流行的计算机基本采用的是冯·诺伊曼计算机体系结构(在某些特殊领域还有哈佛体系架构)。冯·诺依曼结构也称为普林斯顿结构,采用的是一种将程序指令和数据存储在一起的存储结构。冯·诺伊曼计算机中的指令和数据存储器其实指的是计算机中的内存,然后在配合 CPU 处理器就组成了一个最简单的计算机了。
汇编语言其实是一种非常简单的编程语言,因为它面向的计算机模型就是非常简单的。让人觉得汇编语言难学主要有几个原因:不同类型的 CPU 都有自己的一套指令;即使是相同的 CPU,32 位和 64 位的运行模式依然会有差异;不同的汇编工具同样有自己特有的汇编指令;不同的操作系统和高级编程语言和底层汇编的调用规范并不相同。本节将描述几个有趣的汇编语言模型,最后精简出一个适用于 AMD64 架构的精简指令集,以便于 Go 汇编语言的学习。
## 3.2.1 图灵机和 BF 语言
图灵机是由图灵提出的一种抽象计算模型。机器有一条无限长的纸带,纸带分成了一个一个的小方格,每个方格有不同的颜色,这类似于计算机中的内存。同时机器有一个探头在纸带上移来移去,类似于通过内存地址来读写内存上的数据。机器头有一组内部计算状态,还有一些固定的程序(更像一个哈佛结构)。在每个时刻,机器头都要从当前纸带上读入一个方格信息,然后根据自己的内部状态和当前要执行的程序指令将信息输出到纸带方格上,同时更新自己的内部状态并进行移动。
图灵机虽然不容易编程,但是非常容易理解。有一种极小化的 BrainFuck 计算机语言,它的工作模式和图灵机非常相似。BrainFuck 由 Urban Müller 在 1993 年创建的,简称为 BF 语言。Müller 最初的设计目标是建立一种简单的、可以用最小的编译器来实现的、符合图灵完全思想的编程语言。这种语言由八种状态构成,早期为 Amiga 机器编写的编译器(第二版)只有 240 个字节大小!
就象它的名字所暗示的,brainfuck 程序很难读懂。尽管如此,brainfuck 图灵机一样可以完成任何计算任务。虽然 brainfuck 的计算方式如此与众不同,但它确实能够正确运行。这种语言基于一个简单的机器模型,除了指令,这个机器还包括:一个以字节为单位、被初始化为零的数组、一个指向该数组的指针(初始时指向数组的第一个字节)、以及用于输入输出的两个字节流。这是一种按照图灵完备的语言,它的主要设计思路是:用最小的概念实现一种 “简单” 的语言。BrainFuck 语言只有八种符号,所有的操作都由这八种符号的组合来完成。
下面是这八种状态的描述,其中每个状态由一个字符标识:
| 字符 | C 语言类比 | 含义 |
| --- | ----------------- | ------ |
| `>` | `++ptr;` | 指针加一 |
| `<` | `--ptr;` | 指针减一 |
| `+` | `++*ptr;` | 指针指向的字节的值加一 |
| `-` | `--*ptr;` | 指针指向的字节的值减一 |
| `.` | `putchar(*ptr);` | 输出指针指向的单元内容(ASCⅡ 码) |
| `,` | `*ptr = getch();` | 输入内容到指针指向的单元(ASCⅡ 码) |
| `[` | `while(*ptr) {}` | 如果指针指向的单元值为零,向后跳转到对应的 `]` 指令的次一指令处 |
| `]` | | 如果指针指向的单元值不为零,向前跳转到对应的 `[` 指令的次一指令处 |
下面是一个 brainfuck 程序,向标准输出打印 "hi" 字符串:
```
++++++++++[>++++++++++<-]>++++.+.
```
理论上我们可以将 BF 语言当作目标机器语言,将其它高级语言编译为 BF 语言后就可以在 BF 机器上运行了。
## 3.2.2 人力资源机器游戏
《人力资源机器》(Human Resource Machine)是一款设计精良汇编语言编程游戏。在游戏中,玩家扮演一个职员角色,来模拟人力资源机器的运行。通过完成上司给的每一份任务来实现晋升的目标,完成任务的途径就是用游戏提供的 11 个机器指令编写正确的汇编程序,最终得到正确的输出结果。人力资源机器的汇编语言可以认为是跨平台、跨操作系统的通用的汇编语言,因为在 macOS、Windows、Linux 和 iOS 上该游戏的玩法都是完全一致的。
人力资源机器的机器模型非常简单:`INBOX` 命令对应输入设备,`OUTBOX` 对应输出设备,玩家小人对应一个寄存器,临时存放数据的地板对应内存,然后是数据传输、加减、跳转等基本的指令。总共有 11 个机器指令:
| 名称 | 解释 |
| -------- | --- |
| `INBOX` | 从输入通道取一个整数数据,放到手中 (寄存器) |
| `OUTBOX` | 将手中(寄存器)的数据放到输出通道,然后手中将没有数据(此时有些指令不能运行) |
| `COPYFROM` | 将地板上某个编号的格子中的数据复制到手中(手中之前的数据作废),地板格子必须有数据 |
| `COPYTO` | 将手中(寄存器)的数据复制到地板上某个编号的格子中,手中的数据不变 |
| `ADD` | 将手中(寄存器)的数据和某个编号对应的地板格子的数据相加,新数据放到手中(手中之前的数据作废) |
| `SUB` | 将手中(寄存器)的数据和某个编号对应的地板格子的数据相减,新数据放到手中(手中之前的数据作废) |
| `BUMP+` | 自加一 |
| `BUMP-` | 自减一 |
| `JUMP` | 跳转 |
| `JUMP =0` | 为零条件跳转 |
| `JUMP <0` | 为负条件跳转 |
除了机器指令外,游戏中有些环节还提供类似寄存器的场所,用于存放临时的数据。人力资源机器游戏的机器指令主要分为以下几类:
- 输入/输出 (`INBOX`/`OUTBOX`): 输入后手中将只有 1 份新拿到的数据, 输出后手中将没有数据。
- 数据传输指令 (`COPYFROM`/`COPYTO`): 主要用于仅有的 1 个寄存器(手中)和内存之间的数据传输,传输时要确保源数据是有效的
- 算术相关 (`ADD`/`SUB`/`BUMP+`/`BUMP-`)
- 跳转指令: 如果是条件跳转,寄存器中必须要有数据
主流的处理器也有类似的指令。除了基本的算术和逻辑运算指令外,再配合有条件跳转指令就可以实现分支、循环等常见控制流结构了。
下图是某一层的任务:将输入数据的 0 剔除,非 0 的数据依次输出,右边部分是解决方案。

*图 3-1 人力资源机器*
整个程序只有一个输入指令、一个输出指令和两个跳转指令共四个指令:
```
LOOP:
INBOX
JUMP-if-zero LOOP
OUTBOX
JUMP LOOP
```
首先通过 `INBOX` 指令读取一个数据包;然后判断包裹的数据是否为 `0`,如果是 `0` 的话就跳转到开头继续读取下一个数据包;否则将输出数据包,然后再跳转到开头。以此循环无休止地处理数据包裹,直到任务完成晋升到更高一级的岗位,然后处理类似的但更复杂的任务。
## 3.2.3 X86-64 体系结构
X86 其实是是 80X86 的简称(后面三个字母),包括 Intel 8086、80286、80386 以及 80486 等指令集合,因此其架构被称为 x86 架构。x86-64 是 AMD 公司于 1999 年设计的 x86 架构的 64 位拓展,向后兼容于 16 位及 32 位的 x86 架构。X86-64 目前正式名称为 AMD64,也就是 Go 语言中 GOARCH 环境变量指定的 AMD64。如果没有特殊说明的话,本章中的汇编程序都是针对 64 位的 X86-64 环境。
在使用汇编语言之前必须要了解对应的 CPU 体系结构。下面是 X86/AMD 架构图:

*图 3-2 AMD64 架构*
左边是内存部分是常见的内存布局。其中 text 一般对应代码段,用于存储要执行指令数据,代码段一般是只读的。然后是 rodata 和 data 数据段,数据段一般用于存放全局的数据,其中 rodata 是只读的数据段。而 heap 段则用于管理动态的数据,stack 段用于管理每个函数调用时相关的数据。在汇编语言中一般重点关注 text 代码段和 data 数据段,因此 Go 汇编语言中专门提供了对应 TEXT 和 DATA 命令用于定义代码和数据。
中间是 X86 提供的寄存器。寄存器是 CPU 中最重要的资源,每个要处理的内存数据原则上需要先放到寄存器中才能由 CPU 处理,同时寄存器中处理完的结果需要再存入内存。X86 中除了状态寄存器 FLAGS 和指令寄存器 IP 两个特殊的寄存器外,还有 AX、BX、CX、DX、SI、DI、BP、SP 几个通用寄存器。在 X86-64 中又增加了八个以 R8-R15 方式命名的通用寄存器。因为历史的原因 R0-R7 并不是通用寄存器,它们只是 X87 开始引入的 MMX 指令专有的寄存器。在通用寄存器中 BP 和 SP 是两个比较特殊的寄存器:其中 BP 用于记录当前函数帧的开始位置,和函数调用相关的指令会隐式地影响 BP 的值;SP 则对应当前栈指针的位置,和栈相关的指令会隐式地影响 SP 的值;而某些调试工具需要 BP 寄存器才能正常工作。
右边是 X86 的指令集。CPU 是由指令和寄存器组成,指令是每个 CPU 内置的算法,指令处理的对象就是全部的寄存器和内存。我们可以将每个指令看作是 CPU 内置标准库中提供的一个个函数,然后基于这些函数构造更复杂的程序的过程就是用汇编语言编程的过程。
## 3.2.4 Go 汇编中的伪寄存器
Go 汇编为了简化汇编代码的编写,引入了 PC、FP、SP、SB 四个伪寄存器。四个伪寄存器加其它的通用寄存器就是 Go 汇编语言对 CPU 的重新抽象,该抽象的结构也适用于其它非 X86 类型的体系结构。
四个伪寄存器和 X86/AMD64 的内存和寄存器的相互关系如下图:

*图 3-3 Go 汇编的伪寄存器*
在 AMD64 环境,伪 PC 寄存器其实是 IP 指令计数器寄存器的别名。伪 FP 寄存器对应的是函数的帧指针,一般用来访问函数的参数和返回值。伪 SP 栈指针对应的是当前函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪 SP 是一个比较特殊的寄存器,因为还存在一个同名的 SP 真寄存器。真 SP 寄存器对应的是栈的顶部,一般用于定位调用其它函数的参数和返回值。
当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如 `(SP)`、`+8(SP)` 没有标识符前缀为真 SP 寄存器,而 `a(SP)`、`b+8(SP)` 有标识符为前缀表示伪寄存器。
## 3.2.5 X86-64 指令集
很多汇编语言的教程都会强调汇编语言是不可移植的。严格来说汇编语言是在不同的 CPU 类型、或不同的操作系统环境、或不同的汇编工具链下是不可移植的,而在同一种 CPU 中运行的机器指令是完全一样的。汇编语言这种不可移植性正是其普及的一个极大的障碍。虽然 CPU 指令集的差异是导致不好移植的较大因素,但是汇编语言的相关工具链对此也有不可推卸的责任。而源自 Plan9 的 Go 汇编语言对此做了一定的改进:首先 Go 汇编语言在相同 CPU 架构上是完全一致的,也就是屏蔽了操作系统的差异;同时 Go 汇编语言将一些基础并且类似的指令抽象为相同名字的伪指令,从而减少不同 CPU 架构下汇编代码的差异(寄存器名字和数量的差异是一直存在的)。本节的目的也是找出一个较小的精简指令集,以简化 Go 汇编语言的学习。
X86 是一个极其复杂的系统,有人统计 x86-64 中指令有将近一千个之多。不仅仅如此,X86 中的很多单个指令的功能也非常强大,比如有论文证明了仅仅一个 `MOV` 指令就可以构成一个图灵完备的系统。以上这是两种极端情况,太多的指令和太少的指令都不利于汇编程序的编写,但是也从侧面体现了 `MOV` 指令的重要性。
通用的基础机器指令大概可以分为数据传输指令、算术运算和逻辑运算指令、控制流指令和其它指令等几类。因此我们可以尝试精简出一个 X86-64 指令集,以便于 Go 汇编语言的学习。
因此我们先看看重要的 MOV 指令。其中 MOV 指令可以用于将字面值移动到寄存器、字面值移到内存、寄存器之间的数据传输、寄存器和内存之间的数据传输。需要注意的是,MOV 传输指令的内存操作数只能有一个,可以通过某个临时寄存器达到类似目的。最简单的是忽略符号位的数据传输操作,386 和 AMD64 指令一样,不同的 1、2、4 和 8 字节宽度有不同的指令:
| Data Type | 386/AMD64 | Comment |
| ----------- | ----------- | ------------- |
| `[1]byte` | MOVB | B => Byte |
| `[2]byte` | MOVW | W => Word |
| `[4]byte` | MOVL | L => Long |
| `[8]byte` | MOVQ | Q => Quadword |
MOV 指令它不仅仅用于在寄存器和内存之间传输数据,而且还可以用于处理数据的扩展和截断操作。当数据宽度和寄存器的宽度不同又需要处理符号位时,386 和 AMD64 有各自不同的指令:
| Data Type | 386 | AMD64 | Comment |
| --------- | ------- | ------- | ------------- |
| `int8` | MOVBLSX | MOVBQSX | sign extend |
| `uint8` | MOVBLZX | MOVBQZX | zero extend |
| `int16` | MOVWLSX | MOVWQSX | sign extend |
| `uint16` | MOVWLZX | MOVWQZX | zero extend |
比如当需要将一个 `int64` 类型的数据转为 `bool` 类型时,则需要使用 `MOVBQZX` 指令处理。
基础算术指令有 `ADD`、`SUB`、`MUL`、`DIV` 等指令。其中 `ADD`、`SUB`、`MUL`、`DIV` 用于加、减、乘、除运算,最终结果存入目标寄存器。基础的逻辑运算指令有 `AND`、`OR` 和 `NOT` 等几个指令,对应逻辑与、或和取反等几个指令。
| 名称 | 解释 |
| ------ | --- |
| `ADD` | 加法 |
| `SUB` | 减法 |
| `MUL` | 乘法 |
| `DIV` | 除法 |
| `AND` | 逻辑与 |
| `OR` | 逻辑或 |
| `NOT` | 逻辑取反 |
其中算术和逻辑指令是顺序编程的基础。通过逻辑比较影响状态寄存器,再结合有条件跳转指令就可以实现更复杂的分支或循环结构。需要注意的是 `MUL` 和 `DIV` 等乘除法指令可能隐含使用了某些寄存器,指令细节请查阅相关手册。
控制流指令有 `CMP`、`JMP-if-x`、`JMP`、`CALL`、`RET` 等指令。`CMP` 指令用于两个操作数做减法,根据比较结果设置状态寄存器的符号位和零位,可以用于有条件跳转的跳转条件。`JMP-if-x` 是一组有条件跳转指令,常用的有 `JL`、`JLZ`、`JE`、`JNE`、`JG`、`JGE` 等指令,对应小于、小于等于、等于、不等于、大于和大于等于等条件时跳转。`JMP` 指令则对应无条件跳转,将要跳转的地址设置到 IP 指令寄存器就实现了跳转。而 `CALL` 和 `RET` 指令分别为调用函数和函数返回指令。
| 名称 | 解释 |
| -------- | --- |
| `JMP` | 无条件跳转 |
| `JMP-if-x` | 有条件跳转,`JL`、`JLZ`、`JE`、`JNE`、`JG`、`JGE` |
| `CALL` | 调用函数 |
| `RET` | 函数返回 |
无条件和有条件调整指令是实现分支和循环控制流的基础指令。理论上,我们也可以通过跳转指令实现函数的调用和返回功能。不过因为目前函数已经是现代计算机中的一个最基础的抽象,因此大部分的 CPU 都针对函数的调用和返回提供了专有的指令和寄存器。
其它比较重要的指令有 `LEA`、`PUSH`、`POP` 等几个。其中 LEA 指令将标准参数格式中的内存地址加载到寄存器(而不是加载内存位置的内容)。`PUSH` 和 `POP` 分别是压栈和出栈指令,通用寄存器中的 `SP` 为栈指针,栈是向低地址方向增长的。
| 名称 | 解释 |
| ---- | ------ |
| `LEA` | 取地址 |
| `PUSH` | 压栈 |
| `POP` | 出栈 |
当需要通过间接索引的方式访问数组或结构体等某些成员对应的内存时,可以用 LEA 指令先对目前内存取地址,然后在操作对应内存的数据。而栈指令则可以用于函数调整自己的栈空间大小。
最后需要说明的是,Go 汇编语言可能并没有支持全部的 CPU 指令。如果遇到没有支持的 CPU 指令,可以通过 Go 汇编语言提供的 BYTE 命令将真实的 CPU 指令对应的机器码填充到对应的位置。完整的 X86 指令在 [https://github.com/golang/arch/blob/master/x86/x86.csv](https://github.com/golang/arch/blob/master/x86/x86.csv) 文件定义。同时 Go 汇编还正对一些指令定义了别名,具体可以参考这里 [https://golang.org/src/cmd/internal/obj/x86/anames.go](https://golang.org/src/cmd/internal/obj/x86/anames.go)。
================================================
FILE: ch3-asm/ch3-03-const-and-var.md
================================================
# 3.3 常量和全局变量
程序中的一切变量的初始值都直接或间接地依赖常量或常量表达式生成。在 Go 语言中很多变量是默认零值初始化的,但是 Go 汇编中定义的变量最好还是手工通过常量初始化。有了常量之后,就可以衍生定义全局变量,并使用常量组成的表达式初始化其它各种变量。本节将简单讨论 Go 汇编语言中常量和全局变量的用法。
## 3.3.1 常量
Go 汇编语言中常量以 $ 美元符号为前缀。常量的类型有整数常量、浮点数常量、字符常量和字符串常量等几种类型。以下是几种类型常量的例子:
```
$1 // 十进制
$0xf4f8fcff // 十六进制
$1.5 // 浮点数
$'a' // 字符
$"abcd" // 字符串
```
其中整数类型常量默认是十进制格式,也可以用十六进制格式表示整数常量。所有的常量最终都必须和要初始化的变量内存大小匹配。
对于数值型常量,可以通过常量表达式构成新的常量:
```
$2+2 // 常量表达式
$3&1<<2 // == $4
$(3&1)<<2 // == $4
```
其中常量表达式中运算符的优先级和 Go 语言保持一致。
Go 汇编语言中的常量其实不仅仅只有编译时常量,还包含运行时常量。比如包中全局的变量和全局函数在运行时地址也是固定不变的,这里地址不会改变的包变量和函数的地址也是一种汇编常量。
下面是本章第一节用汇编定义的字符串代码:
```
GLOBL ·NameData(SB),$8
DATA ·NameData(SB)/8,$"gopher"
GLOBL ·Name(SB),$16
DATA ·Name+0(SB)/8,$·NameData(SB)
DATA ·Name+8(SB)/8,$6
```
其中 `$·NameData(SB)` 也是以 $ 美元符号为前缀,因此也可以将它看作是一个常量,它对应的是 NameData 包变量的地址。在汇编指令中,我们也可以通过 LEA 指令来获取 NameData 变量的地址。
## 3.3.2 全局变量
在 Go 语言中,变量根据作用域和生命周期有全局变量和局部变量之分。全局变量是包一级的变量,全局变量一般有着较为固定的内存地址,生命周期跨越整个程序运行时间。而局部变量一般是函数内定义的的变量,只有在函数被执行的时间才被在栈上创建,当函数调用完成后将回收(暂时不考虑闭包对局部变量捕获的问题)。
从 Go 汇编语言角度来看,全局变量和局部变量有着非常大的差异。在 Go 汇编中全局变量和全局函数更为相似,都是通过一个人为定义的符号来引用对应的内存,区别只是内存中存放是数据还是要执行的指令。因为在冯诺伊曼系统结构的计算机中指令也是数据,而且指令和数据存放在统一编址的内存中。因为指令和数据并没有本质的差别,因此我们甚至可以像操作数据那样动态生成指令(这是所有 JIT 技术的原理)。而局部变量则需在了解了汇编函数之后,才能通过 SP 栈空间来隐式定义。
在 Go 汇编语言中,内存是通过 SB 伪寄存器定位。SB 是 Static base pointer 的缩写,意为静态内存的开始地址。我们可以将 SB 想象为一个和内容容量有相同大小的字节数组,所有的静态全局符号通常可以通过 SB 加一个偏移量定位,而我们定义的符号其实就是相对于 SB 内存开始地址偏移量。对于 SB 伪寄存器,全局变量和全局函数的符号并没有任何区别。
要定义全局变量,首先要声明一个变量对应的符号,以及变量对应的内存大小。导出变量符号的语法如下:
```
GLOBL symbol(SB), width
```
GLOBL 汇编指令用于定义名为 symbol 的变量,变量对应的内存宽度为 width,内存宽度部分必须用常量初始化。下面的代码通过汇编定义一个 int32 类型的 count 变量:
```
GLOBL ·count(SB),$4
```
其中符号 `·count` 以中点开头表示是当前包的变量,最终符号名为被展开为 `path/to/pkg.count`。count 变量的大小是 4 个字节,常量必须以 $ 美元符号开头。内存的宽度必须是 2 的指数倍,编译器最终会保证变量的真实地址对齐到机器字倍数。需要注意的是,在 Go 汇编中我们无法为 count 变量指定具体的类型。在汇编中定义全局变量时,我们只关心变量的名字和内存大小,变量最终的类型只能在 Go 语言中声明。
变量定义之后,我们可以通过 DATA 汇编指令指定对应内存中的数据,语法如下:
```
DATA symbol+offset(SB)/width, value
```
具体的含义是从 symbol+offset 偏移量开始,width 宽度的内存,用 value 常量对应的值初始化。DATA 初始化内存时,width 必须是 1、2、4、8 几个宽度之一,因为再大的内存无法一次性用一个 uint64 大小的值表示。
对于 int32 类型的 count 变量来说,我们既可以逐个字节初始化,也可以一次性初始化:
```
DATA ·count+0(SB)/1,$1
DATA ·count+1(SB)/1,$2
DATA ·count+2(SB)/1,$3
DATA ·count+3(SB)/1,$4
// or
DATA ·count+0(SB)/4,$0x04030201
```
因为 X86 处理器是小端序,因此用十六进制 0x04030201 初始化全部的 4 个字节,和用 1、2、3、4 逐个初始化 4 个字节是一样的效果。
最后还需要在 Go 语言中声明对应的变量(和 C 语言头文件声明变量的作用类似),这样垃圾回收器会根据变量的类型来管理其中的指针相关的内存数据。
### 3.3.2.1 数组类型
汇编中数组也是一种非常简单的类型。Go 语言中数组是一种有着扁平内存结构的基础类型。因此 `[2]byte` 类型和 `[1]uint16` 类型有着相同的内存结构。只有当数组和结构体结合之后情况才会变的稍微复杂。
下面我们尝试用汇编定义一个 `[2]int` 类型的数组变量 num:
```go
var num [2]int
```
然后在汇编中定义一个对应 16 字节大小的变量,并用零值进行初始化:
```
GLOBL ·num(SB),$16
DATA ·num+0(SB)/8,$0
DATA ·num+8(SB)/8,$0
```
下图是 Go 语句和汇编语句定义变量时的对应关系:

*图 3-4 变量定义*
汇编代码中并不需要 NOPTR 标志,因为 Go 编译器会从 Go 语言语句声明的 `[2]int` 类型中推导出该变量内部没有指针数据。
### 3.3.2.2 bool 型变量
Go 汇编语言定义变量无法指定类型信息,因此需要先通过 Go 语言声明变量的类型。以下是在 Go 语言中声明的几个 bool 类型变量:
```go
var (
boolValue bool
trueValue bool
falseValue bool
)
```
在 Go 语言中声明的变量不能含有初始化语句。然后下面是 amd64 环境的汇编定义:
```
GLOBL ·boolValue(SB),$1 // 未初始化
GLOBL ·trueValue(SB),$1 // var trueValue = true
DATA ·trueValue(SB)/1,$1 // 非 0 均为 true
GLOBL ·falseValue(SB),$1 // var falseValue = false
DATA ·falseValue(SB)/1,$0
```
bool 类型的内存大小为 1 个字节。并且汇编中定义的变量需要手工指定初始化值,否则将可能导致产生未初始化的变量。当需要将 1 个字节的 bool 类型变量加载到 8 字节的寄存器时,需要使用 MOVBQZX 指令将不足的高位用 0 填充。
### 3.3.2.3 int 型变量
所有的整数类型均有类似的定义的方式,比较大的差异是整数类型的内存大小和整数是否是有符号。下面是声明的 int32 和 uint32 类型变量:
```go
var int32Value int32
var uint32Value uint32
```
在 Go 语言中声明的变量不能含有初始化语句。然后下面是 amd64 环境的汇编定义:
```
GLOBL ·int32Value(SB),$4
DATA ·int32Value+0(SB)/1,$0x01 // 第 0 字节
DATA ·int32Value+1(SB)/1,$0x02 // 第 1 字节
DATA ·int32Value+2(SB)/2,$0x03 // 第 3-4 字节
GLOBL ·uint32Value(SB),$4
DATA ·uint32Value(SB)/4,$0x01020304 // 第 1-4 字节
```
汇编定义变量时初始化数据并不区分整数是否有符号。只有在 CPU 指令处理该寄存器数据时,才会根据指令的类型来取分数据的类型或者是否带有符号位。
### 3.3.2.4 float 型变量
Go 汇编语言通常无法区分变量是否是浮点数类型,与之相关的浮点数机器指令会将变量当作浮点数处理。Go 语言的浮点数遵循 IEEE754 标准,有 float32 单精度浮点数和 float64 双精度浮点数之分。
IEEE754 标准中,最高位 1bit 为符号位,然后是指数位(指数为采用移码格式表示),然后是有效数部分(其中小数点左边的一个 bit 位被省略)。下图是 IEEE754 中 float32 类型浮点数的 bit 布局:

*图 3-5 IEEE754 浮点数结构*
IEEE754 浮点数还有一些奇妙的特性:比如有正负两个 0;除了无穷大和无穷小 Inf 还有非数 NaN;同时如果两个浮点数有序那么对应的有符号整数也是有序的(反之则不一定成立,因为浮点数中存在的非数是不可排序的)。浮点数是程序中最难琢磨的角落,因为程序中很多手写的浮点数字面值常量根本无法精确表达,浮点数计算涉及到的误差舍入方式可能也的随机的。
下面是在 Go 语言中声明两个浮点数(如果没有在汇编中定义变量,那么声明的同时也会定义变量)。
```go
var float32Value float32
var float64Value float64
```
然后在汇编中定义并初始化上面声明的两个浮点数:
```
GLOBL ·float32Value(SB),$4
DATA ·float32Value+0(SB)/4,$1.5 // var float32Value = 1.5
GLOBL ·float64Value(SB),$8
DATA ·float64Value(SB)/8,$0x01020304 // bit 方式初始化
```
我们在上一节精简的算术指令中都是针对整数,如果要通过整数指令处理浮点数的加减法必须根据浮点数的运算规则进行:先对齐小数点,然后进行整数加减法,最后再对结果进行归一化并处理精度舍入问题。不过在目前的主流 CPU 中,都针对浮点数提供了专有的计算指令。
### 3.3.2.5 string 类型变量
从 Go 汇编语言角度看,字符串只是一种结构体。string 的头结构定义如下:
```go
type reflect.StringHeader struct {
Data uintptr
Len int
}
```
在 amd64 环境中 StringHeader 有 16 个字节大小,因此我们先在 Go 代码声明字符串变量,然后在汇编中定义一个 16 字节大小的变量:
```go
var helloworld string
```
```
GLOBL ·helloworld(SB),$16
```
同时我们可以为字符串准备真正的数据。在下面的汇编代码中,我们定义了一个 text 当前文件内的私有变量(以 `<>` 为后缀名),内容为 “Hello World!”:
```
GLOBL text<>(SB),NOPTR,$16
DATA text<>+0(SB)/8,$"Hello Wo"
DATA text<>+8(SB)/8,$"rld!"
```
虽然 `text<>` 私有变量表示的字符串只有 12 个字符长度,但是我们依然需要将变量的长度扩展为 2 的指数倍数,这里也就是 16 个字节的长度。其中 `NOPTR` 表示 `text<>` 不包含指针数据。
然后使用 text 私有变量对应的内存地址对应的常量来初始化字符串头结构体中的 Data 部分,并且手工指定 Len 部分为字符串的长度:
```
DATA ·helloworld+0(SB)/8,$text<>(SB) // StringHeader.Data
DATA ·helloworld+8(SB)/8,$12 // StringHeader.Len
```
需要注意的是,字符串是只读类型,要避免在汇编中直接修改字符串底层数据的内容。
### 3.3.2.6 slice 类型变量
slice 变量和 string 变量相似,只不过是对应的是切片头结构体而已。切片头的结构如下:
```go
type reflect.SliceHeader struct {
Data uintptr
Len int
Cap int
}
```
对比可以发现,切片的头的前 2 个成员字符串是一样的。因此我们可以在前面字符串变量的基础上,再扩展一个 Cap 成员就成了切片类型了:
```go
var helloworld []byte
```
```
GLOBL ·helloworld(SB),$24 // var helloworld []byte("Hello World!")
DATA ·helloworld+0(SB)/8,$text<>(SB) // SliceHeader.Data
DATA ·helloworld+8(SB)/8,$12 // SliceHeader.Len
DATA ·helloworld+16(SB)/8,$16 // SliceHeader.Cap
GLOBL text<>(SB),$16
DATA text<>+0(SB)/8,$"Hello Wo" // ...string data...
DATA text<>+8(SB)/8,$"rld!" // ...string data...
```
因为切片和字符串的相容性,我们可以将切片头的前 16 个字节临时作为字符串使用,这样可以省去不必要的转换。
### 3.3.2.7 map/channel 类型变量
map/channel 等类型并没有公开的内部结构,它们只是一种未知类型的指针,无法直接初始化。在汇编代码中我们只能为类似变量定义并进行 0 值初始化:
```go
var m map[string]int
var ch chan int
```
```
GLOBL ·m(SB),$8 // var m map[string]int
DATA ·m+0(SB)/8,$0
GLOBL ·ch(SB),$8 // var ch chan int
DATA ·ch+0(SB)/8,$0
```
其实在 runtime 包中为汇编提供了一些辅助函数。比如在汇编中可以通过 runtime.makemap 和 runtime.makechan 内部函数来创建 map 和 chan 变量。辅助函数的签名如下:
```go
func makemap(mapType *byte, hint int, mapbuf *any) (hmap map[any]any)
func makechan(chanType *byte, size int) (hchan chan any)
```
需要注意的是,makemap 是一种泛型函数,可以创建不同类型的 map,map 的具体类型是通过 mapType 参数指定。
## 3.3.3 变量的内存布局
我们已经多次强调,在 Go 汇编语言中变量是没有类型的。因此在 Go 语言中有着不同类型的变量,底层可能对应的是相同的内存结构。深刻理解每个变量的内存布局是汇编编程时的必备条件。
首先查看前面已经见过的 `[2]int` 类型数组的内存布局:

*图 3-6 变量定义*
变量在 data 段分配空间,数组的元素地址依次从低向高排列。
然后再查看下标准库图像包中 `image.Point` 结构体类型变量的内存布局:

*图 3-7 结构体变量定义*
变量也是在 data 段分配空间,变量结构体成员的地址也是依次从低向高排列。
因此 `[2]int` 和 `image.Point` 类型底层有着近似相同的内存布局。
## 3.3.4 标识符规则和特殊标志
Go 语言的标识符可以由绝对的包路径加标识符本身定位,因此不同包中的标识符即使同名也不会有问题。Go 汇编是通过特殊的符号来表示斜杠和点符号,因为这样可以简化汇编器词法扫描部分代码的编写,只要通过字符串替换就可以了。
下面是汇编中常见的几种标识符的使用方式(通常也适用于函数标识符):
```
GLOBL ·pkg_name1(SB),$1
GLOBL main·pkg_name2(SB),$1
GLOBL my/pkg·pkg_name(SB),$1
```
此外,Go 汇编中可以定义仅当前文件可以访问的私有标识符(类似 C 语言中文件内 static 修饰的变量),以 `<>` 为后缀名
gitextract_23g4on1p/ ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE.md │ └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .nojekyll ├── LICENSE ├── Makefile ├── README.md ├── SUMMARY.md ├── appendix/ │ ├── appendix-a-trap.md │ ├── appendix-b-gems.md │ ├── appendix-c-author.md │ └── readme.md ├── book.ini ├── ch1-basic/ │ ├── ch1-01-genesis.md │ ├── ch1-02-hello-revolution.md │ ├── ch1-03-array-string-and-slice.md │ ├── ch1-04-func-method-interface.md │ ├── ch1-05-mem.md │ ├── ch1-06-goroutine.md │ ├── ch1-07-error-and-panic.md │ ├── ch1-08-ext.md │ └── readme.md ├── ch2-cgo/ │ ├── ch2-01-hello-cgo.md │ ├── ch2-02-basic.md │ ├── ch2-03-cgo-types.md │ ├── ch2-04-func.md │ ├── ch2-05-internal.md │ ├── ch2-06-qsort.md │ ├── ch2-07-memory.md │ ├── ch2-08-class.md │ ├── ch2-09-static-shared-lib.md │ ├── ch2-10-link.md │ ├── ch2-11-ext.md │ └── readme.md ├── ch3-asm/ │ ├── ch3-01-basic.md │ ├── ch3-02-arch.md │ ├── ch3-03-const-and-var.md │ ├── ch3-04-func.md │ ├── ch3-05-control-flow.md │ ├── ch3-06-func-again.md │ ├── ch3-07-hack-asm.md │ ├── ch3-08-goroutine-id.md │ ├── ch3-09-debug.md │ ├── ch3-10-ext.md │ └── readme.md ├── ch4-rpc/ │ ├── ch4-01-rpc-intro.md │ ├── ch4-02-pb-intro.md │ ├── ch4-03-netrpc-hack.md │ ├── ch4-04-grpc.md │ ├── ch4-05-grpc-hack.md │ ├── ch4-06-grpc-ext.md │ ├── ch4-07-pbgo.md │ ├── ch4-08-grpcurl.md │ ├── ch4-09-ext.md │ └── readme.md ├── ch5-web/ │ ├── ch5-01-introduction.md │ ├── ch5-02-router.md │ ├── ch5-03-middleware.md │ ├── ch5-04-validator.md │ ├── ch5-05-database.md │ ├── ch5-06-ratelimit.md │ ├── ch5-07-layout-of-web-project.md │ ├── ch5-08-interface-and-web.md │ ├── ch5-09-gated-launch.md │ ├── ch5-10-ext.md │ └── readme.md ├── ch6-cloud/ │ ├── ch6-01-dist-id.md │ ├── ch6-02-lock.md │ ├── ch6-03-delay-job.md │ ├── ch6-04-search-engine.md │ ├── ch6-05-load-balance.md │ ├── ch6-06-config.md │ ├── ch6-07-crawler.md │ ├── ch6-08-ext.md │ └── readme.md ├── chaoxi/ │ ├── .gitignore │ └── .keep ├── contributors.json ├── doc.go ├── docs/ │ └── .gitignore ├── errata/ │ ├── README.md │ ├── a.go │ └── a_amd64.s ├── examples/ │ ├── README.md │ ├── ch1.1/ │ │ └── 1-hello/ │ │ └── main.go │ ├── ch1.2/ │ │ ├── 1-hello-b-1972/ │ │ │ └── main.b │ │ ├── 10-hello-go-200806/ │ │ │ └── hello.go.txt │ │ ├── 11-hello-go-20080627/ │ │ │ └── hello.go.txt │ │ ├── 12-hello-go-20080811/ │ │ │ └── hello.go.txt │ │ ├── 13-hello-go-20081024/ │ │ │ └── hello.go.txt │ │ ├── 14-hello-go-20090115/ │ │ │ └── hello.go.txt │ │ ├── 15-hello-go-20091211/ │ │ │ └── hello.go │ │ ├── 16-hello-go-v2/ │ │ │ └── hello.go │ │ ├── 2-hello-c-1974/ │ │ │ └── hello-c-01.c │ │ ├── 3-hello-c-1978/ │ │ │ └── hello-c-02.c │ │ ├── 4-hello-c-1988/ │ │ │ └── hello-c-03.c │ │ ├── 5-hello-c-1989/ │ │ │ └── hello-c-04.c │ │ ├── 6-hello-newsqueak-1989/ │ │ │ └── hello.newsqueak │ │ ├── 7-prime-newsqueak/ │ │ │ └── prime.newsqueak │ │ ├── 8-hello-alef-1993/ │ │ │ └── hello.alef │ │ ├── 9-hello-limbo-1995/ │ │ │ └── hello.limbo │ │ ├── xx-hello-go-asm/ │ │ │ ├── hello.go │ │ │ └── hello_amd64.s │ │ ├── xx-hello-go-cgo/ │ │ │ └── hello.go │ │ └── xx-hello-go-swig/ │ │ ├── hello.cc │ │ ├── hello.go │ │ └── hello.swigcxx │ ├── ch2.1/ │ │ ├── hello-01/ │ │ │ └── main.go │ │ ├── hello-02/ │ │ │ └── main.go │ │ ├── hello-03/ │ │ │ ├── hello.c │ │ │ └── main.go │ │ ├── hello-04/ │ │ │ └── main.go │ │ ├── hello-05/ │ │ │ └── main.go │ │ └── hello-06/ │ │ └── main.go │ ├── ch2.10/ │ │ ├── hello-py/ │ │ │ ├── Makefile │ │ │ ├── gopkg.h │ │ │ ├── main.go │ │ │ └── py3-config.go │ │ └── hello-so/ │ │ ├── Makefile │ │ ├── _test_so.c │ │ ├── hello.py │ │ ├── main.go │ │ └── say-hello.h │ ├── ch2.4/ │ │ └── return-go-ptr/ │ │ └── main.go │ ├── ch2.5/ │ │ ├── 01-cgo-gen-files/ │ │ │ ├── Makefile │ │ │ ├── _obj/ │ │ │ │ ├── _cgo_export.c │ │ │ │ ├── _cgo_export.h │ │ │ │ ├── _cgo_flags │ │ │ │ ├── _cgo_gotypes.go │ │ │ │ ├── _cgo_main.c │ │ │ │ ├── hello.cgo1.go │ │ │ │ ├── hello.cgo2.c │ │ │ │ ├── main.cgo1.go │ │ │ │ └── main.cgo2.c │ │ │ ├── hello.go │ │ │ ├── main.go │ │ │ ├── nocgo_1.go │ │ │ └── nocgo_x.go │ │ ├── 02-go-call-c-func/ │ │ │ ├── Makefile │ │ │ ├── _obj/ │ │ │ │ ├── _cgo_export.c │ │ │ │ ├── _cgo_export.h │ │ │ │ ├── _cgo_flags │ │ │ │ ├── _cgo_gotypes.go │ │ │ │ ├── _cgo_main.c │ │ │ │ ├── main.cgo1.go │ │ │ │ └── main.cgo2.c │ │ │ └── main.go │ │ └── 03-c-call-go-func/ │ │ ├── Makefile │ │ ├── _obj/ │ │ │ ├── _cgo_export.c │ │ │ ├── _cgo_export.h │ │ │ ├── _cgo_flags │ │ │ ├── _cgo_gotypes.go │ │ │ ├── _cgo_main.c │ │ │ ├── sum.cgo1.go │ │ │ └── sum.cgo2.c │ │ ├── main.c │ │ ├── sum.go │ │ └── sum.h │ ├── ch2.6/ │ │ ├── 01-qsort-v1/ │ │ │ ├── Makefile │ │ │ └── main.c │ │ ├── 02-qsort-v2/ │ │ │ ├── main.go │ │ │ ├── qsort.go │ │ │ ├── qsort_test.go │ │ │ └── test_helper.go │ │ ├── 03-qsort-v3/ │ │ │ ├── main.go │ │ │ ├── sort.go │ │ │ └── sort_test.go │ │ └── 04-qsort-v4/ │ │ ├── main.go │ │ ├── sort.go │ │ └── sort_test.go │ ├── ch2.8/ │ │ ├── class-cc2go/ │ │ │ ├── main.go │ │ │ ├── my_buffer.cc │ │ │ ├── my_buffer.go │ │ │ ├── my_buffer.h │ │ │ ├── my_buffer_capi.cc │ │ │ ├── my_buffer_capi.go │ │ │ └── my_buffer_capi.h │ │ └── class-go2cc/ │ │ ├── goobj.go │ │ ├── main.cc │ │ ├── main.go │ │ ├── persion.go │ │ ├── person.cc │ │ ├── person.h │ │ ├── person_capi.go │ │ └── person_capi.h │ ├── ch2.9/ │ │ ├── incorrect-dll-api/ │ │ │ ├── Makefile │ │ │ ├── main.go │ │ │ └── mystring/ │ │ │ ├── Makefile │ │ │ ├── mystring.c │ │ │ └── mystring.h │ │ ├── make-clib-dll/ │ │ │ ├── Makefile │ │ │ ├── _test_main.c │ │ │ ├── main.go │ │ │ ├── number-win64.def │ │ │ └── number.h │ │ ├── make-clib-from-multi-pkg/ │ │ │ ├── Makefile │ │ │ ├── _test_main.c │ │ │ ├── main.go │ │ │ ├── main.h │ │ │ └── number/ │ │ │ ├── number.go │ │ │ └── number.h │ │ ├── make-clib-shared/ │ │ │ ├── Makefile │ │ │ ├── _test_main.c │ │ │ ├── main.go │ │ │ └── number.h │ │ ├── make-clib-static/ │ │ │ ├── Makefile │ │ │ ├── _test_main.c │ │ │ ├── main.go │ │ │ └── number.h │ │ ├── plugin/ │ │ │ ├── Makefile │ │ │ ├── main.go │ │ │ └── plugin.go │ │ ├── use-clib-shared/ │ │ │ ├── Makefile │ │ │ ├── main.go │ │ │ └── number/ │ │ │ ├── Makefile │ │ │ ├── number.c │ │ │ └── number.h │ │ ├── use-clib-static-v1/ │ │ │ ├── Makefile │ │ │ ├── main.go │ │ │ └── number/ │ │ │ ├── Makefile │ │ │ ├── number.c │ │ │ └── number.h │ │ └── use-clib-static-v2/ │ │ ├── Makefile │ │ ├── main.go │ │ ├── number/ │ │ │ ├── Makefile │ │ │ ├── number.c │ │ │ └── number.h │ │ └── z_link_number_c.c │ ├── ch2.x/ │ │ ├── hello/ │ │ │ ├── .gitignore │ │ │ ├── Makefile │ │ │ ├── _obj/ │ │ │ │ ├── _cgo_export.c │ │ │ │ ├── _cgo_export.h │ │ │ │ ├── _cgo_flags │ │ │ │ ├── _cgo_gotypes.go │ │ │ │ ├── _cgo_main.c │ │ │ │ ├── hello.cgo1.go │ │ │ │ └── hello.cgo2.c │ │ │ └── hello.go │ │ ├── hello-swig-v1/ │ │ │ ├── Makefile │ │ │ ├── hello.cc │ │ │ ├── hello.swigcxx │ │ │ ├── hello_test.go │ │ │ └── runme.go │ │ └── hello-swig-v2/ │ │ ├── Makefile │ │ ├── hello.cc │ │ ├── hello.go │ │ ├── hello.i │ │ ├── runme.go │ │ └── swig_wrap.cc │ ├── ch3.1/ │ │ ├── id-01/ │ │ │ ├── pkg.go │ │ │ └── runme.go │ │ ├── id-02/ │ │ │ ├── pkg.go │ │ │ ├── pkg_amd64.s │ │ │ └── runme.go │ │ ├── main-01/ │ │ │ ├── Makefile │ │ │ ├── main.go │ │ │ └── main_amd64.s │ │ ├── str-01/ │ │ │ └── pkg.go │ │ ├── str-02/ │ │ │ ├── pkg.go │ │ │ ├── pkg_amd64.s │ │ │ └── runme.go │ │ └── str-03/ │ │ ├── pkg.go │ │ ├── pkg_amd64.s │ │ └── runme.go │ ├── ch3.6/ │ │ ├── asm-split/ │ │ │ ├── main.go │ │ │ └── main_amd64.s │ │ ├── closure-01/ │ │ │ └── main.go │ │ └── closure-02/ │ │ ├── main.go │ │ └── main_amd64.s │ ├── ch3.8/ │ │ ├── ch3.8-1/ │ │ │ └── main.go │ │ ├── ch3.8-2/ │ │ │ └── main.go │ │ ├── ch3.8-3/ │ │ │ └── main.go │ │ ├── ch3.8-4/ │ │ │ ├── main.go │ │ │ └── main_amd64.s │ │ ├── ch3.8-5/ │ │ │ ├── main.go │ │ │ └── main_amd64.s │ │ ├── ch3.8-6/ │ │ │ ├── gls/ │ │ │ │ ├── gls.go │ │ │ │ ├── goid.go │ │ │ │ └── goid_amd64.s │ │ │ ├── go.mod │ │ │ └── main.go │ │ ├── hello/ │ │ │ └── main.go │ │ └── hello-asm/ │ │ ├── main.go │ │ └── main_amd64.s │ ├── ch3.x/ │ │ ├── add/ │ │ │ ├── add.go │ │ │ ├── add_asm.go │ │ │ ├── add_asm_amd64.s │ │ │ ├── add_asm_generic.go │ │ │ ├── add_test.go │ │ │ └── runme.go │ │ ├── binary_search/ │ │ │ ├── binary_search.go │ │ │ ├── binary_search_amd64.s │ │ │ └── binary_search_test.go │ │ ├── cfun/ │ │ │ ├── main.go │ │ │ └── vendor/ │ │ │ └── asmpkg/ │ │ │ ├── asmpkg.go │ │ │ └── asmpkg_amd64.s │ │ ├── globalvar/ │ │ │ ├── asm_amd64.s │ │ │ ├── globalvar.go │ │ │ └── runme.go │ │ ├── hello/ │ │ │ ├── hello.go │ │ │ ├── hello_amd64.s │ │ │ └── runme.go │ │ ├── ifelse/ │ │ │ ├── ifelse.go │ │ │ ├── ifelse_ams_amd64.s │ │ │ ├── ifelse_test.go │ │ │ └── runme.go │ │ ├── instr/ │ │ │ ├── bench_test.go │ │ │ ├── instr.go │ │ │ └── instr_amd64.s │ │ ├── loop/ │ │ │ ├── loop.go │ │ │ ├── loop_asm_amd64.s │ │ │ ├── loop_test.go │ │ │ └── runme.go │ │ ├── min/ │ │ │ ├── min.go │ │ │ ├── min_asm_amd64.s │ │ │ ├── min_test.go │ │ │ └── runme.go │ │ ├── slice/ │ │ │ ├── runme.go │ │ │ ├── slice.go │ │ │ ├── slice_asm_amd64.s │ │ │ └── slice_test.go │ │ ├── stackmap/ │ │ │ ├── stackmap.go │ │ │ ├── stackmap_amd64.s │ │ │ └── stackmap_test.go │ │ ├── sum/ │ │ │ ├── sum.go │ │ │ ├── sum_amd64.s │ │ │ └── sum_test.go │ │ └── vector/ │ │ ├── sum_amd64.s │ │ ├── vector.go │ │ ├── vector_amd64.s │ │ └── vector_test.go │ ├── ch4.1/ │ │ ├── hello-client-v1/ │ │ │ └── main.go │ │ ├── hello-server-v1/ │ │ │ └── main.go │ │ ├── hello-service-v2/ │ │ │ ├── api/ │ │ │ │ └── hello.go │ │ │ ├── client/ │ │ │ │ └── main.go │ │ │ └── server/ │ │ │ └── main.go │ │ └── hello-service-v3/ │ │ ├── client/ │ │ │ └── main.go │ │ ├── server/ │ │ │ └── main.go │ │ └── server-on-http/ │ │ └── main.go │ ├── ch4.2/ │ │ ├── hello-server/ │ │ │ └── main.go │ │ ├── hello.pb/ │ │ │ ├── Makefile │ │ │ ├── hello.pb.go │ │ │ └── hello.proto │ │ └── protoc-gen-go-netrpc/ │ │ ├── main.go │ │ └── netprpc.go │ ├── ch4.3/ │ │ ├── rpc-auth/ │ │ │ ├── client/ │ │ │ │ └── main.go │ │ │ ├── main.go │ │ │ └── server/ │ │ │ └── main.go │ │ ├── rpc-context/ │ │ │ ├── client/ │ │ │ │ └── main.go │ │ │ └── server/ │ │ │ └── main.go │ │ └── rpc-reverse/ │ │ ├── client/ │ │ │ └── main.go │ │ └── server/ │ │ └── main.go │ ├── ch4.4/ │ │ ├── 1/ │ │ │ ├── client/ │ │ │ │ └── main.go │ │ │ ├── helloservice/ │ │ │ │ ├── hello.pb.go │ │ │ │ └── hello.proto │ │ │ └── server/ │ │ │ └── main.go │ │ ├── 2/ │ │ │ ├── HelloService/ │ │ │ │ ├── hello.pb.go │ │ │ │ └── hello.proto │ │ │ ├── client/ │ │ │ │ └── main.go │ │ │ └── server/ │ │ │ └── main.go │ │ ├── 3/ │ │ │ ├── clientpub/ │ │ │ │ └── main.go │ │ │ ├── clientsub/ │ │ │ │ └── main.go │ │ │ ├── pubsubservice/ │ │ │ │ ├── pubsubservice.pb.go │ │ │ │ └── pubsubservice.proto │ │ │ └── server/ │ │ │ └── main.go │ │ ├── basic/ │ │ │ └── client/ │ │ │ ├── Makefile │ │ │ ├── hello.pb.go │ │ │ ├── hello.proto │ │ │ └── main.go │ │ └── grpc-pubsub/ │ │ ├── clientPub/ │ │ │ └── clientPub.go │ │ ├── clientSub/ │ │ │ └── clientSub.go │ │ ├── pubsubservice/ │ │ │ ├── pubsubservice.pb.go │ │ │ └── pubsubservice.proto │ │ └── server/ │ │ └── server.go │ ├── ch4.5/ │ │ ├── on-web/ │ │ │ ├── Makefile │ │ │ ├── helloworld.pb.go │ │ │ ├── helloworld.proto │ │ │ ├── main.go │ │ │ └── tls-config/ │ │ │ ├── Makefile │ │ │ ├── server.crt │ │ │ └── server.key │ │ ├── panic-and-log/ │ │ │ ├── Makefile │ │ │ ├── helloworld.pb.go │ │ │ ├── helloworld.proto │ │ │ └── main.go │ │ ├── rest-and-swagger/ │ │ │ └── dummy.txt │ │ ├── tls/ │ │ │ ├── Makefile │ │ │ ├── helloworld.pb.go │ │ │ ├── helloworld.proto │ │ │ ├── main.go │ │ │ └── tls-config/ │ │ │ ├── Makefile │ │ │ ├── ca.crt │ │ │ ├── ca.key │ │ │ ├── ca.srl │ │ │ ├── client.crt │ │ │ ├── client.csr │ │ │ ├── client.key │ │ │ ├── server.crt │ │ │ ├── server.csr │ │ │ └── server.key │ │ └── tok/ │ │ ├── Makefile │ │ ├── helloworld.pb.go │ │ ├── helloworld.proto │ │ └── main.go │ ├── ch4.6/ │ │ ├── pb2-default-value/ │ │ │ ├── Makefile │ │ │ ├── helloworld.pb.go │ │ │ └── helloworld.proto │ │ ├── rest/ │ │ │ ├── Makefile │ │ │ ├── helloworld.pb.go │ │ │ ├── helloworld.pb.gw.go │ │ │ ├── helloworld.proto │ │ │ ├── helloworld.swagger.json │ │ │ └── main.go │ │ └── validators/ │ │ ├── Makefile │ │ ├── helloworld.pb.go │ │ ├── helloworld.proto │ │ └── helloworld.validator.pb.go │ └── ch4.7/ │ ├── http-router/ │ │ └── dummy.txt │ ├── pb-option/ │ │ ├── Makefile │ │ ├── dummy.txt │ │ ├── helloworld.pb.go │ │ ├── helloworld.proto │ │ └── main/ │ │ └── helloworld.pb.go │ ├── pb-web-frameswork/ │ │ └── dummy.txt │ └── plugin-framework/ │ └── dummy.txt ├── gen_contributors.go ├── go.mod ├── go.sum ├── images/ │ ├── Makefile │ ├── ch1-10-slice-1.ditaa.txt │ ├── ch1-11-init.ditaa.txt │ ├── ch1-12-init.ditaa.txt │ ├── ch1-7-array-4int.ditaa.txt │ ├── ch1-8-string-1.ditaa.txt │ ├── ch1-9-string-2.ditaa.txt │ ├── ch2-1-x-ptr-to-y-ptr.plantuml │ ├── ch2-2-int32-to-char-ptr.plantuml │ ├── ch2-3-x-slice-to-y-slice.plantuml │ ├── ch2-4-cgo-generated-files.dot │ ├── ch2-5-call-c-sum-v1.plantuml │ ├── ch2-6-call-c-sum-v2.plantuml │ ├── ch3-10-func-arg-01.ditaa.txt │ ├── ch3-11-func-local-var-01.ditaa.txt │ ├── ch3-12-func-call-frame-01.ditaa.txt │ ├── ch3-13-func-stack-frame-layout-01.ditaa.txt │ ├── ch3-2-arch-amd64-01.ditaa.txt │ ├── ch3-3-arch-amd64-02.ditaa.txt │ ├── ch3-4-pkg-var-decl-01.ditaa.txt │ ├── ch3-6-pkg-var-decl-02.ditaa.txt │ ├── ch3-7-pkg-var-decl-03.ditaa.txt │ ├── ch3-8-func-decl-01.ditaa.txt │ ├── ch3-9-func-decl-02.ditaa.txt │ ├── ch6-controller-logic-dao-storage.plantuml │ ├── ch6-interface-impl.plantuml │ └── github-social.drawio ├── index.md ├── preface-pdf.md └── preface.md
SYMBOL INDEX (1086 symbols across 208 files)
FILE: errata/a.go
function main (line 7) | func main() {
function LoopAdd (line 11) | func LoopAdd(cnt, v0, step int) int
FILE: examples/ch1.1/1-hello/main.go
function main (line 7) | func main() {
FILE: examples/ch1.2/15-hello-go-20091211/hello.go
function main (line 5) | func main() {
FILE: examples/ch1.2/16-hello-go-v2/hello.go
function main (line 13) | func main() {
FILE: examples/ch1.2/5-hello-c-1989/hello-c-04.c
function main (line 3) | main(void)
FILE: examples/ch1.2/xx-hello-go-asm/hello.go
function main (line 6) | func main()
FILE: examples/ch1.2/xx-hello-go-cgo/hello.go
function main (line 11) | func main() {
FILE: examples/ch1.2/xx-hello-go-swig/hello.cc
function SayHello (line 6) | void SayHello() {
FILE: examples/ch1.2/xx-hello-go-swig/hello.go
function main (line 12) | func main() {
FILE: examples/ch2.1/hello-01/main.go
function main (line 9) | func main() {
FILE: examples/ch2.1/hello-02/main.go
function main (line 15) | func main() {
FILE: examples/ch2.1/hello-03/hello.c
function SayHello (line 6) | void SayHello(const char* s) {
FILE: examples/ch2.1/hello-03/main.go
function main (line 9) | func main() {
FILE: examples/ch2.1/hello-04/main.go
function main (line 18) | func main() {
function cgoPuts (line 23) | func cgoPuts(s *C.char) {
FILE: examples/ch2.1/hello-05/main.go
function main (line 13) | func main() {
function SayHello (line 18) | func SayHello(s *C.char) {
FILE: examples/ch2.1/hello-06/main.go
function main (line 15) | func main() {
function SayHello (line 21) | func SayHello(s string) {
FILE: examples/ch2.10/hello-py/gopkg.h
function cgo_PyArg_ParseTuple_ii (line 25) | static int cgo_PyArg_ParseTuple_ii(PyObject *arg, int *a, int *b) {
function PyObject (line 29) | static PyObject* cgo_PyInit_gopkg(void) {
type GoInt8 (line 52) | typedef signed char GoInt8;
type GoUint8 (line 53) | typedef unsigned char GoUint8;
type GoInt16 (line 54) | typedef short GoInt16;
type GoUint16 (line 55) | typedef unsigned short GoUint16;
type GoInt32 (line 56) | typedef int GoInt32;
type GoUint32 (line 57) | typedef unsigned int GoUint32;
type GoInt64 (line 58) | typedef long long GoInt64;
type GoUint64 (line 59) | typedef unsigned long long GoUint64;
type GoInt64 (line 60) | typedef GoInt64 GoInt;
type GoUint64 (line 61) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 62) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 63) | typedef float GoFloat32;
type GoFloat64 (line 64) | typedef double GoFloat64;
type GoComplex64 (line 65) | typedef float _Complex GoComplex64;
type GoComplex128 (line 66) | typedef double _Complex GoComplex128;
type GoString (line 74) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 77) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 78) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.10/hello-py/main.go
function main (line 39) | func main() {}
function PyInit_gopkg (line 42) | func PyInit_gopkg() *C.PyObject {
function Py_gopkg_sum (line 47) | func Py_gopkg_sum(self, args *C.PyObject) *C.PyObject {
FILE: examples/ch2.10/hello-py/py3-config.go
function main (line 18) | func main() {
FILE: examples/ch2.10/hello-so/_test_so.c
function main (line 7) | int main() {
FILE: examples/ch2.10/hello-so/main.go
function main (line 9) | func main() {}
function SayHello (line 12) | func SayHello(name *C.char) {
FILE: examples/ch2.10/hello-so/say-hello.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.4/return-go-ptr/main.go
function main (line 21) | func main() {
function getGoPtr (line 26) | func getGoPtr() *C.int {
FILE: examples/ch2.5/01-cgo-gen-files/_obj/_cgo_export.h
type _GoString_ (line 13) | typedef struct { const char *p; ptrdiff_t n; } _GoString_;
type GoInt8 (line 30) | typedef signed char GoInt8;
type GoUint8 (line 31) | typedef unsigned char GoUint8;
type GoInt16 (line 32) | typedef short GoInt16;
type GoUint16 (line 33) | typedef unsigned short GoUint16;
type GoInt32 (line 34) | typedef int GoInt32;
type GoUint32 (line 35) | typedef unsigned int GoUint32;
type GoInt64 (line 36) | typedef long long GoInt64;
type GoUint64 (line 37) | typedef unsigned long long GoUint64;
type GoInt64 (line 38) | typedef GoInt64 GoInt;
type GoUint64 (line 39) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 40) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 41) | typedef float GoFloat32;
type GoFloat64 (line 42) | typedef double GoFloat64;
type GoComplex64 (line 43) | typedef float _Complex GoComplex64;
type GoComplex128 (line 44) | typedef double _Complex GoComplex128;
type _GoString_ (line 52) | typedef _GoString_ GoString;
type GoInterface (line 55) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 56) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.5/01-cgo-gen-files/_obj/_cgo_gotypes.go
function _Cgo_ptr (line 12) | func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }
function _Cgo_use (line 17) | func _Cgo_use(interface{})
type _Ctype_void (line 18) | type _Ctype_void
function _cgo_runtime_cgocall (line 21) | func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
function _cgo_runtime_cgocallback (line 24) | func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, u...
function _cgoCheckPointer (line 27) | func _cgoCheckPointer(interface{}, ...interface{})
function _cgoCheckResult (line 30) | func _cgoCheckResult(interface{})
FILE: examples/ch2.5/01-cgo-gen-files/_obj/_cgo_main.c
function main (line 1) | int main() { return 0; }
function crosscall2 (line 2) | void crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __...
function __SIZE_TYPE__ (line 3) | __SIZE_TYPE__ _cgo_wait_runtime_init_done() { return 0; }
function _cgo_release_context (line 4) | void _cgo_release_context(__SIZE_TYPE__ ctxt) { }
function _cgo_allocate (line 6) | void _cgo_allocate(void *a, int c) { }
function _cgo_panic (line 7) | void _cgo_panic(void *a, int c) { }
function _cgo_reginit (line 8) | void _cgo_reginit(void) { }
FILE: examples/ch2.5/01-cgo-gen-files/_obj/hello.cgo2.c
type intgo (line 6) | typedef ptrdiff_t intgo;
type _GoString_ (line 8) | typedef struct { const char *p; intgo n; } _GoString_;
type _GoBytes_ (line 9) | typedef struct { char *p; intgo n; intgo c; } _GoBytes_;
function _GoStringLen (line 17) | __attribute__ ((unused))
type __cgo_long_long (line 39) | typedef long long __cgo_long_long;
FILE: examples/ch2.5/01-cgo-gen-files/_obj/main.cgo1.go
function main (line 11) | func main() {}
FILE: examples/ch2.5/01-cgo-gen-files/_obj/main.cgo2.c
type intgo (line 6) | typedef ptrdiff_t intgo;
type _GoString_ (line 8) | typedef struct { const char *p; intgo n; } _GoString_;
type _GoBytes_ (line 9) | typedef struct { char *p; intgo n; intgo c; } _GoBytes_;
function _GoStringLen (line 17) | __attribute__ ((unused))
type __cgo_long_long (line 39) | typedef long long __cgo_long_long;
FILE: examples/ch2.5/01-cgo-gen-files/main.go
function main (line 8) | func main() {}
FILE: examples/ch2.5/02-go-call-c-func/_obj/_cgo_export.h
type _GoString_ (line 13) | typedef struct { const char *p; ptrdiff_t n; } _GoString_;
type GoInt8 (line 30) | typedef signed char GoInt8;
type GoUint8 (line 31) | typedef unsigned char GoUint8;
type GoInt16 (line 32) | typedef short GoInt16;
type GoUint16 (line 33) | typedef unsigned short GoUint16;
type GoInt32 (line 34) | typedef int GoInt32;
type GoUint32 (line 35) | typedef unsigned int GoUint32;
type GoInt64 (line 36) | typedef long long GoInt64;
type GoUint64 (line 37) | typedef unsigned long long GoUint64;
type GoInt64 (line 38) | typedef GoInt64 GoInt;
type GoUint64 (line 39) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 40) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 41) | typedef float GoFloat32;
type GoFloat64 (line 42) | typedef double GoFloat64;
type GoComplex64 (line 43) | typedef float _Complex GoComplex64;
type GoComplex128 (line 44) | typedef double _Complex GoComplex128;
type _GoString_ (line 52) | typedef _GoString_ GoString;
type GoInterface (line 55) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 56) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.5/02-go-call-c-func/_obj/_cgo_gotypes.go
function _Cgo_ptr (line 12) | func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }
function _Cgo_use (line 17) | func _Cgo_use(interface{})
type _Ctype_int (line 18) | type _Ctype_int
type _Ctype_void (line 20) | type _Ctype_void
function _cgo_runtime_cgocall (line 23) | func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
function _cgo_runtime_cgocallback (line 26) | func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, u...
function _cgoCheckPointer (line 29) | func _cgoCheckPointer(interface{}, ...interface{})
function _cgoCheckResult (line 32) | func _cgoCheckResult(interface{})
function _Cfunc_sum (line 40) | func _Cfunc_sum(p0 _Ctype_int, p1 _Ctype_int) (r1 _Ctype_int) {
FILE: examples/ch2.5/02-go-call-c-func/_obj/_cgo_main.c
function main (line 1) | int main() { return 0; }
function crosscall2 (line 2) | void crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __...
function __SIZE_TYPE__ (line 3) | __SIZE_TYPE__ _cgo_wait_runtime_init_done() { return 0; }
function _cgo_release_context (line 4) | void _cgo_release_context(__SIZE_TYPE__ ctxt) { }
function _cgo_allocate (line 6) | void _cgo_allocate(void *a, int c) { }
function _cgo_panic (line 7) | void _cgo_panic(void *a, int c) { }
function _cgo_reginit (line 8) | void _cgo_reginit(void) { }
FILE: examples/ch2.5/02-go-call-c-func/_obj/main.cgo1.go
function main (line 12) | func main() {
FILE: examples/ch2.5/02-go-call-c-func/_obj/main.cgo2.c
type intgo (line 6) | typedef ptrdiff_t intgo;
type _GoString_ (line 8) | typedef struct { const char *p; intgo n; } _GoString_;
type _GoBytes_ (line 9) | typedef struct { char *p; intgo n; intgo c; } _GoBytes_;
function _GoStringLen (line 17) | __attribute__ ((unused))
function sum (line 24) | int sum(int a, int b) { return a+b; }
type __cgo_long_long (line 43) | typedef long long __cgo_long_long;
function CGO_NO_SANITIZE_THREAD (line 58) | CGO_NO_SANITIZE_THREAD
FILE: examples/ch2.5/02-go-call-c-func/main.go
function main (line 9) | func main() {
FILE: examples/ch2.5/03-c-call-go-func/_obj/_cgo_export.c
function CGO_NO_SANITIZE_THREAD (line 16) | CGO_NO_SANITIZE_THREAD
FILE: examples/ch2.5/03-c-call-go-func/_obj/_cgo_export.h
type _GoString_ (line 13) | typedef struct { const char *p; ptrdiff_t n; } _GoString_;
type GoInt8 (line 35) | typedef signed char GoInt8;
type GoUint8 (line 36) | typedef unsigned char GoUint8;
type GoInt16 (line 37) | typedef short GoInt16;
type GoUint16 (line 38) | typedef unsigned short GoUint16;
type GoInt32 (line 39) | typedef int GoInt32;
type GoUint32 (line 40) | typedef unsigned int GoUint32;
type GoInt64 (line 41) | typedef long long GoInt64;
type GoUint64 (line 42) | typedef unsigned long long GoUint64;
type GoInt64 (line 43) | typedef GoInt64 GoInt;
type GoUint64 (line 44) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 45) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 46) | typedef float GoFloat32;
type GoFloat64 (line 47) | typedef double GoFloat64;
type GoComplex64 (line 48) | typedef float _Complex GoComplex64;
type GoComplex128 (line 49) | typedef double _Complex GoComplex128;
type _GoString_ (line 57) | typedef _GoString_ GoString;
type GoInterface (line 60) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 61) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.5/03-c-call-go-func/_obj/_cgo_gotypes.go
function _Cgo_ptr (line 12) | func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }
function _Cgo_use (line 17) | func _Cgo_use(interface{})
type _Ctype_int (line 18) | type _Ctype_int
type _Ctype_void (line 20) | type _Ctype_void
function _cgo_runtime_cgocall (line 23) | func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
function _cgo_runtime_cgocallback (line 26) | func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, u...
function _cgoCheckPointer (line 29) | func _cgoCheckPointer(interface{}, ...interface{})
function _cgoCheckResult (line 32) | func _cgoCheckResult(interface{})
function _cgoexp_8313eaf44386_sum (line 39) | func _cgoexp_8313eaf44386_sum(a unsafe.Pointer, n int32, ctxt uintptr) {
function _cgoexpwrap_8313eaf44386_sum (line 44) | func _cgoexpwrap_8313eaf44386_sum(p0 _Ctype_int, p1 _Ctype_int) (r0 _Cty...
FILE: examples/ch2.5/03-c-call-go-func/_obj/_cgo_main.c
function main (line 1) | int main() { return 0; }
function crosscall2 (line 2) | void crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __...
function __SIZE_TYPE__ (line 3) | __SIZE_TYPE__ _cgo_wait_runtime_init_done() { return 0; }
function _cgo_release_context (line 4) | void _cgo_release_context(__SIZE_TYPE__ ctxt) { }
function _cgo_allocate (line 6) | void _cgo_allocate(void *a, int c) { }
function _cgo_panic (line 7) | void _cgo_panic(void *a, int c) { }
function _cgo_reginit (line 8) | void _cgo_reginit(void) { }
FILE: examples/ch2.5/03-c-call-go-func/_obj/sum.cgo1.go
function sum (line 13) | func sum(a, b _Ctype_int) _Ctype_int {
function main (line 17) | func main() {}
FILE: examples/ch2.5/03-c-call-go-func/_obj/sum.cgo2.c
type intgo (line 6) | typedef ptrdiff_t intgo;
type _GoString_ (line 8) | typedef struct { const char *p; intgo n; } _GoString_;
type _GoBytes_ (line 9) | typedef struct { char *p; intgo n; intgo c; } _GoBytes_;
function _GoStringLen (line 17) | __attribute__ ((unused))
type __cgo_long_long (line 43) | typedef long long __cgo_long_long;
FILE: examples/ch2.5/03-c-call-go-func/main.c
function main (line 6) | int main() {
FILE: examples/ch2.5/03-c-call-go-func/sum.go
function sum (line 10) | func sum(a, b C.int) C.int {
function main (line 14) | func main() {}
FILE: examples/ch2.5/03-c-call-go-func/sum.h
type _GoString_ (line 13) | typedef struct { const char *p; ptrdiff_t n; } _GoString_;
type GoInt8 (line 35) | typedef signed char GoInt8;
type GoUint8 (line 36) | typedef unsigned char GoUint8;
type GoInt16 (line 37) | typedef short GoInt16;
type GoUint16 (line 38) | typedef unsigned short GoUint16;
type GoInt32 (line 39) | typedef int GoInt32;
type GoUint32 (line 40) | typedef unsigned int GoUint32;
type GoInt64 (line 41) | typedef long long GoInt64;
type GoUint64 (line 42) | typedef unsigned long long GoUint64;
type GoInt64 (line 43) | typedef GoInt64 GoInt;
type GoUint64 (line 44) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 45) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 46) | typedef float GoFloat32;
type GoFloat64 (line 47) | typedef double GoFloat64;
type GoComplex64 (line 48) | typedef float _Complex GoComplex64;
type GoComplex128 (line 49) | typedef double _Complex GoComplex128;
type _GoString_ (line 57) | typedef _GoString_ GoString;
type GoInterface (line 60) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 61) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.6/01-qsort-v1/main.c
function cmp (line 9) | static int cmp(const void* a, const void* b) {
function main (line 15) | int main() {
FILE: examples/ch2.6/02-qsort-v2/main.go
function main (line 18) | func main() {
function go_qsort_compare (line 29) | func go_qsort_compare(a, b unsafe.Pointer) C.int {
FILE: examples/ch2.6/02-qsort-v2/qsort.go
type CompareFunc (line 15) | type CompareFunc
function Sort (line 17) | func Sort(base unsafe.Pointer, num, size int, cmp CompareFunc) {
FILE: examples/ch2.6/02-qsort-v2/qsort_test.go
function TestSort (line 12) | func TestSort(t *testing.T) {
FILE: examples/ch2.6/02-qsort-v2/test_helper.go
function t_get_go_qsort_compare (line 13) | func t_get_go_qsort_compare() CompareFunc {
function t_go_qsort_compare (line 18) | func t_go_qsort_compare(a, b unsafe.Pointer) C.int {
FILE: examples/ch2.6/03-qsort-v3/main.go
function main (line 15) | func main() {
FILE: examples/ch2.6/03-qsort-v3/sort.go
function _cgo_qsort_compare (line 24) | func _cgo_qsort_compare(a, b unsafe.Pointer) C.int {
function Sort (line 28) | func Sort(base unsafe.Pointer, num, size int, cmp func(a, b unsafe.Point...
FILE: examples/ch2.6/03-qsort-v3/sort_test.go
function TestSort (line 12) | func TestSort(t *testing.T) {
FILE: examples/ch2.6/04-qsort-v4/main.go
function main (line 14) | func main() {
FILE: examples/ch2.6/04-qsort-v4/sort.go
function _cgo_qsort_compare (line 31) | func _cgo_qsort_compare(a, b unsafe.Pointer) C.int {
function Slice (line 51) | func Slice(slice interface{}, less func(a, b int) bool) {
FILE: examples/ch2.6/04-qsort-v4/sort_test.go
function TestSlice (line 11) | func TestSlice(t *testing.T) {
FILE: examples/ch2.8/class-cc2go/main.go
function main (line 10) | func main() {
FILE: examples/ch2.8/class-cc2go/my_buffer.go
type MyBuffer (line 8) | type MyBuffer struct
method Delete (line 18) | func (p *MyBuffer) Delete() {
method Data (line 22) | func (p *MyBuffer) Data() []byte {
function NewMyBuffer (line 12) | func NewMyBuffer(size int) *MyBuffer {
FILE: examples/ch2.8/class-cc2go/my_buffer.h
function MyBuffer (line 6) | struct MyBuffer {
FILE: examples/ch2.8/class-cc2go/my_buffer_capi.cc
type MyBuffer_T (line 10) | struct MyBuffer_T: MyBuffer {
method MyBuffer_T (line 11) | MyBuffer_T(int size): MyBuffer(size) {}
function MyBuffer_T (line 15) | MyBuffer_T* NewMyBuffer(int size) {
method MyBuffer_T (line 11) | MyBuffer_T(int size): MyBuffer(size) {}
function DeleteMyBuffer (line 19) | void DeleteMyBuffer(MyBuffer_T* p) {
function MyBuffer_Size (line 26) | int MyBuffer_Size(MyBuffer_T* p) {
FILE: examples/ch2.8/class-cc2go/my_buffer_capi.go
type cgo_MyBuffer_T (line 13) | type cgo_MyBuffer_T
function cgo_NewMyBuffer (line 15) | func cgo_NewMyBuffer(size int) *cgo_MyBuffer_T {
function cgo_DeleteMyBuffer (line 20) | func cgo_DeleteMyBuffer(p *cgo_MyBuffer_T) {
function cgo_MyBuffer_Data (line 24) | func cgo_MyBuffer_Data(p *cgo_MyBuffer_T) *C.char {
function cgo_MyBuffer_Size (line 28) | func cgo_MyBuffer_Size(p *cgo_MyBuffer_T) C.int {
FILE: examples/ch2.8/class-cc2go/my_buffer_capi.h
type MyBuffer_T (line 4) | typedef struct MyBuffer_T MyBuffer_T;
FILE: examples/ch2.8/class-go2cc/goobj.go
type ObjectId (line 10) | type ObjectId
method IsNil (line 37) | func (id ObjectId) IsNil() bool {
method Get (line 41) | func (id ObjectId) Get() interface{} {
method Free (line 48) | func (id ObjectId) Free() interface{} {
function init (line 18) | func init() {
function NewObjectId (line 26) | func NewObjectId(obj interface{}) ObjectId {
FILE: examples/ch2.8/class-go2cc/main.cc
function Main (line 8) | void Main() {
FILE: examples/ch2.8/class-go2cc/main.go
function main (line 10) | func main() {
FILE: examples/ch2.8/class-go2cc/persion.go
type Person (line 6) | type Person struct
method Set (line 18) | func (p *Person) Set(name string, age int) {
method Get (line 23) | func (p *Person) Get() (name string, age int) {
function NewPerson (line 11) | func NewPerson(name string, age int) *Person {
FILE: examples/ch2.8/class-go2cc/person.h
function Delete (line 8) | struct Person {
function Set (line 16) | void Set(char* name, int age) {
function GetAge (line 22) | int GetAge() {
FILE: examples/ch2.8/class-go2cc/person_capi.go
function person_new (line 11) | func person_new(name *C.char, age C.int) C.person_handle_t {
function person_delete (line 17) | func person_delete(h C.person_handle_t) {
function person_set (line 22) | func person_set(h C.person_handle_t, name *C.char, age C.int) {
function person_get_name (line 28) | func person_get_name(h C.person_handle_t, buf *C.char, size C.int) *C.ch...
function person_get_age (line 41) | func person_get_age(h C.person_handle_t) C.int {
FILE: examples/ch2.8/class-go2cc/person_capi.h
type person_handle_t (line 6) | typedef uintptr_t person_handle_t;
FILE: examples/ch2.9/incorrect-dll-api/main.go
function main (line 17) | func main() {
FILE: examples/ch2.9/incorrect-dll-api/mystring/mystring.c
function free (line 14) | static void free(void* p) {
function free_string (line 24) | void free_string(char* s) {
FILE: examples/ch2.9/make-clib-dll/_test_main.c
function main (line 8) | int main() {
FILE: examples/ch2.9/make-clib-dll/main.go
function main (line 8) | func main() {}
function number_add_mod (line 11) | func number_add_mod(a, b, mod C.int) C.int {
FILE: examples/ch2.9/make-clib-dll/number.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.9/make-clib-from-multi-pkg/_test_main.c
function main (line 9) | int main() {
FILE: examples/ch2.9/make-clib-from-multi-pkg/main.go
function main (line 14) | func main() {
function goPrintln (line 19) | func goPrintln(s *C.char) {
FILE: examples/ch2.9/make-clib-from-multi-pkg/main.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.9/make-clib-from-multi-pkg/number/number.go
function number_add_mod (line 9) | func number_add_mod(a, b, mod C.int) C.int {
FILE: examples/ch2.9/make-clib-shared/_test_main.c
function main (line 8) | int main() {
FILE: examples/ch2.9/make-clib-shared/main.go
function main (line 8) | func main() {}
function number_add_mod (line 11) | func number_add_mod(a, b, mod C.int) C.int {
FILE: examples/ch2.9/make-clib-shared/number.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.9/make-clib-static/_test_main.c
function main (line 8) | int main() {
FILE: examples/ch2.9/make-clib-static/main.go
function main (line 8) | func main() {}
function number_add_mod (line 11) | func number_add_mod(a, b, mod C.int) C.int {
FILE: examples/ch2.9/make-clib-static/number.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.9/plugin/main.go
function main (line 8) | func main() {
FILE: examples/ch2.9/plugin/plugin.go
function main (line 11) | func main()
function F (line 15) | func F() { fmt.Printf("Hello, number %d\n", V) }
FILE: examples/ch2.9/use-clib-shared/main.go
function main (line 13) | func main() {
FILE: examples/ch2.9/use-clib-shared/number/number.c
function number_add_mod (line 3) | int number_add_mod(int a, int b, int mod) {
FILE: examples/ch2.9/use-clib-static-v1/main.go
function main (line 13) | func main() {
FILE: examples/ch2.9/use-clib-static-v1/number/number.c
function number_add_mod (line 3) | int number_add_mod(int a, int b, int mod) {
FILE: examples/ch2.9/use-clib-static-v2/main.go
function main (line 11) | func main() {
FILE: examples/ch2.9/use-clib-static-v2/number/number.c
function number_add_mod (line 3) | int number_add_mod(int a, int b, int mod) {
FILE: examples/ch2.x/hello-swig-v1/hello.cc
function SayHello (line 6) | void SayHello() {
FILE: examples/ch2.x/hello-swig-v1/hello_test.go
function TestSayHello (line 10) | func TestSayHello(t *testing.T) {
FILE: examples/ch2.x/hello-swig-v1/runme.go
function main (line 12) | func main() {
FILE: examples/ch2.x/hello-swig-v2/hello.cc
function SayHello (line 6) | void SayHello() {
FILE: examples/ch2.x/hello-swig-v2/hello.go
type _ (line 43) | type _
type _swig_fnptr (line 51) | type _swig_fnptr
type _swig_memberptr (line 52) | type _swig_memberptr
type _ (line 55) | type _
function Swig_free (line 57) | func Swig_free(arg1 uintptr) {
function Swig_malloc (line 62) | func Swig_malloc(arg1 int) (_swig_ret uintptr) {
function SayHello (line 69) | func SayHello() {
FILE: examples/ch2.x/hello-swig-v2/runme.go
function main (line 12) | func main() {
FILE: examples/ch2.x/hello/_obj/_cgo_export.c
function CGO_NO_SANITIZE_THREAD (line 16) | CGO_NO_SANITIZE_THREAD
function CGO_NO_SANITIZE_THREAD (line 31) | CGO_NO_SANITIZE_THREAD
function CGO_NO_SANITIZE_THREAD (line 46) | CGO_NO_SANITIZE_THREAD
FILE: examples/ch2.x/hello/_obj/_cgo_export.h
type GoInt8 (line 19) | typedef signed char GoInt8;
type GoUint8 (line 20) | typedef unsigned char GoUint8;
type GoInt16 (line 21) | typedef short GoInt16;
type GoUint16 (line 22) | typedef unsigned short GoUint16;
type GoInt32 (line 23) | typedef int GoInt32;
type GoUint32 (line 24) | typedef unsigned int GoUint32;
type GoInt64 (line 25) | typedef long long GoInt64;
type GoUint64 (line 26) | typedef unsigned long long GoUint64;
type GoInt64 (line 27) | typedef GoInt64 GoInt;
type GoUint64 (line 28) | typedef GoUint64 GoUint;
type __SIZE_TYPE__ (line 29) | typedef __SIZE_TYPE__ GoUintptr;
type GoFloat32 (line 30) | typedef float GoFloat32;
type GoFloat64 (line 31) | typedef double GoFloat64;
type GoComplex64 (line 32) | typedef float _Complex GoComplex64;
type GoComplex128 (line 33) | typedef double _Complex GoComplex128;
type GoString (line 41) | typedef struct { const char *p; GoInt n; } GoString;
type GoInterface (line 44) | typedef struct { void *t; void *v; } GoInterface;
type GoSlice (line 45) | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
FILE: examples/ch2.x/hello/_obj/_cgo_gotypes.go
function _Cgo_ptr (line 12) | func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr }
function _Cgo_use (line 17) | func _Cgo_use(interface{})
type _Ctype_void (line 18) | type _Ctype_void
function _cgo_runtime_cgocall (line 21) | func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32
function _cgo_runtime_cgocallback (line 24) | func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, u...
function _cgoCheckPointer (line 27) | func _cgoCheckPointer(interface{}, ...interface{})
function _cgoCheckResult (line 30) | func _cgoCheckResult(interface{})
function _cgoexp_16f1900c27a8_helloInt (line 37) | func _cgoexp_16f1900c27a8_helloInt(a unsafe.Pointer, n int32, ctxt uintp...
function _cgoexpwrap_16f1900c27a8_helloInt (line 42) | func _cgoexpwrap_16f1900c27a8_helloInt(p0 int) {
function _cgoexp_16f1900c27a8_helloString (line 50) | func _cgoexp_16f1900c27a8_helloString(a unsafe.Pointer, n int32, ctxt ui...
function _cgoexpwrap_16f1900c27a8_helloString (line 55) | func _cgoexpwrap_16f1900c27a8_helloString(p0 string) {
function _cgoexp_16f1900c27a8_helloSlice (line 63) | func _cgoexp_16f1900c27a8_helloSlice(a unsafe.Pointer, n int32, ctxt uin...
function _cgoexpwrap_16f1900c27a8_helloSlice (line 68) | func _cgoexpwrap_16f1900c27a8_helloSlice(p0 []byte) {
FILE: examples/ch2.x/hello/_obj/_cgo_main.c
function main (line 1) | int main() { return 0; }
function crosscall2 (line 2) | void crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __...
function __SIZE_TYPE__ (line 3) | __SIZE_TYPE__ _cgo_wait_runtime_init_done() { return 0; }
function _cgo_release_context (line 4) | void _cgo_release_context(__SIZE_TYPE__ ctxt) { }
function _cgo_allocate (line 6) | void _cgo_allocate(void *a, int c) { }
function _cgo_panic (line 7) | void _cgo_panic(void *a, int c) { }
function _cgo_reginit (line 8) | void _cgo_reginit(void) { }
FILE: examples/ch2.x/hello/_obj/hello.cgo1.go
function main (line 7) | func main() {
function helloInt (line 12) | func helloInt(s int) {
function helloString (line 17) | func helloString(s string) {
function helloSlice (line 22) | func helloSlice(s []byte) {
FILE: examples/ch2.x/hello/_obj/hello.cgo2.c
type __cgo_long_long (line 17) | typedef long long __cgo_long_long;
FILE: examples/ch2.x/hello/hello.go
function main (line 8) | func main() {
function helloInt (line 13) | func helloInt(s int) {
function helloString (line 18) | func helloString(s string) {
function helloSlice (line 23) | func helloSlice(s []byte) {
FILE: examples/ch3.1/id-01/runme.go
function main (line 7) | func main() {
FILE: examples/ch3.1/id-02/runme.go
function main (line 9) | func main() {
FILE: examples/ch3.1/main-01/main.go
function main (line 5) | func main()
FILE: examples/ch3.1/str-02/runme.go
function main (line 9) | func main() {
FILE: examples/ch3.1/str-03/runme.go
function main (line 9) | func main() {
FILE: examples/ch3.6/asm-split/main.go
function main (line 4) | func main() {
function printnl (line 8) | func printnl()
FILE: examples/ch3.6/closure-01/main.go
function NewTwiceFunClosure (line 6) | func NewTwiceFunClosure(x int) func() int {
function main (line 13) | func main() {
FILE: examples/ch3.6/closure-02/main.go
type FunTwiceClosure (line 10) | type FunTwiceClosure struct
function NewTwiceFunClosure (line 15) | func NewTwiceFunClosure(x int) func() int {
function ptrToFunc (line 23) | func ptrToFunc(p unsafe.Pointer) func() int
function asmFunTwiceClosureAddr (line 25) | func asmFunTwiceClosureAddr() uintptr
function asmFunTwiceClosureBody (line 26) | func asmFunTwiceClosureBody() int
function main (line 28) | func main() {
FILE: examples/ch3.8/ch3.8-1/main.go
function main (line 3) | func main() {
FILE: examples/ch3.8/ch3.8-2/main.go
function main (line 5) | func main() {
FILE: examples/ch3.8/ch3.8-3/main.go
function main (line 11) | func main() {
function GetGoid (line 20) | func GetGoid() int64 {
FILE: examples/ch3.8/ch3.8-4/main.go
function getg (line 7) | func getg() unsafe.Pointer
constant g_goid_offset (line 9) | g_goid_offset = 152
function GetGroutineId (line 11) | func GetGroutineId() int64 {
function main (line 17) | func main() {
FILE: examples/ch3.8/ch3.8-5/main.go
function getg (line 8) | func getg() interface{}
type eface (line 10) | type eface struct
function runtime_convT2E_hack (line 15) | func runtime_convT2E_hack(_type, elem uintptr) eface {
function GetGoid (line 22) | func GetGoid() int64 {
function main (line 27) | func main() {
FILE: examples/ch3.8/ch3.8-6/gls/gls.go
function init (line 10) | func init() {
function getMap (line 14) | func getMap() map[interface{}]interface{} {
function Get (line 28) | func Get(key interface{}) interface{} {
function Put (line 31) | func Put(key interface{}, v interface{}) {
function Delete (line 34) | func Delete(key interface{}) {
function Clean (line 38) | func Clean() {
FILE: examples/ch3.8/ch3.8-6/gls/goid.go
function getg (line 7) | func getg() interface{}
type eface (line 9) | type eface struct
function runtime_convT2E_hack (line 14) | func runtime_convT2E_hack(_type, elem uintptr) eface {
function GetGoid (line 21) | func GetGoid() int64 {
FILE: examples/ch3.8/ch3.8-6/main.go
function main (line 10) | func main() {
FILE: examples/ch3.8/hello-asm/main.go
function main (line 3) | func main() { asmSayHello() }
function asmSayHello (line 5) | func asmSayHello()
FILE: examples/ch3.8/hello/main.go
function main (line 7) | func main() {
FILE: examples/ch3.x/add/add.go
function Add (line 8) | func Add(a, b int) int {
function AddSlice (line 12) | func AddSlice(dst, a, b []int) {
FILE: examples/ch3.x/add/add_asm.go
function AsmAdd (line 10) | func AsmAdd(a, b int) int
function AsmAddSlice (line 12) | func AsmAddSlice(dst, a, b []int) {
function AsmAddSlice__todo (line 16) | func AsmAddSlice__todo(dst, a, b []int)
FILE: examples/ch3.x/add/add_asm_generic.go
function AsmAdd (line 10) | func AsmAdd(a, b int) int {
function AsmAddSlice (line 14) | func AsmAddSlice(dst, a, b []int) {
FILE: examples/ch3.x/add/add_test.go
function TestAdd (line 12) | func TestAdd(t *testing.T) {
function TestAddSlice (line 25) | func TestAddSlice(t *testing.T) {
function BenchmarkAdd (line 52) | func BenchmarkAdd(b *testing.B) {
function BenchmarkAddSlice (line 65) | func BenchmarkAddSlice(b *testing.B) {
FILE: examples/ch3.x/add/runme.go
function main (line 12) | func main() {
FILE: examples/ch3.x/binary_search/binary_search.go
function BinarySearch (line 6) | func BinarySearch(arr []int, num int) bool
FILE: examples/ch3.x/binary_search/binary_search_test.go
function TestBinarySearch (line 8) | func TestBinarySearch(t *testing.T) {
FILE: examples/ch3.x/cfun/main.go
function main (line 19) | func main() {
FILE: examples/ch3.x/cfun/vendor/asmpkg/asmpkg.go
function CallCAdd_SystemV_ABI (line 3) | func CallCAdd_SystemV_ABI(cfun uintptr, a, b int64) int64
function CallCAdd_Win64_ABI (line 4) | func CallCAdd_Win64_ABI(cfun uintptr, a, b int64) int64
function SyscallWrite_Darwin (line 6) | func SyscallWrite_Darwin(fd int, msg string) int
function SyscallWrite_Linux (line 7) | func SyscallWrite_Linux(fd int, msg string) int
function SyscallWrite_Windows (line 8) | func SyscallWrite_Windows(fd int, msg string) int
function CopySlice_AVX2 (line 10) | func CopySlice_AVX2(dst, src []byte, len int)
FILE: examples/ch3.x/globalvar/globalvar.go
type PkgInfo (line 10) | type PkgInfo struct
function init (line 21) | func init() {
function GetPkgValue (line 30) | func GetPkgValue() int
function GetPkgInfo (line 32) | func GetPkgInfo() PkgInfo
FILE: examples/ch3.x/globalvar/runme.go
function main (line 14) | func main() {
FILE: examples/ch3.x/hello/hello.go
function PrintHelloWorld (line 8) | func PrintHelloWorld()
function PrintHelloWorld_zh (line 9) | func PrintHelloWorld_zh()
function PrintHelloWorld_var (line 10) | func PrintHelloWorld_var()
FILE: examples/ch3.x/hello/runme.go
function main (line 14) | func main() {
FILE: examples/ch3.x/ifelse/ifelse.go
function If (line 8) | func If(ok bool, a, b int) int {
function AsmIf (line 15) | func AsmIf(ok bool, a, b int) int
FILE: examples/ch3.x/ifelse/ifelse_test.go
function TestMin (line 12) | func TestMin(t *testing.T) {
FILE: examples/ch3.x/ifelse/runme.go
function main (line 12) | func main() {
FILE: examples/ch3.x/instr/bench_test.go
function BenchmarkSum (line 10) | func BenchmarkSum(b *testing.B) {
function BenchmarkSum2 (line 17) | func BenchmarkSum2(b *testing.B) {
FILE: examples/ch3.x/instr/instr.go
function Add (line 6) | func Add(n, m int64) int64 {
function Add2 (line 10) | func Add2(n, m int64) int64
function BSF (line 14) | func BSF(n int64) int
function BSF32 (line 16) | func BSF32(n int32) int32
function Sum (line 18) | func Sum(s []int64) int64 {
function Sum2 (line 26) | func Sum2(s []int64) int64
FILE: examples/ch3.x/loop/loop.go
function LoopAdd (line 8) | func LoopAdd(cnt, v0, step int) int {
function AsmLoopAdd (line 16) | func AsmLoopAdd(cnt, v0, step int) int
FILE: examples/ch3.x/loop/loop_test.go
function TestLoopAdd (line 12) | func TestLoopAdd(t *testing.T) {
function BenchmarkLoopAdd (line 43) | func BenchmarkLoopAdd(b *testing.B) {
FILE: examples/ch3.x/loop/runme.go
function main (line 12) | func main() {
FILE: examples/ch3.x/min/min.go
function Min (line 8) | func Min(a, b int) int {
function MinNoInline (line 16) | func MinNoInline(a, b int) int {
function Max (line 23) | func Max(a, b int) int {
function AsmMin (line 30) | func AsmMin(a, b int) int
function AsmMax (line 31) | func AsmMax(a, b int) int
FILE: examples/ch3.x/min/min_test.go
function TestMin (line 12) | func TestMin(t *testing.T) {
function TestMax (line 30) | func TestMax(t *testing.T) {
function BenchmarkMin (line 49) | func BenchmarkMin(b *testing.B) {
FILE: examples/ch3.x/min/runme.go
function main (line 12) | func main() {
FILE: examples/ch3.x/slice/runme.go
function main (line 12) | func main() {
FILE: examples/ch3.x/slice/slice.go
function SumIntSlice (line 8) | func SumIntSlice(s []int) int {
function SumFloat32Slice (line 16) | func SumFloat32Slice(s []float32) float32 {
function SumFloat64Slice (line 24) | func SumFloat64Slice(s []float64) float64 {
function AsmSumInt16Slice (line 32) | func AsmSumInt16Slice(v []int16) int16
function AsmSumIntSlice (line 34) | func AsmSumIntSlice(s []int) int
function AsmSumIntSliceV2 (line 35) | func AsmSumIntSliceV2(s []int) int
FILE: examples/ch3.x/slice/slice_test.go
function TestLoopAdd (line 12) | func TestLoopAdd(t *testing.T) {
function BenchmarkLoopAdd (line 30) | func BenchmarkLoopAdd(b *testing.B) {
FILE: examples/ch3.x/stackmap/stackmap.go
function X (line 6) | func X(b []byte) []byte
function growSlice (line 17) | func growSlice(b []byte) []byte {
FILE: examples/ch3.x/stackmap/stackmap_test.go
function TestX (line 11) | func TestX(t *testing.T) {
function mkSlice (line 35) | func mkSlice(cap int, vs ...byte) []byte {
function slicesEqual (line 42) | func slicesEqual(b0, b1 []byte) bool {
FILE: examples/ch3.x/sum/sum.go
function Sum (line 6) | func Sum(a int, b int) int
FILE: examples/ch3.x/sum/sum_test.go
function TestSum (line 8) | func TestSum(t *testing.T) {
FILE: examples/ch3.x/vector/vector.go
function Find (line 6) | func Find(vec []int, num int) bool
function SumVec (line 8) | func SumVec(vec1 []int32, vec2 []int32) [4]int32
FILE: examples/ch3.x/vector/vector_test.go
function TestFind (line 8) | func TestFind(t *testing.T) {
function TestSum (line 19) | func TestSum(t *testing.T) {
FILE: examples/ch4.1/hello-client-v1/main.go
function main (line 9) | func main() {
FILE: examples/ch4.1/hello-server-v1/main.go
type HelloService (line 9) | type HelloService struct
method Hello (line 11) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 16) | func main() {
FILE: examples/ch4.1/hello-service-v2/api/hello.go
constant HelloServiceName (line 5) | HelloServiceName = "path/to/pkg.HelloService"
function RegisterHelloService (line 11) | func RegisterHelloService(svc HelloServiceInterface) error {
type HelloServiceClient (line 15) | type HelloServiceClient struct
method Hello (line 29) | func (p *HelloServiceClient) Hello(request string, reply *string) error {
function DialHelloService (line 21) | func DialHelloService(network, address string) (*HelloServiceClient, err...
FILE: examples/ch4.1/hello-service-v2/client/main.go
type HelloService (line 10) | type HelloService struct
method Hello (line 12) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 17) | func main() {
FILE: examples/ch4.1/hello-service-v2/server/main.go
type HelloService (line 11) | type HelloService struct
method Hello (line 13) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 18) | func main() {
FILE: examples/ch4.1/hello-service-v3/client/main.go
function main (line 14) | func main() {
FILE: examples/ch4.1/hello-service-v3/server-on-http/main.go
type HelloService (line 10) | type HelloService struct
method Hello (line 12) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 17) | func main() {
FILE: examples/ch4.1/hello-service-v3/server/main.go
type HelloService (line 12) | type HelloService struct
method Hello (line 14) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 21) | func main() {
FILE: examples/ch4.2/hello-server/main.go
type HelloService (line 11) | type HelloService struct
method Hello (line 13) | func (p *HelloService) Hello(request pb.String, reply *pb.String) error {
function main (line 18) | func main() {
FILE: examples/ch4.2/hello.pb/hello.pb.go
constant _ (line 19) | _ = proto.ProtoPackageIsVersion2
type String (line 21) | type String struct
method Reset (line 28) | func (m *String) Reset() { *m = String{} }
method String (line 29) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 30) | func (*String) ProtoMessage() {}
method Descriptor (line 31) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 34) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 37) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 40) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 43) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 46) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 52) | func (m *String) GetValue() string {
function init (line 59) | func init() {
function init (line 63) | func init() { proto.RegisterFile("hello.proto", fileDescriptor_hello_d23...
FILE: examples/ch4.2/protoc-gen-go-netrpc/main.go
function main (line 59) | func main() {
FILE: examples/ch4.2/protoc-gen-go-netrpc/netprpc.go
function init (line 16) | func init() {
type netrpcPlugin (line 20) | type netrpcPlugin struct
method Name (line 22) | func (p *netrpcPlugin) Name() string { return "netrpc" }
method Init (line 23) | func (p *netrpcPlugin) Init(g *generator.Generator) { p.Generator = g }
method GenerateImports (line 25) | func (p *netrpcPlugin) GenerateImports(file *generator.FileDescriptor) {
method Generate (line 31) | func (p *netrpcPlugin) Generate(file *generator.FileDescriptor) {
method genImportCode (line 48) | func (p *netrpcPlugin) genImportCode(file *generator.FileDescriptor) {
method genServiceCode (line 52) | func (p *netrpcPlugin) genServiceCode(svc *descriptor.ServiceDescripto...
method buildServiceSpec (line 65) | func (p *netrpcPlugin) buildServiceSpec(svc *descriptor.ServiceDescrip...
type ServiceSpec (line 37) | type ServiceSpec struct
type ServiceMethodSpec (line 42) | type ServiceMethodSpec struct
constant tmplService (line 81) | tmplService = `
FILE: examples/ch4.3/rpc-auth/client/main.go
function main (line 9) | func main() {
FILE: examples/ch4.3/rpc-auth/main.go
function main (line 3) | func main() {
FILE: examples/ch4.3/rpc-auth/server/main.go
type HelloService (line 10) | type HelloService struct
method Login (line 21) | func (p *HelloService) Login(request string, reply *string) error {
method Hello (line 31) | func (p *HelloService) Hello(request string, reply *string) error {
function ServeHelloService (line 15) | func ServeHelloService(conn net.Conn) {
function main (line 39) | func main() {
FILE: examples/ch4.3/rpc-context/client/main.go
function main (line 9) | func main() {
FILE: examples/ch4.3/rpc-context/server/main.go
type HelloService (line 9) | type HelloService struct
method Hello (line 19) | func (p *HelloService) Hello(request string, reply *string) error {
function ServeHelloService (line 13) | func ServeHelloService(conn net.Conn) {
function main (line 24) | func main() {
FILE: examples/ch4.3/rpc-reverse/client/main.go
function main (line 10) | func main() {
FILE: examples/ch4.3/rpc-reverse/server/main.go
type HelloService (line 9) | type HelloService struct
method Hello (line 11) | func (p *HelloService) Hello(request string, reply *string) error {
function main (line 16) | func main() {
FILE: examples/ch4.4/1/client/main.go
function main (line 13) | func main() {
FILE: examples/ch4.4/1/helloservice/hello.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type String (line 26) | type String struct
method Reset (line 33) | func (m *String) Reset() { *m = String{} }
method String (line 34) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 35) | func (*String) ProtoMessage() {}
method Descriptor (line 36) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 45) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 57) | func (m *String) GetValue() string {
function init (line 64) | func init() {
constant _ (line 74) | _ = grpc.SupportPackageIsVersion4
type HelloServiceClient (line 79) | type HelloServiceClient interface
type helloServiceClient (line 83) | type helloServiceClient struct
method Hello (line 91) | func (c *helloServiceClient) Hello(ctx context.Context, in *String, op...
function NewHelloServiceClient (line 87) | func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
type HelloServiceServer (line 101) | type HelloServiceServer interface
function RegisterHelloServiceServer (line 105) | func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
function _HelloService_Hello_Handler (line 109) | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, d...
function init (line 140) | func init() { proto.RegisterFile("hello.proto", fileDescriptor_hello_c1f...
FILE: examples/ch4.4/1/server/main.go
type HelloServiceImpl (line 13) | type HelloServiceImpl struct
method Hello (line 15) | func (p *HelloServiceImpl) Hello(
function main (line 22) | func main() {
FILE: examples/ch4.4/2/HelloService/hello.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type String (line 26) | type String struct
method Reset (line 33) | func (m *String) Reset() { *m = String{} }
method String (line 34) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 35) | func (*String) ProtoMessage() {}
method Descriptor (line 36) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 45) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 57) | func (m *String) GetValue() string {
function init (line 64) | func init() {
constant _ (line 74) | _ = grpc.SupportPackageIsVersion4
type HelloServiceClient (line 79) | type HelloServiceClient interface
type helloServiceClient (line 84) | type helloServiceClient struct
method Hello (line 92) | func (c *helloServiceClient) Hello(ctx context.Context, in *String, op...
method Channel (line 101) | func (c *helloServiceClient) Channel(ctx context.Context, opts ...grpc...
function NewHelloServiceClient (line 88) | func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
type HelloService_ChannelClient (line 110) | type HelloService_ChannelClient interface
type helloServiceChannelClient (line 116) | type helloServiceChannelClient struct
method Send (line 120) | func (x *helloServiceChannelClient) Send(m *String) error {
method Recv (line 124) | func (x *helloServiceChannelClient) Recv() (*String, error) {
type HelloServiceServer (line 133) | type HelloServiceServer interface
function RegisterHelloServiceServer (line 138) | func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
function _HelloService_Hello_Handler (line 142) | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, d...
function _HelloService_Channel_Handler (line 160) | func _HelloService_Channel_Handler(srv interface{}, stream grpc.ServerSt...
type HelloService_ChannelServer (line 164) | type HelloService_ChannelServer interface
type helloServiceChannelServer (line 170) | type helloServiceChannelServer struct
method Send (line 174) | func (x *helloServiceChannelServer) Send(m *String) error {
method Recv (line 178) | func (x *helloServiceChannelServer) Recv() (*String, error) {
function init (line 206) | func init() { proto.RegisterFile("hello.proto", fileDescriptor_hello_89b...
FILE: examples/ch4.4/2/client/main.go
function main (line 15) | func main() {
FILE: examples/ch4.4/2/server/main.go
type HelloServiceImpl (line 14) | type HelloServiceImpl struct
method Hello (line 16) | func (p *HelloServiceImpl) Hello(
method Channel (line 24) | func (p *HelloServiceImpl) Channel(stream hs.HelloService_ChannelServe...
function main (line 43) | func main() {
FILE: examples/ch4.4/3/clientpub/main.go
function main (line 12) | func main() {
FILE: examples/ch4.4/3/clientsub/main.go
function main (line 14) | func main() {
FILE: examples/ch4.4/3/pubsubservice/pubsubservice.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type String (line 26) | type String struct
method Reset (line 33) | func (m *String) Reset() { *m = String{} }
method String (line 34) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 35) | func (*String) ProtoMessage() {}
method Descriptor (line 36) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 45) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 57) | func (m *String) GetValue() string {
function init (line 64) | func init() {
constant _ (line 74) | _ = grpc.SupportPackageIsVersion4
type PubsubServiceClient (line 79) | type PubsubServiceClient interface
type pubsubServiceClient (line 84) | type pubsubServiceClient struct
method Publish (line 92) | func (c *pubsubServiceClient) Publish(ctx context.Context, in *String,...
method Subscribe (line 101) | func (c *pubsubServiceClient) Subscribe(ctx context.Context, in *Strin...
function NewPubsubServiceClient (line 88) | func NewPubsubServiceClient(cc *grpc.ClientConn) PubsubServiceClient {
type PubsubService_SubscribeClient (line 116) | type PubsubService_SubscribeClient interface
type pubsubServiceSubscribeClient (line 121) | type pubsubServiceSubscribeClient struct
method Recv (line 125) | func (x *pubsubServiceSubscribeClient) Recv() (*String, error) {
type PubsubServiceServer (line 134) | type PubsubServiceServer interface
function RegisterPubsubServiceServer (line 139) | func RegisterPubsubServiceServer(s *grpc.Server, srv PubsubServiceServer) {
function _PubsubService_Publish_Handler (line 143) | func _PubsubService_Publish_Handler(srv interface{}, ctx context.Context...
function _PubsubService_Subscribe_Handler (line 161) | func _PubsubService_Subscribe_Handler(srv interface{}, stream grpc.Serve...
type PubsubService_SubscribeServer (line 169) | type PubsubService_SubscribeServer interface
type pubsubServiceSubscribeServer (line 174) | type pubsubServiceSubscribeServer struct
method Send (line 178) | func (x *pubsubServiceSubscribeServer) Send(m *String) error {
function init (line 201) | func init() { proto.RegisterFile("pubsubservice.proto", fileDescriptor_p...
FILE: examples/ch4.4/3/server/main.go
type PubsubService (line 16) | type PubsubService struct
method Publish (line 26) | func (p *PubsubService) Publish(
method Subscribe (line 36) | func (p *PubsubService) Subscribe(
function NewPubsubService (line 20) | func NewPubsubService() *PubsubService {
function main (line 60) | func main() {
FILE: examples/ch4.4/basic/client/hello.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type String (line 26) | type String struct
method Reset (line 33) | func (m *String) Reset() { *m = String{} }
method String (line 34) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 35) | func (*String) ProtoMessage() {}
method Descriptor (line 36) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 45) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 57) | func (m *String) GetValue() string {
function init (line 64) | func init() {
constant _ (line 74) | _ = grpc.SupportPackageIsVersion4
type HelloServiceClient (line 79) | type HelloServiceClient interface
type helloServiceClient (line 84) | type helloServiceClient struct
method Hello (line 92) | func (c *helloServiceClient) Hello(ctx context.Context, in *String, op...
method Channel (line 101) | func (c *helloServiceClient) Channel(ctx context.Context, opts ...grpc...
function NewHelloServiceClient (line 88) | func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
type HelloService_ChannelClient (line 110) | type HelloService_ChannelClient interface
type helloServiceChannelClient (line 116) | type helloServiceChannelClient struct
method Send (line 120) | func (x *helloServiceChannelClient) Send(m *String) error {
method Recv (line 124) | func (x *helloServiceChannelClient) Recv() (*String, error) {
type HelloServiceServer (line 133) | type HelloServiceServer interface
function RegisterHelloServiceServer (line 138) | func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
function _HelloService_Hello_Handler (line 142) | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, d...
function _HelloService_Channel_Handler (line 160) | func _HelloService_Channel_Handler(srv interface{}, stream grpc.ServerSt...
type HelloService_ChannelServer (line 164) | type HelloService_ChannelServer interface
type helloServiceChannelServer (line 170) | type helloServiceChannelServer struct
method Send (line 174) | func (x *helloServiceChannelServer) Send(m *String) error {
method Recv (line 178) | func (x *helloServiceChannelServer) Recv() (*String, error) {
function init (line 206) | func init() { proto.RegisterFile("hello.proto", fileDescriptor_hello_890...
FILE: examples/ch4.4/basic/client/main.go
type HelloServiceImpl (line 14) | type HelloServiceImpl struct
method Hello (line 16) | func (p *HelloServiceImpl) Hello(ctx context.Context, args *String) (*...
method Channel (line 21) | func (p *HelloServiceImpl) Channel(stream HelloService_ChannelServer) ...
function main (line 41) | func main() {
function startGrpcServer (line 46) | func startGrpcServer() {
function doClientWork (line 58) | func doClientWork() {
FILE: examples/ch4.4/grpc-pubsub/clientPub/clientPub.go
function main (line 12) | func main() {
FILE: examples/ch4.4/grpc-pubsub/clientSub/clientSub.go
function main (line 14) | func main() {
FILE: examples/ch4.4/grpc-pubsub/pubsubservice/pubsubservice.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type String (line 26) | type String struct
method Reset (line 33) | func (m *String) Reset() { *m = String{} }
method String (line 34) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 35) | func (*String) ProtoMessage() {}
method Descriptor (line 36) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 45) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 57) | func (m *String) GetValue() string {
function init (line 64) | func init() {
constant _ (line 74) | _ = grpc.SupportPackageIsVersion4
type PubsubServiceClient (line 79) | type PubsubServiceClient interface
type pubsubServiceClient (line 84) | type pubsubServiceClient struct
method Publish (line 92) | func (c *pubsubServiceClient) Publish(ctx context.Context, in *String,...
method Subscribe (line 101) | func (c *pubsubServiceClient) Subscribe(ctx context.Context, in *Strin...
function NewPubsubServiceClient (line 88) | func NewPubsubServiceClient(cc *grpc.ClientConn) PubsubServiceClient {
type PubsubService_SubscribeClient (line 116) | type PubsubService_SubscribeClient interface
type pubsubServiceSubscribeClient (line 121) | type pubsubServiceSubscribeClient struct
method Recv (line 125) | func (x *pubsubServiceSubscribeClient) Recv() (*String, error) {
type PubsubServiceServer (line 134) | type PubsubServiceServer interface
function RegisterPubsubServiceServer (line 139) | func RegisterPubsubServiceServer(s *grpc.Server, srv PubsubServiceServer) {
function _PubsubService_Publish_Handler (line 143) | func _PubsubService_Publish_Handler(srv interface{}, ctx context.Context...
function _PubsubService_Subscribe_Handler (line 161) | func _PubsubService_Subscribe_Handler(srv interface{}, stream grpc.Serve...
type PubsubService_SubscribeServer (line 169) | type PubsubService_SubscribeServer interface
type pubsubServiceSubscribeServer (line 174) | type pubsubServiceSubscribeServer struct
method Send (line 178) | func (x *pubsubServiceSubscribeServer) Send(m *String) error {
function init (line 201) | func init() { proto.RegisterFile("pubsubservice.proto", fileDescriptor_p...
FILE: examples/ch4.4/grpc-pubsub/server/server.go
type PubsubService (line 16) | type PubsubService struct
method Publish (line 26) | func (p *PubsubService) Publish(
method Subscribe (line 36) | func (p *PubsubService) Subscribe(
function NewPubsubService (line 20) | func NewPubsubService() *PubsubService {
function main (line 60) | func main() {
FILE: examples/ch4.5/on-web/helloworld.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type HelloRequest (line 26) | type HelloRequest struct
method Reset (line 33) | func (m *HelloRequest) Reset() { *m = HelloRequest{} }
method String (line 34) | func (m *HelloRequest) String() string { return proto.CompactTextStrin...
method ProtoMessage (line 35) | func (*HelloRequest) ProtoMessage() {}
method Descriptor (line 36) | func (*HelloRequest) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]by...
method XXX_Merge (line 45) | func (dst *HelloRequest) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *HelloRequest) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *HelloRequest) XXX_DiscardUnknown() {
method GetName (line 57) | func (m *HelloRequest) GetName() string {
type HelloReply (line 64) | type HelloReply struct
method Reset (line 71) | func (m *HelloReply) Reset() { *m = HelloReply{} }
method String (line 72) | func (m *HelloReply) String() string { return proto.CompactTextString(...
method ProtoMessage (line 73) | func (*HelloReply) ProtoMessage() {}
method Descriptor (line 74) | func (*HelloReply) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 77) | func (m *HelloReply) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 80) | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte...
method XXX_Merge (line 83) | func (dst *HelloReply) XXX_Merge(src proto.Message) {
method XXX_Size (line 86) | func (m *HelloReply) XXX_Size() int {
method XXX_DiscardUnknown (line 89) | func (m *HelloReply) XXX_DiscardUnknown() {
method GetMessage (line 95) | func (m *HelloReply) GetMessage() string {
function init (line 102) | func init() {
constant _ (line 113) | _ = grpc.SupportPackageIsVersion4
type GreeterClient (line 118) | type GreeterClient interface
type greeterClient (line 122) | type greeterClient struct
method SayHello (line 130) | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest...
function NewGreeterClient (line 126) | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
type GreeterServer (line 140) | type GreeterServer interface
function RegisterGreeterServer (line 144) | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
function _Greeter_SayHello_Handler (line 148) | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec...
function init (line 179) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.5/on-web/main.go
type myGrpcServer (line 17) | type myGrpcServer struct
method SayHello (line 19) | func (s *myGrpcServer) SayHello(ctx context.Context, in *HelloRequest)...
function main (line 23) | func main() {
function startServer (line 30) | func startServer() {
function doClientWork (line 56) | func doClientWork() {
FILE: examples/ch4.5/panic-and-log/helloworld.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type HelloRequest (line 26) | type HelloRequest struct
method Reset (line 33) | func (m *HelloRequest) Reset() { *m = HelloRequest{} }
method String (line 34) | func (m *HelloRequest) String() string { return proto.CompactTextStrin...
method ProtoMessage (line 35) | func (*HelloRequest) ProtoMessage() {}
method Descriptor (line 36) | func (*HelloRequest) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]by...
method XXX_Merge (line 45) | func (dst *HelloRequest) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *HelloRequest) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *HelloRequest) XXX_DiscardUnknown() {
method GetName (line 57) | func (m *HelloRequest) GetName() string {
type HelloReply (line 64) | type HelloReply struct
method Reset (line 71) | func (m *HelloReply) Reset() { *m = HelloReply{} }
method String (line 72) | func (m *HelloReply) String() string { return proto.CompactTextString(...
method ProtoMessage (line 73) | func (*HelloReply) ProtoMessage() {}
method Descriptor (line 74) | func (*HelloReply) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 77) | func (m *HelloReply) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 80) | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte...
method XXX_Merge (line 83) | func (dst *HelloReply) XXX_Merge(src proto.Message) {
method XXX_Size (line 86) | func (m *HelloReply) XXX_Size() int {
method XXX_DiscardUnknown (line 89) | func (m *HelloReply) XXX_DiscardUnknown() {
method GetMessage (line 95) | func (m *HelloReply) GetMessage() string {
function init (line 102) | func init() {
constant _ (line 113) | _ = grpc.SupportPackageIsVersion4
type GreeterClient (line 118) | type GreeterClient interface
type greeterClient (line 122) | type greeterClient struct
method SayHello (line 130) | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest...
function NewGreeterClient (line 126) | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
type GreeterServer (line 140) | type GreeterServer interface
function RegisterGreeterServer (line 144) | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
function _Greeter_SayHello_Handler (line 148) | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec...
function init (line 179) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.5/panic-and-log/main.go
type myGrpcServer (line 15) | type myGrpcServer struct
method SayHello (line 17) | func (s *myGrpcServer) SayHello(ctx context.Context, in *HelloRequest)...
function main (line 22) | func main() {
function filter (line 29) | func filter(
function startServer (line 47) | func startServer() {
function doClientWork (line 61) | func doClientWork() {
FILE: examples/ch4.5/tls/helloworld.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type HelloRequest (line 26) | type HelloRequest struct
method Reset (line 33) | func (m *HelloRequest) Reset() { *m = HelloRequest{} }
method String (line 34) | func (m *HelloRequest) String() string { return proto.CompactTextStrin...
method ProtoMessage (line 35) | func (*HelloRequest) ProtoMessage() {}
method Descriptor (line 36) | func (*HelloRequest) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]by...
method XXX_Merge (line 45) | func (dst *HelloRequest) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *HelloRequest) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *HelloRequest) XXX_DiscardUnknown() {
method GetName (line 57) | func (m *HelloRequest) GetName() string {
type HelloReply (line 64) | type HelloReply struct
method Reset (line 71) | func (m *HelloReply) Reset() { *m = HelloReply{} }
method String (line 72) | func (m *HelloReply) String() string { return proto.CompactTextString(...
method ProtoMessage (line 73) | func (*HelloReply) ProtoMessage() {}
method Descriptor (line 74) | func (*HelloReply) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 77) | func (m *HelloReply) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 80) | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte...
method XXX_Merge (line 83) | func (dst *HelloReply) XXX_Merge(src proto.Message) {
method XXX_Size (line 86) | func (m *HelloReply) XXX_Size() int {
method XXX_DiscardUnknown (line 89) | func (m *HelloReply) XXX_DiscardUnknown() {
method GetMessage (line 95) | func (m *HelloReply) GetMessage() string {
function init (line 102) | func init() {
constant _ (line 113) | _ = grpc.SupportPackageIsVersion4
type GreeterClient (line 118) | type GreeterClient interface
type greeterClient (line 122) | type greeterClient struct
method SayHello (line 130) | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest...
function NewGreeterClient (line 126) | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
type GreeterServer (line 140) | type GreeterServer interface
function RegisterGreeterServer (line 144) | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
function _Greeter_SayHello_Handler (line 148) | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec...
function init (line 179) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.5/tls/main.go
type myGrpcServer (line 30) | type myGrpcServer struct
method SayHello (line 32) | func (s *myGrpcServer) SayHello(ctx context.Context, in *HelloRequest)...
function main (line 36) | func main() {
function startServer (line 43) | func startServer() {
function doClientWork (line 80) | func doClientWork() {
FILE: examples/ch4.5/tok/helloworld.pb.go
constant _ (line 24) | _ = proto.ProtoPackageIsVersion2
type HelloRequest (line 26) | type HelloRequest struct
method Reset (line 33) | func (m *HelloRequest) Reset() { *m = HelloRequest{} }
method String (line 34) | func (m *HelloRequest) String() string { return proto.CompactTextStrin...
method ProtoMessage (line 35) | func (*HelloRequest) ProtoMessage() {}
method Descriptor (line 36) | func (*HelloRequest) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 39) | func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 42) | func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]by...
method XXX_Merge (line 45) | func (dst *HelloRequest) XXX_Merge(src proto.Message) {
method XXX_Size (line 48) | func (m *HelloRequest) XXX_Size() int {
method XXX_DiscardUnknown (line 51) | func (m *HelloRequest) XXX_DiscardUnknown() {
method GetName (line 57) | func (m *HelloRequest) GetName() string {
type HelloReply (line 64) | type HelloReply struct
method Reset (line 71) | func (m *HelloReply) Reset() { *m = HelloReply{} }
method String (line 72) | func (m *HelloReply) String() string { return proto.CompactTextString(...
method ProtoMessage (line 73) | func (*HelloReply) ProtoMessage() {}
method Descriptor (line 74) | func (*HelloReply) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 77) | func (m *HelloReply) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 80) | func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte...
method XXX_Merge (line 83) | func (dst *HelloReply) XXX_Merge(src proto.Message) {
method XXX_Size (line 86) | func (m *HelloReply) XXX_Size() int {
method XXX_DiscardUnknown (line 89) | func (m *HelloReply) XXX_DiscardUnknown() {
method GetMessage (line 95) | func (m *HelloReply) GetMessage() string {
function init (line 102) | func init() {
constant _ (line 113) | _ = grpc.SupportPackageIsVersion4
type GreeterClient (line 118) | type GreeterClient interface
type greeterClient (line 122) | type greeterClient struct
method SayHello (line 130) | func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest...
function NewGreeterClient (line 126) | func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
type GreeterServer (line 140) | type GreeterServer interface
function RegisterGreeterServer (line 144) | func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
function _Greeter_SayHello_Handler (line 148) | func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec...
function init (line 179) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.5/tok/main.go
type myGrpcServer (line 19) | type myGrpcServer struct
method SayHello (line 21) | func (s *myGrpcServer) SayHello(ctx context.Context, in *HelloRequest)...
type Authentication (line 46) | type Authentication struct
method GetRequestMetadata (line 51) | func (a *Authentication) GetRequestMetadata(context.Context, ...string...
method RequireTransportSecurity (line 54) | func (a *Authentication) RequireTransportSecurity() bool {
function main (line 58) | func main() {
function startServer (line 65) | func startServer() {
function doClientWork (line 79) | func doClientWork() {
FILE: examples/ch4.6/pb2-default-value/helloworld.pb.go
constant _ (line 20) | _ = proto.ProtoPackageIsVersion2
type Message (line 22) | type Message struct
method Reset (line 30) | func (m *Message) Reset() { *m = Message{} }
method String (line 31) | func (m *Message) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 32) | func (*Message) ProtoMessage() {}
method Descriptor (line 33) | func (*Message) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 36) | func (m *Message) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 39) | func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, e...
method XXX_Merge (line 42) | func (dst *Message) XXX_Merge(src proto.Message) {
method XXX_Size (line 45) | func (m *Message) XXX_Size() int {
method XXX_DiscardUnknown (line 48) | func (m *Message) XXX_DiscardUnknown() {
method GetName (line 54) | func (m *Message) GetName() string {
method GetAge (line 61) | func (m *Message) GetAge() int32 {
function init (line 86) | func init() {
function init (line 92) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.6/rest/helloworld.pb.go
constant _ (line 25) | _ = proto.ProtoPackageIsVersion2
type StringMessage (line 27) | type StringMessage struct
method Reset (line 34) | func (m *StringMessage) Reset() { *m = StringMessage{} }
method String (line 35) | func (m *StringMessage) String() string { return proto.CompactTextStri...
method ProtoMessage (line 36) | func (*StringMessage) ProtoMessage() {}
method Descriptor (line 37) | func (*StringMessage) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 40) | func (m *StringMessage) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 43) | func (m *StringMessage) XXX_Marshal(b []byte, deterministic bool) ([]b...
method XXX_Merge (line 46) | func (dst *StringMessage) XXX_Merge(src proto.Message) {
method XXX_Size (line 49) | func (m *StringMessage) XXX_Size() int {
method XXX_DiscardUnknown (line 52) | func (m *StringMessage) XXX_DiscardUnknown() {
method GetValue (line 58) | func (m *StringMessage) GetValue() string {
function init (line 65) | func init() {
constant _ (line 75) | _ = grpc.SupportPackageIsVersion4
type RestServiceClient (line 80) | type RestServiceClient interface
type restServiceClient (line 85) | type restServiceClient struct
method Get (line 93) | func (c *restServiceClient) Get(ctx context.Context, in *StringMessage...
method Post (line 102) | func (c *restServiceClient) Post(ctx context.Context, in *StringMessag...
function NewRestServiceClient (line 89) | func NewRestServiceClient(cc *grpc.ClientConn) RestServiceClient {
type RestServiceServer (line 112) | type RestServiceServer interface
function RegisterRestServiceServer (line 117) | func RegisterRestServiceServer(s *grpc.Server, srv RestServiceServer) {
function _RestService_Get_Handler (line 121) | func _RestService_Get_Handler(srv interface{}, ctx context.Context, dec ...
function _RestService_Post_Handler (line 139) | func _RestService_Post_Handler(srv interface{}, ctx context.Context, dec...
function init (line 174) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.6/rest/helloworld.pb.gw.go
function request_RestService_Get_0 (line 31) | func request_RestService_Get_0(ctx context.Context, marshaler runtime.Ma...
function request_RestService_Post_0 (line 58) | func request_RestService_Post_0(ctx context.Context, marshaler runtime.M...
function RegisterRestServiceHandlerFromEndpoint (line 73) | func RegisterRestServiceHandlerFromEndpoint(ctx context.Context, mux *ru...
function RegisterRestServiceHandler (line 98) | func RegisterRestServiceHandler(ctx context.Context, mux *runtime.ServeM...
function RegisterRestServiceHandlerClient (line 107) | func RegisterRestServiceHandlerClient(ctx context.Context, mux *runtime....
FILE: examples/ch4.6/rest/main.go
type myGrpcServer (line 20) | type myGrpcServer struct
method Get (line 22) | func (s *myGrpcServer) Get(ctx context.Context, in *StringMessage) (*S...
method Post (line 25) | func (s *myGrpcServer) Post(ctx context.Context, in *StringMessage) (*...
function run (line 29) | func run() error {
function main (line 50) | func main() {
function startGrpcServer (line 61) | func startGrpcServer() {
FILE: examples/ch4.6/validators/helloworld.pb.go
constant _ (line 20) | _ = proto.ProtoPackageIsVersion2
type Message (line 22) | type Message struct
method Reset (line 30) | func (m *Message) Reset() { *m = Message{} }
method String (line 31) | func (m *Message) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 32) | func (*Message) ProtoMessage() {}
method Descriptor (line 33) | func (*Message) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 36) | func (m *Message) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 39) | func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, e...
method XXX_Merge (line 42) | func (dst *Message) XXX_Merge(src proto.Message) {
method XXX_Size (line 45) | func (m *Message) XXX_Size() int {
method XXX_DiscardUnknown (line 48) | func (m *Message) XXX_DiscardUnknown() {
method GetImportantString (line 54) | func (m *Message) GetImportantString() string {
method GetAge (line 61) | func (m *Message) GetAge() int32 {
function init (line 68) | func init() {
function init (line 72) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.6/validators/helloworld.validator.pb.go
method Validate (line 29) | func (this *Message) Validate() error {
FILE: examples/ch4.7/pb-option/helloworld.pb.go
constant _ (line 25) | _ = proto.ProtoPackageIsVersion2
type Message (line 27) | type Message struct
method Reset (line 34) | func (m *Message) Reset() { *m = Message{} }
method String (line 35) | func (m *Message) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 36) | func (*Message) ProtoMessage() {}
method Descriptor (line 37) | func (*Message) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 40) | func (m *Message) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 43) | func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, e...
method XXX_Merge (line 46) | func (dst *Message) XXX_Merge(src proto.Message) {
method XXX_Size (line 49) | func (m *Message) XXX_Size() int {
method XXX_DiscardUnknown (line 52) | func (m *Message) XXX_DiscardUnknown() {
method GetName (line 58) | func (m *Message) GetName() string {
type String (line 65) | type String struct
method Reset (line 72) | func (m *String) Reset() { *m = String{} }
method String (line 73) | func (m *String) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 74) | func (*String) ProtoMessage() {}
method Descriptor (line 75) | func (*String) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 78) | func (m *String) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 81) | func (m *String) XXX_Marshal(b []byte, deterministic bool) ([]byte, er...
method XXX_Merge (line 84) | func (dst *String) XXX_Merge(src proto.Message) {
method XXX_Size (line 87) | func (m *String) XXX_Size() int {
method XXX_DiscardUnknown (line 90) | func (m *String) XXX_DiscardUnknown() {
method GetValue (line 96) | func (m *String) GetValue() string {
function init (line 148) | func init() {
constant _ (line 164) | _ = grpc.SupportPackageIsVersion4
type HelloServiceClient (line 169) | type HelloServiceClient interface
type helloServiceClient (line 173) | type helloServiceClient struct
method Hello (line 181) | func (c *helloServiceClient) Hello(ctx context.Context, in *String, op...
function NewHelloServiceClient (line 177) | func NewHelloServiceClient(cc *grpc.ClientConn) HelloServiceClient {
type HelloServiceServer (line 191) | type HelloServiceServer interface
function RegisterHelloServiceServer (line 195) | func RegisterHelloServiceServer(s *grpc.Server, srv HelloServiceServer) {
function _HelloService_Hello_Handler (line 199) | func _HelloService_Hello_Handler(srv interface{}, ctx context.Context, d...
function init (line 230) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: examples/ch4.7/pb-option/main/helloworld.pb.go
constant _ (line 20) | _ = proto.ProtoPackageIsVersion2
type Message0 (line 22) | type Message0 struct
method Reset (line 30) | func (m *Message0) Reset() { *m = Message0{} }
method String (line 31) | func (m *Message0) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 32) | func (*Message0) ProtoMessage() {}
method Descriptor (line 33) | func (*Message0) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 36) | func (m *Message0) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 39) | func (m *Message0) XXX_Marshal(b []byte, deterministic bool) ([]byte, ...
method XXX_Merge (line 42) | func (dst *Message0) XXX_Merge(src proto.Message) {
method XXX_Size (line 45) | func (m *Message0) XXX_Size() int {
method XXX_DiscardUnknown (line 48) | func (m *Message0) XXX_DiscardUnknown() {
method GetName (line 57) | func (m *Message0) GetName() string {
method GetAge (line 64) | func (m *Message0) GetAge() int32 {
constant Default_Message0_Name (line 54) | Default_Message0_Name string = "gopher"
constant Default_Message0_Age (line 55) | Default_Message0_Age int32 = 10
type Message2 (line 71) | type Message2 struct
method Reset (line 79) | func (m *Message2) Reset() { *m = Message2{} }
method String (line 80) | func (m *Message2) String() string { return proto.CompactTextString(m) }
method ProtoMessage (line 81) | func (*Message2) ProtoMessage() {}
method Descriptor (line 82) | func (*Message2) Descriptor() ([]byte, []int) {
method XXX_Unmarshal (line 85) | func (m *Message2) XXX_Unmarshal(b []byte) error {
method XXX_Marshal (line 88) | func (m *Message2) XXX_Marshal(b []byte, deterministic bool) ([]byte, ...
method XXX_Merge (line 91) | func (dst *Message2) XXX_Merge(src proto.Message) {
method XXX_Size (line 94) | func (m *Message2) XXX_Size() int {
method XXX_DiscardUnknown (line 97) | func (m *Message2) XXX_DiscardUnknown() {
method GetName (line 103) | func (m *Message2) GetName() string {
method GetAge (line 110) | func (m *Message2) GetAge() int32 {
function init (line 144) | func init() {
function init (line 152) | func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_hell...
FILE: gen_contributors.go
type User (line 24) | type User struct
function main (line 29) | func main() {
function genTableHeader (line 48) | func genTableHeader(users []User) string {
function genTableHeaderSepLine (line 60) | func genTableHeaderSepLine() string {
function genTableElemLines (line 63) | func genTableElemLines(users []User) string {
Condensed preview — 459 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,197K chars).
[
{
"path": ".editorconfig",
"chars": 610,
"preview": "# Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.\n# Use of this source code is governed by a Apache\n# li"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 18,
"preview": "提示:哪一章节的问题,建议如何修改\n"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 20,
"preview": "提示:解决了什么问题,也可以讲下理由。\n"
},
{
"path": ".gitignore",
"chars": 26,
"preview": "/_book\n/book\n\n*.out*\n_zz*\n"
},
{
"path": ".nojekyll",
"chars": 0,
"preview": ""
},
{
"path": "LICENSE",
"chars": 1313,
"preview": "BSD 2-Clause License\n\nCopyright (c) 2017, chai2010\nAll rights reserved.\n\nRedistribution and use in source and binary for"
},
{
"path": "Makefile",
"chars": 727,
"preview": "# Copyright 2016 <chaishushan{AT}gmail.com>. All rights reserved.\n# Use of this source code is governed by a BSD-style\n#"
},
{
"path": "README.md",
"chars": 7917,
"preview": "# Go语言高级编程 (Advanced Go Programming)\n\n- *凹语言(专为 WebAssembly 设计): https://github.com/wa-lang/wa*\n- *WaBook(Go语言实现的MD电子书构建"
},
{
"path": "SUMMARY.md",
"chars": 2798,
"preview": "# 目录\n\n[Go语言高级编程](index.md)\n[前言](preface.md)\n\n- [语言基础](ch1-basic/readme.md)\n - [Go语言创世纪](ch1-basic/ch1-01-genesis.md)\n "
},
{
"path": "appendix/appendix-a-trap.md",
"chars": 5178,
"preview": "# 附录A:Go语言常见坑\n\n这里列举的Go语言常见坑都是符合Go语言语法的,可以正常的编译,但是可能是运行结果错误,或者是有资源泄漏的风险。\n\n## 可变参数是空接口类型\n\n当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展"
},
{
"path": "appendix/appendix-b-gems.md",
"chars": 2400,
"preview": "# 附录B:有趣的代码片段\n\n这里收集一些比较有意思的Go程序片段。\n\n## 自重写程序\n\nUNIX/Go语言之父 Ken Thompson 在1983年的图灵奖演讲 Reflections on Trusting Trust 就给出了一个"
},
{
"path": "appendix/appendix-c-author.md",
"chars": 380,
"preview": "# 附录C:作者简介\n\n- 柴树杉(Github [@chai2010](https://github.com/chai2010); Twitter [@chaishushan](https://twitter.com/chaishusha"
},
{
"path": "appendix/readme.md",
"chars": 70,
"preview": "# 附录\n\n附录部分主要包含量三个部分:第一部分是摘录量一些Go语言常见的坑和解决方案;第二部分是一些有趣的代码片段;第三部分是作者信息。\n"
},
{
"path": "book.ini",
"chars": 1260,
"preview": "# https://giscus.app\n\n[book]\ntitle = \"Go语言高级编程\"\nauthors = [\"柴树杉\", \"曹春晖\"]\ndescription = \"Go语言高级编程\"\nlanguage = \"zh\"\nmultil"
},
{
"path": "ch1-basic/ch1-01-genesis.md",
"chars": 3915,
"preview": "# 1.1 Go 语言创世纪\n\nGo 语言最初由 Google 公司的 *Robert Griesemer*、*Ken Thompson* 和 *Rob Pike* 三个大牛于 2007 年开始设计发明,设计新语言的最初的洪荒之力来自于对超"
},
{
"path": "ch1-basic/ch1-02-hello-revolution.md",
"chars": 9005,
"preview": "# 1.2 Hello, World 的革命\n\n在创世纪章节中我们简单介绍了 Go 语言的演化基因族谱,对其中来自于贝尔实验室的特有并发编程基因做了重点介绍,最后引出了 Go 语言版的“Hello, World”程序。其实“Hello, W"
},
{
"path": "ch1-basic/ch1-03-array-string-and-slice.md",
"chars": 16944,
"preview": "# 1.3 数组、字符串和切片\n\n在主流的编程语言中数组及其相关的数据结构是使用得最为频繁的,只有在它(们)不能满足时才会考虑链表、hash 表(hash 表可以看作是数组和链表的混合体)和更复杂的自定义数据结构。\n\nGo 语言中数组、字符"
},
{
"path": "ch1-basic/ch1-04-func-method-interface.md",
"chars": 13988,
"preview": "# 1.4 函数、方法和接口\n\n函数对应操作序列,是程序的基本组成元素。Go 语言中的函数有具名和匿名之分:具名函数一般对应于包级的函数,是匿名函数的一种特例,当匿名函数引用了外部作用域中的变量时就成了闭包函数,闭包函数是函数式编程语言的核"
},
{
"path": "ch1-basic/ch1-05-mem.md",
"chars": 10262,
"preview": "# 1.5 面向并发的内存模型\n\n在早期,CPU 都是以单核的形式顺序执行机器指令。Go 语言的祖先 C 语言正是这种顺序编程语言的代表。顺序编程语言中的顺序是指:所有的指令都是以串行的方式执行,在相同的时刻有且仅有一个 CPU 在顺序执行"
},
{
"path": "ch1-basic/ch1-06-goroutine.md",
"chars": 18330,
"preview": "# 1.6 常见的并发模式\n\nGo 语言最吸引人的地方是它内建的并发支持。Go 语言并发体系的理论是 _C.A.R Hoare_ 在 1978 年提出的 CSP(Communicating Sequential Process,通讯顺序进程"
},
{
"path": "ch1-basic/ch1-07-error-and-panic.md",
"chars": 9113,
"preview": "# 1.7 错误和异常\n\n错误处理是每个编程语言都要考虑的一个重要话题。在 Go 语言的错误处理中,错误是软件包 API 和应用程序用户界面的一个重要组成部分。\n\n在程序中总有一部分函数总是要求必须能够成功的运行。比如 `strconv.I"
},
{
"path": "ch1-basic/ch1-08-ext.md",
"chars": 470,
"preview": "## 1.8 补充说明\n\n本书定位是 Go 语言进阶图书,因此读者需要有一定的 Go 语言基础。如果对 Go 语言不太了解,作者推荐通过以下资料开始学习 Go 语言。首先是安装 Go 语言环境,然后通过 `go tool tour` 命令打"
},
{
"path": "ch1-basic/readme.md",
"chars": 327,
"preview": "# 第 1 章 语言基础\n\n*我不知道,你过去 10 年为什么不快乐。但相信我,抛掉过去的沉重,使用 Go 语言,体会最初的快乐!——469856321*\n\n*搬砖民工也会建成自己的罗马帝国。——小张*\n\n---\n\n本章首先简要介绍 Go "
},
{
"path": "ch2-cgo/ch2-01-hello-cgo.md",
"chars": 5108,
"preview": "# 2.1 快速入门\n\n本节我们将通过一系列由浅入深的小例子来快速掌握 CGO 的基本用法。\n\n## 2.1.1 最简 CGO 程序\n\n真实的 CGO 程序一般都比较复杂。不过我们可以由浅入深,一个最简的 CGO 程序该是什么样的呢?要构造"
},
{
"path": "ch2-cgo/ch2-02-basic.md",
"chars": 4669,
"preview": "# 2.2 CGO 基础\n\n要使用 CGO 特性,需要安装 C/C++ 构建工具链,在 macOS 和 Linux 下是要安装 GCC,在 windows 下是需要安装 MinGW 工具。同时需要保证环境变量 `CGO_ENABLED` 被"
},
{
"path": "ch2-cgo/ch2-03-cgo-types.md",
"chars": 11717,
"preview": "# 2.3 类型转换\n\n最初 CGO 是为了达到方便从 Go 语言函数调用 C 语言函数(用 C 语言实现 Go 语言声明的函数)以复用 C 语言资源这一目的而出现的(因为 C 语言还会涉及回调函数,自然也会涉及到从 C 语言函数调用 Go"
},
{
"path": "ch2-cgo/ch2-04-func.md",
"chars": 3133,
"preview": "# 2.4 函数调用\n\n函数是 C 语言编程的核心,通过 CGO 技术我们不仅仅可以在 Go 语言中调用 C 语言函数,也可以将 Go 语言函数导出为 C 语言函数。\n\n## 2.4.1 Go 调用 C 函数\n\n对于一个启用 CGO 特性的"
},
{
"path": "ch2-cgo/ch2-05-internal.md",
"chars": 5718,
"preview": "# 2.5 内部机制\n\n对于刚刚接触 CGO 用户来说,CGO 的很多特性类似魔法。CGO 特性主要是通过一个叫 cgo 的命令行工具来辅助输出 Go 和 C 之间的桥接代码。本节我们尝试从生成的代码分析 Go 语言和 C 语言函数直接相互"
},
{
"path": "ch2-cgo/ch2-06-qsort.md",
"chars": 7630,
"preview": "# 2.6 实战: 封装 qsort\n\nqsort 快速排序函数是 C 语言的高阶函数,支持用于自定义排序比较函数,可以对任意类型的数组进行排序。本节我们尝试基于 C 语言的 qsort 函数封装一个 Go 语言版本的 qsort 函数。\n"
},
{
"path": "ch2-cgo/ch2-07-memory.md",
"chars": 7390,
"preview": "# 2.7 CGO 内存模型\n\nCGO 是架接 Go 语言和 C 语言的桥梁,它使二者在二进制接口层面实现了互通,但是我们要注意因两种语言的内存模型的差异而可能引起的问题。如果在 CGO 处理的跨语言函数调用时涉及到了指针的传递,则可能会出"
},
{
"path": "ch2-cgo/ch2-08-class.md",
"chars": 10109,
"preview": "# 2.8 C++ 类包装\n\nCGO 是 C 语言和 Go 语言之间的桥梁,原则上无法直接支持 C++ 的类。CGO 不支持 C++ 语法的根本原因是 C++ 至今为止还没有一个二进制接口规范 (ABI)。一个 C++ 类的构造函数在编译为"
},
{
"path": "ch2-cgo/ch2-09-static-shared-lib.md",
"chars": 7116,
"preview": "# 2.9 静态库和动态库\n\nCGO 在使用 C/C++ 资源的时候一般有三种形式:直接使用源码;链接静态库;链接动态库。直接使用源码就是在 `import \"C\"` 之前的注释部分包含 C 代码,或者在当前包中包含 C/C++ 源文件。链"
},
{
"path": "ch2-cgo/ch2-10-link.md",
"chars": 3226,
"preview": "# 2.10 编译和链接参数\n\n编译和链接参数是每一个 C/C++ 程序员需要经常面对的问题。构建每一个 C/C++ 应用均需要经过编译和链接两个步骤,CGO 也是如此。\n本节我们将简要讨论 CGO 中经常用到的编译和链接参数的用法。\n\n#"
},
{
"path": "ch2-cgo/ch2-11-ext.md",
"chars": 449,
"preview": "## 2.11 补充说明\n\nCGO 是 C 语言和 Go 语言混合编程的技术,因此要想熟练地使用 CGO 需要了解这两门语言。C 语言推荐两本书:第一本是 C 语言之父编写的《C 程序设计语言》;第二本是讲述 C 语言模块化编程的《C 语言"
},
{
"path": "ch2-cgo/readme.md",
"chars": 405,
"preview": "# 第 2 章 CGO 编程\n\n*过去的经验往往是走向未来的枷锁,因为在过气技术中投入的沉没成本会阻碍人们拥抱新技术。——chai2010*\n\n*曾经一度因未能习得 C++ 令人眼花缭乱的新标准而痛苦不已;Go 语言 “少既是多” 大道至简"
},
{
"path": "ch3-asm/ch3-01-basic.md",
"chars": 7391,
"preview": "# 3.1 快速入门\n\nGo 汇编程序始终是幽灵一样的存在。我们将通过分析简单的 Go 程序输出的汇编代码,然后照猫画虎用汇编实现一个简单的输出程序。\n\n## 3.1.1 实现和声明\n\nGo 汇编语言并不是一个独立的语言,因为 Go 汇编程"
},
{
"path": "ch3-asm/ch3-02-arch.md",
"chars": 8162,
"preview": "# 3.2 计算机结构\n\n汇编语言是直面计算机的编程语言,因此理解计算机结构是掌握汇编语言的前提。当前流行的计算机基本采用的是冯·诺伊曼计算机体系结构(在某些特殊领域还有哈佛体系架构)。冯·诺依曼结构也称为普林斯顿结构,采用的是一种将程序指"
},
{
"path": "ch3-asm/ch3-03-const-and-var.md",
"chars": 8736,
"preview": "# 3.3 常量和全局变量\n\n程序中的一切变量的初始值都直接或间接地依赖常量或常量表达式生成。在 Go 语言中很多变量是默认零值初始化的,但是 Go 汇编中定义的变量最好还是手工通过常量初始化。有了常量之后,就可以衍生定义全局变量,并使用常"
},
{
"path": "ch3-asm/ch3-04-func.md",
"chars": 8125,
"preview": "# 3.4 函数\n\n终于到函数了!因为 Go 汇编语言中,可以也建议通过 Go 语言来定义全局变量,那么剩下的也就是函数了。只有掌握了汇编函数的基本用法,才能真正算是 Go 汇编语言入门。本章将简单讨论 Go 汇编中函数的定义和用法。\n\n#"
},
{
"path": "ch3-asm/ch3-05-control-flow.md",
"chars": 6395,
"preview": "# 3.5 控制流\n\n程序主要有顺序、分支和循环几种执行流程。本节主要讨论如何将 Go 语言的控制流比较直观地转译为汇编程序,或者说如何以汇编思维来编写 Go 语言代码。\n\n## 3.5.1 顺序执行\n\n顺序执行是我们比较熟悉的工作模式,类"
},
{
"path": "ch3-asm/ch3-06-func-again.md",
"chars": 11738,
"preview": "# 3.6 再论函数\n\n在前面的章节中我们已经简单讨论过 Go 的汇编函数,但是那些主要是叶子函数。叶子函数的最大特点是不会调用其他函数,也就是栈的大小是可以预期的,叶子函数也就是可以基本忽略爆栈的问题(如果已经爆了,那也是上级函数的问题)"
},
{
"path": "ch3-asm/ch3-07-hack-asm.md",
"chars": 6317,
"preview": "# 3.7 汇编语言的威力\n\n汇编语言的真正威力来自两个维度:一是突破框架限制,实现看似不可能的任务;二是突破指令限制,通过高级指令挖掘极致的性能。对于第一个问题,我们将演示如何通过 Go 汇编语言直接访问系统调用,和直接调用 C 语言函数"
},
{
"path": "ch3-asm/ch3-08-goroutine-id.md",
"chars": 7722,
"preview": "# 3.8 例子:Goroutine ID\n\n在操作系统中,每个进程都会有一个唯一的进程编号,每个线程也有自己唯一的线程编号。同样在 Go 语言中,每个 Goroutine 也有自己唯一的 Go 程编号,这个编号在 panic 等场景下经常"
},
{
"path": "ch3-asm/ch3-09-debug.md",
"chars": 10966,
"preview": "# 3.9 Delve 调试器\n\n目前 Go 语言支持 GDB、LLDB 和 Delve 几种调试器。其中 GDB 是最早支持的调试工具,LLDB 是 macOS 系统推荐的标准调试工具。但是 GDB 和 LLDB 对 Go 语言的专有特性"
},
{
"path": "ch3-asm/ch3-10-ext.md",
"chars": 934,
"preview": "## 3.10 补充说明\n\n如果是纯粹学习汇编语言,则可以从《深入理解程序设计:使用 Linux 汇编语言》开始,该书讲述了如何以 C 语言的思维实现汇编程序。如果是学习 X86 汇编,则可以从《汇编语言:基于 x86 处理器》开始,然后再"
},
{
"path": "ch3-asm/readme.md",
"chars": 609,
"preview": "# 第 3 章 Go 汇编语言\n\n*能跑就行,不行加机器。——rfyiamcool & 爱学习的孙老板*\n\n*跟对人,做对事。——Rhichy*\n\nGo 语言中很多设计思想和工具都是传承自 Plan9 操作系统,Go 汇编语言也是基于 Pl"
},
{
"path": "ch4-rpc/ch4-01-rpc-intro.md",
"chars": 8090,
"preview": "# 4.1 RPC 入门\n\nRPC 是远程过程调用的简称,是分布式系统中不同节点间流行的通信方式。在互联网时代,RPC 已经和 IPC 一样成为一个不可或缺的基础构件。因此 Go 语言的标准库也提供了一个简单的 RPC 实现,我们将以此为入"
},
{
"path": "ch4-rpc/ch4-02-pb-intro.md",
"chars": 10950,
"preview": "# 4.2 Protobuf\n\nProtobuf 是 Protocol Buffers 的简称,它是 Google 公司开发的一种数据描述语言,并于 2008 年对外开源。Protobuf 刚开源时的定位类似于 XML、JSON 等数据描述"
},
{
"path": "ch4-rpc/ch4-03-netrpc-hack.md",
"chars": 6763,
"preview": "# 4.3 玩转 RPC\n\n在不同的场景中 RPC 有着不同的需求,因此开源的社区就诞生了各种 RPC 框架。本节我们将尝试 Go 内置 RPC 框架在一些比较特殊场景的用法。\n\n## 4.3.1 客户端 RPC 的实现原理\n\nGo 语言的"
},
{
"path": "ch4-rpc/ch4-04-grpc.md",
"chars": 8435,
"preview": "# 4.4 gRPC 入门\n\ngRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。gRPC 基于 HTTP/2 协议设计,可以基于一个 HTTP/2 连接提供多个服务,对于移动设备更加友好。本节将讲述 "
},
{
"path": "ch4-rpc/ch4-05-grpc-hack.md",
"chars": 10456,
"preview": "# 4.5 gRPC 进阶\n\n作为一个基础的 RPC 框架,安全和扩展是经常遇到的问题。本节将简单介绍如何对 gRPC 进行安全认证。然后介绍通过 gRPC 的截取器特性,以及如何通过截取器优雅地实现 Token 认证、调用跟踪以及 Pan"
},
{
"path": "ch4-rpc/ch4-06-grpc-ext.md",
"chars": 7463,
"preview": "# 4.6 gRPC 和 Protobuf 扩展\n\n目前开源社区已经围绕 Protobuf 和 gRPC 开发出众多扩展,形成了庞大的生态。本节我们将简单介绍验证器和 REST 接口扩展。\n\n## 4.6.1 验证器\n\n到目前为止,我们接触"
},
{
"path": "ch4-rpc/ch4-07-pbgo.md",
"chars": 6221,
"preview": "# 4.7 pbgo: 基于 Protobuf 的框架\n\npbgo 是我们专门针对本节内容设计的较为完整的迷你框架,它基于 Protobuf 的扩展语法,通过插件自动生成 rpc 和 rest 相关代码。在本章第二节我们已经展示过如何定制一"
},
{
"path": "ch4-rpc/ch4-08-grpcurl.md",
"chars": 4032,
"preview": "\n# 4.8 grpcurl 工具\n\nProtobuf 本身具有反射功能,可以在运行时获取对象的 Proto 文件。gRPC 同样也提供了一个名为 reflection 的反射包,用于为 gRPC 服务提供查询。gRPC 官方提供了一个 C"
},
{
"path": "ch4-rpc/ch4-09-ext.md",
"chars": 286,
"preview": "## 4.9 补充说明\n\n目前专门讲述 RPC 的图书比较少。目前 Protobuf 和 gRPC 的官网都提供了详细的参考资料和例子。本章重点讲述了 Go 标准库的 RPC 和基于 Protobuf 衍生的 gRPC 框架,同时也简单展示"
},
{
"path": "ch4-rpc/readme.md",
"chars": 415,
"preview": "# 第 4 章 RPC 和 Protobuf\n\n*学习编程,重要的是什么?多练、多看、多实践!跨语言学习,掌握基础语法和语言的特性之后,实战,效率来的最快!——khlipeng*\n\nRPC 是远程过程调用的缩写(Remote Procedu"
},
{
"path": "ch5-web/ch5-01-introduction.md",
"chars": 5228,
"preview": "# 5.1 Web 开发简介\n\n因为 Go 的 `net/http` 包提供了基础的路由函数组合与丰富的功能函数。所以在社区里流行一种用 Go 编写 API 不需要框架的观点,在我们看来,如果你的项目的路由在个位数、URI 固定且不通过 U"
},
{
"path": "ch5-web/ch5-02-router.md",
"chars": 7278,
"preview": "# 5.2 router 请求路由\n\n在常见的 Web 框架中,router 是必备的组件。Go 语言圈子里 router 也时常被称为 `http` 的 multiplexer。在上一节中我们通过对 Burrow 代码的简单学习,已经知道"
},
{
"path": "ch5-web/ch5-03-middleware.md",
"chars": 7025,
"preview": "# 5.3 中间件\n\n本章将对现在流行的 Web 框架中的中间件 (middleware) 技术原理进行分析,并介绍如何使用中间件技术将业务和非业务代码功能进行解耦。\n\n## 5.3.1 代码泥潭\n\n先来看一段代码:\n\n```go\n// m"
},
{
"path": "ch5-web/ch5-04-validator.md",
"chars": 5643,
"preview": "# 5.4 validator 请求校验\n\n社区里曾经有人用 *图 5-10* 来嘲笑 PHP:\n\n\n\n*图 5-10 validator 流程*\n\n"
},
{
"path": "ch5-web/ch5-05-database.md",
"chars": 6999,
"preview": "# 5.5 Database 和数据库打交道\n\n本节将对 `db/sql` 官方标准库作一些简单分析,并介绍一些应用比较广泛的开源 ORM 和 SQL Builder。并从企业级应用开发和公司架构的角度来分析哪种技术栈对于现代的企业级应用更"
},
{
"path": "ch5-web/ch5-06-ratelimit.md",
"chars": 8056,
"preview": "# 5.6 Ratelimit 服务流量限制\n\n计算机程序可依据其瓶颈分为磁盘 IO 瓶颈型,CPU 计算瓶颈型,网络带宽瓶颈型,分布式场景下有时候也会外部系统而导致自身瓶颈。\n\nWeb 系统打交道最多的是网络,无论是接收,解析用户请求,访"
},
{
"path": "ch5-web/ch5-07-layout-of-web-project.md",
"chars": 5394,
"preview": "# 5.7 layout 常见大型 Web 项目分层\n\n流行的 Web 框架大多数是 MVC 框架,MVC 这个概念最早由 Trygve Reenskaug 在 1978 年提出,为了能够对 GUI 类型的应用进行方便扩展,将程序划分为:\n"
},
{
"path": "ch5-web/ch5-08-interface-and-web.md",
"chars": 6570,
"preview": "# 5.8 接口和表驱动开发\n\n在 Web 项目中经常会遇到外部依赖环境的变化,比如:\n\n1. 公司的老存储系统年久失修,现在已经没有人维护了,新的系统上线也没有考虑平滑迁移,但最后通牒已下,要求 N 天之内迁移完毕。\n2. 平台部门的老用"
},
{
"path": "ch5-web/ch5-09-gated-launch.md",
"chars": 5942,
"preview": "# 5.9 灰度发布和 A/B test\n\n中型的互联网公司往往有着以百万计的用户,而大型互联网公司的系统则可能要服务千万级甚至亿级的用户需求。大型系统的请求流入往往是源源不断的,任何风吹草动,都一定会有最终用户感受得到。例如你的系统在上线"
},
{
"path": "ch5-web/ch5-10-ext.md",
"chars": 237,
"preview": "# 5.10 补充说明\n\n现代的软件工程是离不开 Web 的,广义地来讲,Web 甚至可以不用非得基于 http 协议。只要是 CS 或者 BS 架构,都可以认为是 Web 系统。\n\n即使是在看起来非常封闭的游戏系统里,因为玩家们与日俱增的"
},
{
"path": "ch5-web/readme.md",
"chars": 225,
"preview": "# 第 5 章 go 和 Web\n\n*不管何种编程语言,适合自己的就是最好的。不管何种编程语言,能稳定实现业务逻辑的就是最好的。世间编程语言千千万,世间程序猿万万千,能做到深入理解并应用的就是最好的。——kenrong*\n\n本章将会阐述 G"
},
{
"path": "ch6-cloud/ch6-01-dist-id.md",
"chars": 4899,
"preview": "# 6.1 分布式 id 生成器\n\n有时我们需要能够生成类似 MySQL 自增 ID 这样不断增大,同时又不会重复的 id。以支持业务中的高并发场景。比较典型的,电商促销时,短时间内会有大量的订单涌入到系统,比如每秒 10w+。明星出轨时,"
},
{
"path": "ch6-cloud/ch6-02-lock.md",
"chars": 6509,
"preview": "# 6.2 分布式锁\n\n在单机程序并发或并行修改全局变量时,需要对修改行为加锁以创造临界区。为什么需要加锁呢?我们看看在不加锁的情况下并发计数会发生什么情况:\n\n```go\npackage main\n\nimport (\n\t\"sync\"\n)\n"
},
{
"path": "ch6-cloud/ch6-03-delay-job.md",
"chars": 2698,
"preview": "# 6.3 延时任务系统\n\n我们在做系统时,很多时候是处理实时的任务,请求来了马上就处理,然后立刻给用户以反馈。但有时也会遇到非实时的任务,比如确定的时间点发布重要公告。或者需要在用户做了一件事情的 X 分钟 / Y 小时后,对其特定动作,"
},
{
"path": "ch6-cloud/ch6-04-search-engine.md",
"chars": 8923,
"preview": "# 6.4 分布式搜索引擎\n\n在 Web 一章中,我们提到 MySQL 很脆弱。数据库系统本身要保证实时和强一致性,所以其功能设计上都是为了满足这种一致性需求。比如 write ahead log 的设计,基于 B + 树实现的索引和数据组"
},
{
"path": "ch6-cloud/ch6-05-load-balance.md",
"chars": 3868,
"preview": "# 6.5 负载均衡\n\n本节将会讨论常见的分布式系统负载均衡手段。\n\n## 6.5.1 常见的负载均衡思路\n\n如果我们不考虑均衡的话,现在有 n 个服务节点,我们完成业务流程只需要从这 n 个中挑出其中的一个。有几种思路:\n\n1. 按顺序挑"
},
{
"path": "ch6-cloud/ch6-06-config.md",
"chars": 4966,
"preview": "# 6.6 分布式配置管理\n\n在分布式系统中,常困扰我们的还有上线问题。虽然目前有一些优雅重启方案,但实际应用中可能受限于我们系统内部的运行情况而没有办法做到真正的 “优雅”。比如我们为了对去下游的流量进行限制,在内存中堆积一些数据,并对堆"
},
{
"path": "ch6-cloud/ch6-07-crawler.md",
"chars": 6884,
"preview": "# 6.7 分布式爬虫\n\n互联网时代的信息爆炸是很多人倍感头痛的问题,应接不暇的新闻、信息、视频,无孔不入地侵占着我们的碎片时间。但另一方面,在我们真正需要数据的时候,却感觉数据并不是那么容易获取的。比如我们想要分析现在人在讨论些什么,关心"
},
{
"path": "ch6-cloud/ch6-08-ext.md",
"chars": 263,
"preview": "# 6.8 补充说明\n\n分布式是很大的领域,本章中的介绍只能算是对领域的管中窥豹。因为大型系统流量大,并发高,所以往往很多朴素的方案会变得难以满足需求。人们为了解决大型系统场景中的各种问题,而开发出了各式各样的分布式系统。有些系统非常简单,"
},
{
"path": "ch6-cloud/readme.md",
"chars": 363,
"preview": "# 第 6 章 分布式系统\n\n*被别人指出问题时,别管别人能不能做到,看别人说的对不对,然后完善自己。别人能不能做到是别人的事情,自己能不能做到关系到自己能否发展的更好。——hustlihaifeng*\n\nGo 语言号称是互联网时代的 C "
},
{
"path": "chaoxi/.gitignore",
"chars": 7,
"preview": "!*.pdf\n"
},
{
"path": "chaoxi/.keep",
"chars": 0,
"preview": ""
},
{
"path": "contributors.json",
"chars": 31274,
"preview": "[\n {\n \"login\": \"chai2010\",\n \"id\": 2295542,\n \"node_id\": \"MDQ6VXNlcjIyOTU1NDI=\",\n \"avatar_url\": \"https://avat"
},
{
"path": "doc.go",
"chars": 289,
"preview": "// Copyright 2017 <chaishushan{AT}gmail.com>. All rights reserved.\n// Use of this source code is governed by a BSD-style"
},
{
"path": "docs/.gitignore",
"chars": 20,
"preview": "!x64_cheatsheet.pdf\n"
},
{
"path": "errata/README.md",
"chars": 1531,
"preview": "# 勘误·第一版第一次印刷\n\n## 1. ch3.4最后一个图有错误\n\nsum函数的`ret+24(FP)`改为`ret+16(FP)`\n\n## 2. ch3.5 控制流 - for例子有错误 #438\n\n171页第一个代码段改为:\n\n``"
},
{
"path": "errata/a.go",
"chars": 139,
"preview": "package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\", LoopAdd(100, 1, 1))\n}\n\nfunc LoopAdd(cnt"
},
{
"path": "errata/a_amd64.s",
"chars": 561,
"preview": "#include \"textflag.h\"\n\n// func LoopAdd(cnt, v0, step int) int\nTEXT ·LoopAdd(SB), NOSPLIT, $0-32\n\tMOVQ $0, BX /"
},
{
"path": "examples/README.md",
"chars": 167,
"preview": "## goproxy代理服务\n\n```\n$ export GOPROXY=https://athens.azurefd.net\n```\n\n- https://github.com/gomods/athens/releases\n- https"
},
{
"path": "examples/ch1.1/1-hello/main.go",
"chars": 121,
"preview": "// go run chai2010.cn/gobook/examples/ch1.1/1-hello\n\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Println(\"你好, 世界!\")\n}"
},
{
"path": "examples/ch1.2/1-hello-b-1972/main.b",
"chars": 111,
"preview": "main() {\n\textrn a, b, c;\n\tputchar(a); putchar(b); putchar(c);\n\tputchar('!*n');\n}\na 'hell';\nb 'o, w';\nc 'orld';\n"
},
{
"path": "examples/ch1.2/10-hello-go-200806/hello.go.txt",
"chars": 88,
"preview": "// +build ignore\n\npackage main\n\nfunc main() int {\n\tprint \"hello, world\\n\";\n\treturn 0;\n}\n"
},
{
"path": "examples/ch1.2/11-hello-go-20080627/hello.go.txt",
"chars": 73,
"preview": "// +build ignore\n\npackage main\n\nfunc main() {\n\tprint \"hello, world\\n\";\n}\n"
},
{
"path": "examples/ch1.2/12-hello-go-20080811/hello.go.txt",
"chars": 56,
"preview": "package main\n\nfunc main() {\n\tprint(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/13-hello-go-20081024/hello.go.txt",
"chars": 93,
"preview": "// +build ignore\n\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.printf(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/14-hello-go-20090115/hello.go.txt",
"chars": 93,
"preview": "// +build ignore\n\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/15-hello-go-20091211/hello.go",
"chars": 74,
"preview": "package main\n\nimport \"fmt\"\n\nfunc main() {\n\tfmt.Printf(\"hello, world\\n\")\n}\n"
},
{
"path": "examples/ch1.2/16-hello-go-v2/hello.go",
"chars": 543,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/2-hello-c-1974/hello-c-01.c",
"chars": 36,
"preview": "main()\n{\n\tprintf(\"hello, world\");\n}\n"
},
{
"path": "examples/ch1.2/3-hello-c-1978/hello-c-02.c",
"chars": 38,
"preview": "main()\n{\n\tprintf(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/4-hello-c-1988/hello-c-03.c",
"chars": 58,
"preview": "#include <stdio.h>\n\nmain()\n{\n\tprintf(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/5-hello-c-1989/hello-c-04.c",
"chars": 62,
"preview": "#include <stdio.h>\n\nmain(void)\n{\n\tprintf(\"hello, world\\n\");\n}\n"
},
{
"path": "examples/ch1.2/6-hello-newsqueak-1989/hello.newsqueak",
"chars": 32,
"preview": "print(\"Hello,\", \"World\", \"\\n\");\n"
},
{
"path": "examples/ch1.2/7-prime-newsqueak/prime.newsqueak",
"chars": 613,
"preview": "// 向管道输出从2开始的自然数序列\ncounter := prog(c:chan of int) {\n\ti := 2;\n\tfor(;;) {\n\t\tc <-= i++;\n\t}\n};\n\n// 针对listen管道获取的数列,过滤掉是prime"
},
{
"path": "examples/ch1.2/8-hello-alef-1993/hello.alef",
"chars": 286,
"preview": "#include <alef.h>\n\nvoid receive(chan(byte*) c) {\n\tbyte *s;\n\ts = <- c;\n\tprint(\"%s\\n\", s);\n\tterminate(nil);\n}\n\nvoid main(v"
},
{
"path": "examples/ch1.2/9-hello-limbo-1995/hello.limbo",
"chars": 256,
"preview": "implement Hello;\n\ninclude \"sys.m\"; sys: Sys;\ninclude \"draw.m\";\n\nHello: module\n{\n\tinit: fn(ctxt: ref Draw->Context, args:"
},
{
"path": "examples/ch1.2/xx-hello-go-asm/hello.go",
"chars": 150,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/xx-hello-go-asm/hello_amd64.s",
"chars": 446,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/xx-hello-go-cgo/hello.go",
"chars": 324,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/xx-hello-go-swig/hello.cc",
"chars": 209,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/xx-hello-go-swig/hello.go",
"chars": 213,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch1.2/xx-hello-go-swig/hello.swigcxx",
"chars": 176,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-01/main.go",
"chars": 224,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-02/main.go",
"chars": 283,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-03/hello.c",
"chars": 187,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-03/main.go",
"chars": 239,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-04/main.go",
"chars": 404,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-05/main.go",
"chars": 326,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.1/hello-06/main.go",
"chars": 378,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.10/hello-py/Makefile",
"chars": 340,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.10/hello-py/gopkg.h",
"chars": 2157,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package command-line-arguments */\n\n/* Start of preamble from import \"C"
},
{
"path": "examples/ch2.10/hello-py/main.go",
"chars": 1151,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.10/hello-py/py3-config.go",
"chars": 751,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.10/hello-so/Makefile",
"chars": 288,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.10/hello-so/_test_so.c",
"chars": 214,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.10/hello-so/hello.py",
"chars": 301,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.10/hello-so/main.go",
"chars": 274,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.10/hello-so/say-hello.h",
"chars": 1325,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package command-line-arguments */\n\n/* Start of preamble from import \"C"
},
{
"path": "examples/ch2.4/return-go-ptr/main.go",
"chars": 429,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/01-cgo-gen-files/Makefile",
"chars": 218,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/_cgo_export.c",
"chars": 400,
"preview": "/* Created by cgo - DO NOT EDIT. */\n#include <stdlib.h>\n#include \"_cgo_export.h\"\n\nextern void crosscall2(void (*fn)(void"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/_cgo_export.h",
"chars": 1462,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package main */\n\n\n#line 1 \"cgo-builtin-prolog\"\n\n#include <stddef.h> /*"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/_cgo_flags",
"chars": 13,
"preview": "_CGO_CFLAGS=\n"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/_cgo_gotypes.go",
"chars": 808,
"preview": "// Created by cgo - DO NOT EDIT\n\npackage main\n\nimport \"unsafe\"\n\nimport _ \"runtime/cgo\"\n\nimport \"syscall\"\n\nvar _ syscall."
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/_cgo_main.c",
"chars": 412,
"preview": "int main() { return 0; }\nvoid crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __SIZE_TYPE__ ctxt) { }\n_"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/hello.cgo1.go",
"chars": 207,
"preview": "// Created by cgo - DO NOT EDIT\n\n//line hello.go:1\n// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// Licens"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/hello.cgo2.c",
"chars": 1462,
"preview": "\n#line 1 \"cgo-builtin-prolog\"\n#include <stddef.h> /* for ptrdiff_t and size_t below */\n\n/* Define intgo when compiling w"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/main.cgo1.go",
"chars": 222,
"preview": "// Created by cgo - DO NOT EDIT\n\n//line main.go:1\n// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/_obj/main.cgo2.c",
"chars": 1462,
"preview": "\n#line 1 \"cgo-builtin-prolog\"\n#include <stddef.h> /* for ptrdiff_t and size_t below */\n\n/* Define intgo when compiling w"
},
{
"path": "examples/ch2.5/01-cgo-gen-files/hello.go",
"chars": 149,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/01-cgo-gen-files/main.go",
"chars": 165,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/01-cgo-gen-files/nocgo_1.go",
"chars": 137,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/01-cgo-gen-files/nocgo_x.go",
"chars": 137,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/02-go-call-c-func/Makefile",
"chars": 198,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/_cgo_export.c",
"chars": 400,
"preview": "/* Created by cgo - DO NOT EDIT. */\n#include <stdlib.h>\n#include \"_cgo_export.h\"\n\nextern void crosscall2(void (*fn)(void"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/_cgo_export.h",
"chars": 1462,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package main */\n\n\n#line 1 \"cgo-builtin-prolog\"\n\n#include <stddef.h> /*"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/_cgo_flags",
"chars": 13,
"preview": "_CGO_CFLAGS=\n"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/_cgo_gotypes.go",
"chars": 1326,
"preview": "// Created by cgo - DO NOT EDIT\n\npackage main\n\nimport \"unsafe\"\n\nimport _ \"runtime/cgo\"\n\nimport \"syscall\"\n\nvar _ syscall."
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/_cgo_main.c",
"chars": 412,
"preview": "int main() { return 0; }\nvoid crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __SIZE_TYPE__ ctxt) { }\n_"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/main.cgo1.go",
"chars": 292,
"preview": "// Created by cgo - DO NOT EDIT\n\n//line main.go:1\n// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License"
},
{
"path": "examples/ch2.5/02-go-call-c-func/_obj/main.cgo2.c",
"chars": 2019,
"preview": "\n#line 1 \"cgo-builtin-prolog\"\n#include <stddef.h> /* for ptrdiff_t and size_t below */\n\n/* Define intgo when compiling w"
},
{
"path": "examples/ch2.5/02-go-call-c-func/main.go",
"chars": 228,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/03-c-call-go-func/Makefile",
"chars": 287,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/_cgo_export.c",
"chars": 842,
"preview": "/* Created by cgo - DO NOT EDIT. */\n#include <stdlib.h>\n#include \"_cgo_export.h\"\n\nextern void crosscall2(void (*fn)(void"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/_cgo_export.h",
"chars": 1680,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package main */\n\n\n#line 1 \"cgo-builtin-prolog\"\n\n#include <stddef.h> /*"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/_cgo_flags",
"chars": 13,
"preview": "_CGO_CFLAGS=\n"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/_cgo_gotypes.go",
"chars": 1303,
"preview": "// Created by cgo - DO NOT EDIT\n\npackage main\n\nimport \"unsafe\"\n\nimport _ \"runtime/cgo\"\n\nimport \"syscall\"\n\nvar _ syscall."
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/_cgo_main.c",
"chars": 442,
"preview": "int main() { return 0; }\nvoid crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __SIZE_TYPE__ ctxt) { }\n_"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/sum.cgo1.go",
"chars": 315,
"preview": "// Created by cgo - DO NOT EDIT\n\n//line sum.go:1\n// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License:"
},
{
"path": "examples/ch2.5/03-c-call-go-func/_obj/sum.cgo2.c",
"chars": 1646,
"preview": "\n#line 1 \"cgo-builtin-prolog\"\n#include <stddef.h> /* for ptrdiff_t and size_t below */\n\n/* Define intgo when compiling w"
},
{
"path": "examples/ch2.5/03-c-call-go-func/main.c",
"chars": 233,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/03-c-call-go-func/sum.go",
"chars": 249,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.5/03-c-call-go-func/sum.h",
"chars": 1698,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package command-line-arguments */\n\n\n#line 1 \"cgo-builtin-prolog\"\n\n#inc"
},
{
"path": "examples/ch2.6/01-qsort-v1/Makefile",
"chars": 186,
"preview": "# Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.6/01-qsort-v1/main.c",
"chars": 544,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/02-qsort-v2/main.go",
"chars": 610,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/02-qsort-v2/qsort.go",
"chars": 433,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/02-qsort-v2/qsort_test.go",
"chars": 514,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/02-qsort-v2/test_helper.go",
"chars": 457,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/03-qsort-v3/main.go",
"chars": 468,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/03-qsort-v3/sort.go",
"chars": 813,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/03-qsort-v3/sort_test.go",
"chars": 586,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/04-qsort-v4/main.go",
"chars": 347,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/04-qsort-v4/sort.go",
"chars": 1924,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.6/04-qsort-v4/sort_test.go",
"chars": 466,
"preview": "// Copyright © 2018 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/main.go",
"chars": 342,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer.cc",
"chars": 149,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer.go",
"chars": 538,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer.h",
"chars": 397,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer_capi.cc",
"chars": 536,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer_capi.go",
"chars": 640,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-cc2go/my_buffer_capi.h",
"chars": 305,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/goobj.go",
"chars": 805,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/main.cc",
"chars": 373,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/main.go",
"chars": 228,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/persion.go",
"chars": 430,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/person.cc",
"chars": 144,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/person.h",
"chars": 600,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/person_capi.go",
"chars": 1062,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.8/class-go2cc/person_capi.h",
"chars": 429,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/incorrect-dll-api/Makefile",
"chars": 393,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.9/incorrect-dll-api/main.go",
"chars": 424,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/incorrect-dll-api/mystring/Makefile",
"chars": 191,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.9/incorrect-dll-api/mystring/mystring.c",
"chars": 421,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/incorrect-dll-api/mystring/mystring.h",
"chars": 186,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/make-clib-dll/Makefile",
"chars": 570,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.9/make-clib-dll/_test_main.c",
"chars": 308,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/make-clib-dll/main.go",
"chars": 259,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/make-clib-dll/number-win64.def",
"chars": 49,
"preview": "LIBRARY number-win64.dll\n\nEXPORTS\nnumber_add_mod\n"
},
{
"path": "examples/ch2.9/make-clib-dll/number.h",
"chars": 1400,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package github.com/chai2010/advanced-go-programming-book/examples/ch2-"
},
{
"path": "examples/ch2.9/make-clib-from-multi-pkg/Makefile",
"chars": 264,
"preview": "# Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n# License: https://creativecommons.org/licenses/by-nc-sa/4.0/"
},
{
"path": "examples/ch2.9/make-clib-from-multi-pkg/_test_main.c",
"chars": 355,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/make-clib-from-multi-pkg/main.go",
"chars": 365,
"preview": "// Copyright © 2017 ChaiShushan <chaishushan{AT}gmail.com>.\n// License: https://creativecommons.org/licenses/by-nc-sa/4."
},
{
"path": "examples/ch2.9/make-clib-from-multi-pkg/main.h",
"chars": 1393,
"preview": "/* Created by \"go tool cgo\" - DO NOT EDIT. */\n\n/* package github.com/chai2010/advanced-go-programming-book/examples/ch2-"
}
]
// ... and 259 more files (download for full content)
About this extraction
This page contains the full source code of the chai2010/advanced-go-programming-book GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 459 files (1.1 MB), approximately 564.7k tokens, and a symbol index with 1086 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.