内容简介:原文地址:欢迎访问我的博客有时候我们想要知道写出来的代码是怎么编译执行的,这时候
原文地址: https://yuchanns.org/posts/2020/01/31/golang-assembly/
欢迎访问我的博客 yuchanns'Atelier
有时候我们想要知道写出来的代码是怎么编译执行的,这时候 go tool compile
就是一个很好用的工具。
本文相关代码 yuchanns/gobyexample
如何输出汇编代码
有三种方法可以输出 go 代码的汇编代码:
- go tool compile 生成obj文件
- go build -gcflags 生成最终二进制文件
-
先go build然后在go tool objdump 对二进制文件进行反汇编
当然,具体行为还需要在这些命令后面加上具体的flag
。flag的内容可以通过查阅 官方文档 获得。
本文涉及Flags说明
-N 禁止优化
-S 输出汇编代码
-l 禁止内联
什么是内联
如果学过 c/c++
就知道,通过 inline
关键字修饰的函数叫做内联函数。内联函数的优势是在编译过程中直接展开函数中的代码,将其替换到源码的函数调用位置,这样可以节省函数调用的消耗,提高运行速度。适用于函数体短小且频繁调用的函数,如果函数体太大了,会增大目标代码。是一种空间换时间的做法。
go编译器会智能判断对代码进行优化和使用汇编。而我们在分析学习代码调用情况的时候需要禁止掉这些优化,避免混淆理解。
以下我们使用 go build -gcflags="-N -l -S" file
来获得汇编代码。
获取一份简单的汇编代码
现在手上有一份关于range的代码,但是我们运行之后出现了一些问题 [1] [2] :
package assembly import "fmt" func RangeClause() { arr := []int{1, 2, 3} var newArr []*int for _, v := range arr { newArr = append(newArr, &v) } for _, v := range newArr { fmt.Println(*v) } }
结果输出了三个3。
也许我们在学习过程中见过类似的错误,然后设法(或者别人告诉我们怎么)避免错误,但是仍然百思不得其解,知其然不知其所以然。
这时候获取汇编代码就可以排上用场了。
执行 go build -gcflags="-N -l -S" range_clause.go
,得到下面这份输出结果:
"".RangeClause STEXT size=842 args=0x0 locals=0x158 0x0000 00000 (range_clause.go:5) TEXT "".RangeClause(SB), ABIInternal, $344-0 0x0000 00000 (range_clause.go:5) MOVQ (TLS), CX 0x0009 00009 (range_clause.go:5) LEAQ -216(SP), AX 0x0011 00017 (range_clause.go:5) CMPQ AX, 16(CX) 0x0015 00021 (range_clause.go:5) JLS 832 0x001b 00027 (range_clause.go:5) SUBQ $344, SP 0x0022 00034 (range_clause.go:5) MOVQ BP, 336(SP) 0x002a 00042 (range_clause.go:5) LEAQ 336(SP), BP 0x0032 00050 (range_clause.go:5) FUNCDATA $0, gclocals·f0a67958015464e4cc8847ce0df60843(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $1, gclocals·1be50b3ff1c6bce621b19ced5cafc212(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $2, gclocals·160a1dd0c9595e8bcf8efc4c6b948d91(SB) 0x0032 00050 (range_clause.go:5) FUNCDATA $3, "".RangeClause.stkobj(SB) 0x0032 00050 (range_clause.go:6) PCDATA $0, $1 0x0032 00050 (range_clause.go:6) PCDATA $1, $0 0x0032 00050 (range_clause.go:6) LEAQ ""..autotmp_9+120(SP), AX 0x0037 00055 (range_clause.go:6) PCDATA $1, $1 0x0037 00055 (range_clause.go:6) MOVQ AX, ""..autotmp_8+152(SP) 0x003f 00063 (range_clause.go:6) PCDATA $0, $0 0x003f 00063 (range_clause.go:6) TESTB AL, (AX) 0x0041 00065 (range_clause.go:6) MOVQ ""..stmp_0(SB), AX 0x0048 00072 (range_clause.go:6) MOVQ AX, ""..autotmp_9+120(SP) 0x004d 00077 (range_clause.go:6) MOVUPS ""..stmp_0+8(SB), X0 0x0054 00084 (range_clause.go:6) MOVUPS X0, ""..autotmp_9+128(SP) 0x005c 00092 (range_clause.go:6) PCDATA $0, $1 0x005c 00092 (range_clause.go:6) PCDATA $1, $0 0x005c 00092 (range_clause.go:6) MOVQ ""..autotmp_8+152(SP), AX 0x0064 00100 (range_clause.go:6) TESTB AL, (AX) 0x0066 00102 (range_clause.go:6) JMP 104 0x0068 00104 (range_clause.go:6) PCDATA $0, $0 0x0068 00104 (range_clause.go:6) PCDATA $1, $2 0x0068 00104 (range_clause.go:6) MOVQ AX, "".arr+240(SP) 0x0070 00112 (range_clause.go:6) MOVQ $3, "".arr+248(SP) 0x007c 00124 (range_clause.go:6) MOVQ $3, "".arr+256(SP) 0x0088 00136 (range_clause.go:7) PCDATA $1, $3 0x0088 00136 (range_clause.go:7) MOVQ $0, "".newArr+216(SP) 0x0094 00148 (range_clause.go:7) XORPS X0, X0 0x0097 00151 (range_clause.go:7) MOVUPS X0, "".newArr+224(SP) 0x009f 00159 (range_clause.go:8) PCDATA $0, $1 0x009f 00159 (range_clause.go:8) LEAQ type.int(SB), AX 0x00a6 00166 (range_clause.go:8) PCDATA $0, $0 0x00a6 00166 (range_clause.go:8) MOVQ AX, (SP) 0x00aa 00170 (range_clause.go:8) CALL runtime.newobject(SB) 0x00af 00175 (range_clause.go:8) PCDATA $0, $1 0x00af 00175 (range_clause.go:8) MOVQ 8(SP), AX 0x00b4 00180 (range_clause.go:8) PCDATA $0, $0 0x00b4 00180 (range_clause.go:8) PCDATA $1, $4 0x00b4 00180 (range_clause.go:8) MOVQ AX, "".&v+192(SP) 0x00bc 00188 (range_clause.go:8) MOVQ "".arr+256(SP), AX 0x00c4 00196 (range_clause.go:8) MOVQ "".arr+248(SP), CX 0x00cc 00204 (range_clause.go:8) PCDATA $0, $2 0x00cc 00204 (range_clause.go:8) PCDATA $1, $5 0x00cc 00204 (range_clause.go:8) MOVQ "".arr+240(SP), DX 0x00d4 00212 (range_clause.go:8) PCDATA $0, $0 0x00d4 00212 (range_clause.go:8) PCDATA $1, $6 0x00d4 00212 (range_clause.go:8) MOVQ DX, ""..autotmp_5+288(SP) 0x00dc 00220 (range_clause.go:8) MOVQ CX, ""..autotmp_5+296(SP) 0x00e4 00228 (range_clause.go:8) MOVQ AX, ""..autotmp_5+304(SP) 0x00ec 00236 (range_clause.go:8) MOVQ $0, ""..autotmp_10+112(SP) 0x00f5 00245 (range_clause.go:8) MOVQ ""..autotmp_5+296(SP), AX 0x00fd 00253 (range_clause.go:8) MOVQ AX, ""..autotmp_11+104(SP) 0x0102 00258 (range_clause.go:8) JMP 260 0x0104 00260 (range_clause.go:8) MOVQ ""..autotmp_11+104(SP), CX 0x0109 00265 (range_clause.go:8) CMPQ ""..autotmp_10+112(SP), CX 0x010e 00270 (range_clause.go:8) JLT 277 0x0110 00272 (range_clause.go:8) JMP 516 0x0115 00277 (range_clause.go:8) MOVQ ""..autotmp_10+112(SP), CX 0x011a 00282 (range_clause.go:8) SHLQ $3, CX 0x011e 00286 (range_clause.go:8) PCDATA $0, $3 0x011e 00286 (range_clause.go:8) ADDQ ""..autotmp_5+288(SP), CX 0x0126 00294 (range_clause.go:8) PCDATA $0, $0 0x0126 00294 (range_clause.go:8) MOVQ (CX), CX 0x0129 00297 (range_clause.go:8) MOVQ CX, ""..autotmp_12+96(SP) 0x012e 00302 (range_clause.go:8) PCDATA $0, $2 0x012e 00302 (range_clause.go:8) MOVQ "".&v+192(SP), DX 0x0136 00310 (range_clause.go:8) PCDATA $0, $0 0x0136 00310 (range_clause.go:8) MOVQ CX, (DX) 0x0139 00313 (range_clause.go:9) PCDATA $0, $3 0x0139 00313 (range_clause.go:9) MOVQ "".&v+192(SP), CX 0x0141 00321 (range_clause.go:9) PCDATA $0, $0 0x0141 00321 (range_clause.go:9) PCDATA $1, $7 0x0141 00321 (range_clause.go:9) MOVQ CX, ""..autotmp_13+184(SP) 0x0149 00329 (range_clause.go:9) MOVQ "".newArr+232(SP), CX 0x0151 00337 (range_clause.go:9) MOVQ "".newArr+224(SP), DX 0x0159 00345 (range_clause.go:9) PCDATA $0, $4 0x0159 00345 (range_clause.go:9) PCDATA $1, $8 0x0159 00345 (range_clause.go:9) MOVQ "".newArr+216(SP), BX 0x0161 00353 (range_clause.go:9) LEAQ 1(DX), SI 0x0165 00357 (range_clause.go:9) CMPQ SI, CX 0x0168 00360 (range_clause.go:9) JLS 364 0x016a 00362 (range_clause.go:9) JMP 446 0x016c 00364 (range_clause.go:9) PCDATA $0, $-2 0x016c 00364 (range_clause.go:9) PCDATA $1, $-2 0x016c 00364 (range_clause.go:9) JMP 366 0x016e 00366 (range_clause.go:9) PCDATA $0, $5 0x016e 00366 (range_clause.go:9) PCDATA $1, $9 0x016e 00366 (range_clause.go:9) MOVQ ""..autotmp_13+184(SP), AX 0x0176 00374 (range_clause.go:9) PCDATA $0, $6 0x0176 00374 (range_clause.go:9) LEAQ (BX)(DX*8), DI 0x017a 00378 (range_clause.go:9) PCDATA $0, $-2 0x017a 00378 (range_clause.go:9) PCDATA $1, $-2 0x017a 00378 (range_clause.go:9) CMPL runtime.writeBarrier(SB), $0 0x0181 00385 (range_clause.go:9) JEQ 389 0x0183 00387 (range_clause.go:9) JMP 439 0x0185 00389 (range_clause.go:9) MOVQ AX, (BX)(DX*8) 0x0189 00393 (range_clause.go:9) JMP 395 0x018b 00395 (range_clause.go:9) PCDATA $0, $0 0x018b 00395 (range_clause.go:9) PCDATA $1, $6 0x018b 00395 (range_clause.go:9) MOVQ BX, "".newArr+216(SP) 0x0193 00403 (range_clause.go:9) MOVQ SI, "".newArr+224(SP) 0x019b 00411 (range_clause.go:9) MOVQ CX, "".newArr+232(SP) 0x01a3 00419 (range_clause.go:9) JMP 421 0x01a5 00421 (range_clause.go:8) MOVQ ""..autotmp_10+112(SP), CX 0x01aa 00426 (range_clause.go:8) INCQ CX 0x01ad 00429 (range_clause.go:8) MOVQ CX, ""..autotmp_10+112(SP) 0x01b2 00434 (range_clause.go:8) JMP 260 0x01b7 00439 (range_clause.go:9) PCDATA $0, $-2 0x01b7 00439 (range_clause.go:9) PCDATA $1, $-2 0x01b7 00439 (range_clause.go:9) CALL runtime.gcWriteBarrier(SB) 0x01bc 00444 (range_clause.go:9) JMP 395 0x01be 00446 (range_clause.go:9) PCDATA $0, $4 0x01be 00446 (range_clause.go:9) PCDATA $1, $8 0x01be 00446 (range_clause.go:9) MOVQ DX, ""..autotmp_21+64(SP) 0x01c3 00451 (range_clause.go:9) PCDATA $0, $5 0x01c3 00451 (range_clause.go:9) LEAQ type.*int(SB), AX 0x01ca 00458 (range_clause.go:9) PCDATA $0, $4 0x01ca 00458 (range_clause.go:9) MOVQ AX, (SP) 0x01ce 00462 (range_clause.go:9) PCDATA $0, $0 0x01ce 00462 (range_clause.go:9) MOVQ BX, 8(SP) 0x01d3 00467 (range_clause.go:9) MOVQ DX, 16(SP) 0x01d8 00472 (range_clause.go:9) MOVQ CX, 24(SP) 0x01dd 00477 (range_clause.go:9) MOVQ SI, 32(SP) 0x01e2 00482 (range_clause.go:9) CALL runtime.growslice(SB) 0x01e7 00487 (range_clause.go:9) PCDATA $0, $4 0x01e7 00487 (range_clause.go:9) MOVQ 40(SP), BX 0x01ec 00492 (range_clause.go:9) MOVQ 48(SP), AX 0x01f1 00497 (range_clause.go:9) MOVQ 56(SP), CX 0x01f6 00502 (range_clause.go:9) LEAQ 1(AX), SI 0x01fa 00506 (range_clause.go:9) MOVQ ""..autotmp_21+64(SP), DX 0x01ff 00511 (range_clause.go:9) JMP 366 0x0204 00516 (range_clause.go:11) PCDATA $0, $0 0x0204 00516 (range_clause.go:11) PCDATA $1, $10 0x0204 00516 (range_clause.go:11) MOVQ "".newArr+232(SP), AX 0x020c 00524 (range_clause.go:11) MOVQ "".newArr+224(SP), CX 0x0214 00532 (range_clause.go:11) PCDATA $0, $2 0x0214 00532 (range_clause.go:11) PCDATA $1, $0 0x0214 00532 (range_clause.go:11) MOVQ "".newArr+216(SP), DX 0x021c 00540 (range_clause.go:11) PCDATA $0, $0 0x021c 00540 (range_clause.go:11) PCDATA $1, $11 0x021c 00540 (range_clause.go:11) MOVQ DX, ""..autotmp_6+264(SP) 0x0224 00548 (range_clause.go:11) MOVQ CX, ""..autotmp_6+272(SP) 0x022c 00556 (range_clause.go:11) MOVQ AX, ""..autotmp_6+280(SP) 0x0234 00564 (range_clause.go:11) MOVQ $0, ""..autotmp_14+88(SP) 0x023d 00573 (range_clause.go:11) MOVQ ""..autotmp_6+272(SP), AX 0x0245 00581 (range_clause.go:11) MOVQ AX, ""..autotmp_15+80(SP) 0x024a 00586 (range_clause.go:11) JMP 588 0x024c 00588 (range_clause.go:11) MOVQ ""..autotmp_15+80(SP), AX 0x0251 00593 (range_clause.go:11) CMPQ ""..autotmp_14+88(SP), AX 0x0256 00598 (range_clause.go:11) JLT 605 0x0258 00600 (range_clause.go:11) JMP 816 0x025d 00605 (range_clause.go:11) MOVQ ""..autotmp_14+88(SP), AX 0x0262 00610 (range_clause.go:11) SHLQ $3, AX 0x0266 00614 (range_clause.go:11) PCDATA $0, $1 0x0266 00614 (range_clause.go:11) ADDQ ""..autotmp_6+264(SP), AX 0x026e 00622 (range_clause.go:11) MOVQ (AX), AX 0x0271 00625 (range_clause.go:11) MOVQ AX, ""..autotmp_16+176(SP) 0x0279 00633 (range_clause.go:11) MOVQ AX, "".v+144(SP) 0x0281 00641 (range_clause.go:12) TESTB AL, (AX) 0x0283 00643 (range_clause.go:12) PCDATA $0, $0 0x0283 00643 (range_clause.go:12) MOVQ (AX), AX 0x0286 00646 (range_clause.go:12) MOVQ AX, ""..autotmp_17+72(SP) 0x028b 00651 (range_clause.go:12) MOVQ AX, (SP) 0x028f 00655 (range_clause.go:12) CALL runtime.convT64(SB) 0x0294 00660 (range_clause.go:12) PCDATA $0, $1 0x0294 00660 (range_clause.go:12) MOVQ 8(SP), AX 0x0299 00665 (range_clause.go:12) PCDATA $0, $0 0x0299 00665 (range_clause.go:12) PCDATA $1, $12 0x0299 00665 (range_clause.go:12) MOVQ AX, ""..autotmp_18+168(SP) 0x02a1 00673 (range_clause.go:12) PCDATA $1, $13 0x02a1 00673 (range_clause.go:12) XORPS X0, X0 0x02a4 00676 (range_clause.go:12) MOVUPS X0, ""..autotmp_7+200(SP) 0x02ac 00684 (range_clause.go:12) PCDATA $0, $1 0x02ac 00684 (range_clause.go:12) PCDATA $1, $12 0x02ac 00684 (range_clause.go:12) LEAQ ""..autotmp_7+200(SP), AX 0x02b4 00692 (range_clause.go:12) MOVQ AX, ""..autotmp_20+160(SP) 0x02bc 00700 (range_clause.go:12) TESTB AL, (AX) 0x02be 00702 (range_clause.go:12) PCDATA $0, $7 0x02be 00702 (range_clause.go:12) PCDATA $1, $11 0x02be 00702 (range_clause.go:12) MOVQ ""..autotmp_18+168(SP), CX 0x02c6 00710 (range_clause.go:12) PCDATA $0, $8 0x02c6 00710 (range_clause.go:12) LEAQ type.int(SB), DX 0x02cd 00717 (range_clause.go:12) PCDATA $0, $7 0x02cd 00717 (range_clause.go:12) MOVQ DX, ""..autotmp_7+200(SP) 0x02d5 00725 (range_clause.go:12) PCDATA $0, $1 0x02d5 00725 (range_clause.go:12) MOVQ CX, ""..autotmp_7+208(SP) 0x02dd 00733 (range_clause.go:12) TESTB AL, (AX) 0x02df 00735 (range_clause.go:12) JMP 737 0x02e1 00737 (range_clause.go:12) MOVQ AX, ""..autotmp_19+312(SP) 0x02e9 00745 (range_clause.go:12) MOVQ $1, ""..autotmp_19+320(SP) 0x02f5 00757 (range_clause.go:12) MOVQ $1, ""..autotmp_19+328(SP) 0x0301 00769 (range_clause.go:12) PCDATA $0, $0 0x0301 00769 (range_clause.go:12) MOVQ AX, (SP) 0x0305 00773 (range_clause.go:12) MOVQ $1, 8(SP) 0x030e 00782 (range_clause.go:12) MOVQ $1, 16(SP) 0x0317 00791 (range_clause.go:12) CALL fmt.Println(SB) 0x031c 00796 (range_clause.go:12) JMP 798 0x031e 00798 (range_clause.go:11) MOVQ ""..autotmp_14+88(SP), AX 0x0323 00803 (range_clause.go:11) INCQ AX 0x0326 00806 (range_clause.go:11) MOVQ AX, ""..autotmp_14+88(SP) 0x032b 00811 (range_clause.go:11) JMP 588 0x0330 00816 (<unknown line number>) PCDATA $1, $0 0x0330 00816 (<unknown line number>) MOVQ 336(SP), BP 0x0338 00824 (<unknown line number>) ADDQ $344, SP 0x033f 00831 (<unknown line number>) RET 0x0340 00832 (<unknown line number>) NOP 0x0340 00832 (range_clause.go:5) PCDATA $1, $-1 0x0340 00832 (range_clause.go:5) PCDATA $0, $-1 0x0340 00832 (range_clause.go:5) CALL runtime.morestack_noctxt(SB) 0x0345 00837 (range_clause.go:5) JMP 0
看着输出结果,很cool~~~但是看不懂:(
汇编的简单知识
go使用的汇编叫做 plan9汇编
。最初go是在plan9系统上开发的,后来才在 Linux 和Mac上实现。
关于plan9汇编的入门,推荐看这个视频: plan9汇编入门|go夜读
寄存器
寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。
助记符 | 名字 | 用途 |
---|---|---|
AX | 累加寄存器(AccumulatorRegister) | 用于存放数据,包括算术、操作数、结果和临时存放地址 |
BX | 基址寄存器(BaseRegister) | 用于存放访问存储器时的地址 |
CX | 计数寄存器(CountRegister) | 用于保存计算值,用作计数器 |
DX | 数据寄存器(DataRegister) | 用于数据传递,在寄存器间接寻址中的I/O指令中存放I/O端口的地址 |
SP | 堆栈顶指针(StackPointer) |
如果是 symbol+offset(SP)
的形式表示go汇编的伪寄存器;如果是 offset(SP)
的形式表示硬件寄存器 |
BP | 堆栈基指针(BasePointer) | 保存在进入函数前的栈顶基址 |
SB | 静态基指针(StaticBasePointer) |
go汇编的伪寄存器。 foo(SB)
用于表示变量在内存中的地址, foo+4(SB)
表示foo起始地址往后偏移四字节。一般用来声明函数或全局变量 |
FP | 栈帧指针(FramePointer) |
go汇编的伪寄存器。引用函数的输入参数,形式是 symbol+offset(FP)
,例如 arg0+0(FP)
|
SI | 源变址寄存器(SourceIndex) | 用于存放源操作数的偏移地址 |
DI | 目的寄存器(DestinationIndex) | 用于存放目的操作数的偏移地址 |
操作指令
用于指导汇编如何进行。以下指令后缀<mark>Q</mark>说明是64位上的汇编指令。
助记符 | 指令种类 | 用途 | 示例 |
---|---|---|---|
MOVQ | 传送 | 数据传送 |
MOVQ 48, AX
表示把48传送AX中 |
LEAQ | 传送 | 地址传送 |
LEAQ AX, BX
表示把AX有效地址传送到BX中 |
|
|
|
PUSHQ AX
表示先修改栈顶指针,将AX内容送入新的栈顶位置 SUBQ
代替 |
|
|
|
POPQ AX
表示先弹出栈顶的数据,然后修改栈顶指针 ADDQ
代替 |
ADDQ | 运算 | 相加并赋值 |
ADDQ BX, AX
表示BX和AX的值相加并赋值给AX |
SUBQ | 运算 | 相减并赋值 | 略,同上 |
IMULQ | 运算 | 无符号乘法 | 略,同上 |
IDIVQ | 运算 | 无符号除法 |
IDIVQ CX
除数是CX,被除数是AX,结果存储到AX中 |
CMPQ | 运算 | 对两数相减,比较大小 |
CMPQ SI CX
表示比较SI和CX的大小。与SUBQ类似,只是不返回相减的结果 |
CALL | 转移 | 调用函数 |
CALL runtime.printnl(SB)
表示通过<mark>printnl</mark>函数的内存地址发起调用 |
JMP | 转移 | 无条件转移指令 |
JMP 389
无条件转至 0x0185
地址处(十进制389转换成十六进制0x0185) |
JLS | 转移 | 条件转移指令 |
JLS 389
上一行的比较结果,左边小于右边则执行跳到 0x0185
地址处(十进制389转换成十六进制0x0185) |
可以看到,表中的 PUSHQ
和 POPQ
被去掉了,这是因为在go汇编中,对栈的操作并不是出栈入栈,而是通过对SP进行运算来实现的。
标志位
助记符 | 名字 | 用途 |
---|---|---|
OF | 溢出 | 0为无溢出 1为溢出 |
CF | 进位 | 0为最高位无进位或错位 1为有 |
PF | 奇偶 | 0表示数据最低8位中1的个数为奇数,1则表示1的个数为偶数 |
AF | 辅助进位 | |
ZF | 零 | 0表示结果不为0 1表示结果为0 |
SF | 符号 | 0表示最高位为0 1表示最高位为1 |
这么一通信息轰炸下来,作为初学者可能已经头晕脑胀记不住了,其实是否记住这并不重要——后面分析用到了再回来查阅意思即可。
分析汇编代码
从1+1开始
“好了,现在我们已经学会了加减乘除四则运算,接下来我们来解答一下这道微积分的题目”XD
下面内容查看 原文>> yuchanns'Atelier
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- iOS汇编入门教程(一)ARM64汇编基础
- iOS 汇编入门教程(一):ARM64 汇编基础
- iOS汇编入门教程(三)汇编中的 Section 与数据存取
- iOS汇编入门教程(二)在Xcode工程中嵌入汇编代码
- Golang 汇编入门知识总结
- 汇编语言8086笔记
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Java Language Specification, Java SE 7 Edition
James Gosling、Bill Joy、Guy L. Steele Jr.、Gilad Bracha、Alex Buckley / Addison-Wesley Professional / 2013-2-24 / USD 59.99
Written by the inventors of the technology, The Java(r) Language Specification, Java SE 7 Edition, is the definitive technical reference for the Java programming language. The book provides complete, ......一起来看看 《The Java Language Specification, Java SE 7 Edition》 这本书的介绍吧!