一道变量compare题的延伸
我遇到的问题是第4道例子,建议读者先不要看答案,因为代码本身非常简单。
先下载题目,看看一共5道例子能不能完全做对,这样看到答案的时候会感到更有趣。
判断下面的例子, a
和b
是否相等
例子一
// compare_1.c
#include <stdio.h>
int main() {
char a[11] = "helloworld";
char b[11] = "helloworld";
if (a == b) {
printf("equal\n");
} else {
printf("not equal\n");
}
return 0;
}
答案是not equal
,这里面正常gcc
的版本直接优化掉了流程,.rodata
和.data
都没有equal
而且.text
发现了helloworld
立即数,证据如下
objdump -s -j .rodata compare_1
Contents of section .rodata:
0790 01000200 6e6f7420 65717561 6c00 ....not equal.
objdump -s -j .data compare_1
Contents of section .data:
201000 00000000 00000000 08102000 00000000 .......... .....
objdump -s -j .text compare_1
...
400570 68656c6c 6f776f72 488945e2 66c745ea helloworH.E.f.E.
...
如果定义成char a[11] = "helloworld\0",对于常量字符串的存储不一样,但是对于结果优化一致
例子二
// compare_2.c
#include <stdio.h>
int main () {
char a[11];
char b[11];
scanf("%s", a); // 输入 helloworld
getchar(); //吞掉回车
scanf("%s", b); //输入 helloworld
if (a == b) {
printf("equal\n");
} else {
printf("not equal\n");
}
return 0;
}
答案是 not equal
使用ida调试的时候,./linux_serverx64 < input.txt 来保证标准输入
0000000000400607 <main>:
400607: 55 push %rbp
400608: 48 89 e5 mov %rsp,%rbp
40060b: 48 83 ec 20 sub $0x20,%rsp
40060f: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
400616: 00 00
400618: 48 89 45 f8 mov %rax,-0x8(%rbp)
40061c: 31 c0 xor %eax,%eax
40061e: 48 8d 45 e2 lea -0x1e(%rbp),%rax
400622: 48 89 c6 mov %rax,%rsi
400625: 48 8d 3d d8 00 00 00 lea 0xd8(%rip),%rdi # 400704 <_IO_stdin_used+0x4>
40062c: b8 00 00 00 00 mov $0x0,%eax
400631: e8 da fe ff ff callq 400510 <__isoc99_scanf@plt>
400636: e8 c5 fe ff ff callq 400500 <getchar@plt>
40063b: 48 8d 45 ed lea -0x13(%rbp),%rax
40063f: 48 89 c6 mov %rax,%rsi
400642: 48 8d 3d bb 00 00 00 lea 0xbb(%rip),%rdi # 400704 <_IO_stdin_used+0x4>
400649: b8 00 00 00 00 mov $0x0,%eax
40064e: e8 bd fe ff ff callq 400510 <__isoc99_scanf@plt>
400653: 48 8d 3d ad 00 00 00 lea 0xad(%rip),%rdi # 400707 <_IO_stdin_used+0x7>
40065a: e8 81 fe ff ff callq 4004e0 <puts@plt>
40065f: b8 00 00 00 00 mov $0x0,%eax
400664: 48 8b 55 f8 mov -0x8(%rbp),%rdx
400668: 64 48 33 14 25 28 00 xor %fs:0x28,%rdx
40066f: 00 00
400671: 74 05 je 400678 <main+0x71>
400673: e8 78 fe ff ff callq 4004f0 <__stack_chk_fail@plt>
400678: c9 leaveq
400679: c3 retq
40067a: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1)
例子三
// compare_3.c
#include <stdio.h>
int main() {
char *a = "helloworld";
char *b = "helloworld";
if (a == b) {
printf("equal\n");
} else {
printf("not equal\n");
}
return 0;
}
答案是equal
objdump -d -s -j .text compare_3
00000000004004e7 <main>:
4004e7: 55 push %rbp
4004e8: 48 89 e5 mov %rsp,%rbp
4004eb: 48 83 ec 10 sub $0x10,%rsp
4004ef: 48 8d 05 be 00 00 00 lea 0xbe(%rip),%rax # 4005b4 <_IO_stdin_used+0x4>
4004f6: 48 89 45 f0 mov %rax,-0x10(%rbp)
4004fa: 48 8d 05 b3 00 00 00 lea 0xb3(%rip),%rax # 4005b4 <_IO_stdin_used+0x4>
400501: 48 89 45 f8 mov %rax,-0x8(%rbp)
400505: 48 8b 45 f0 mov -0x10(%rbp),%rax
400509: 48 3b 45 f8 cmp -0x8(%rbp),%rax
40050d: 75 0e jne 40051d <main+0x36>
40050f: 48 8d 3d a9 00 00 00 lea 0xa9(%rip),%rdi # 4005bf <_IO_stdin_used+0xf>
400516: e8 d5 fe ff ff callq 4003f0 <puts@plt>
40051b: eb 0c jmp 400529 <main+0x42>
40051d: 48 8d 3d a1 00 00 00 lea 0xa1(%rip),%rdi # 4005c5 <_IO_stdin_used+0x15>
400524: e8 c7 fe ff ff callq 4003f0 <puts@plt>
400529: b8 00 00 00 00 mov $0x0,%eax
40052e: c9 leaveq
40052f: c3 retq
objdump -s -j .rodata compare_3
Contents of section .rodata:
4005b0 01000200 68656c6c 6f776f72 6c640065 ....helloworld.e
4005c0 7175616c 006e6f74 20657175 616c00 qual.not equal.
这里面优化的是,字符串常量只有一个,所以都指向了同一个
例子四
// compare_4.go
package main
import "fmt"
func main() {
a := []byte("helloworld")
b := []byte("helloworld")
if a == b {
fmt.Printf("equal\n")
} else {
fmt.Printf("not equal\n")
}
}
答案是编译错误,因为[]byte
只可以与nil
比较
例子五
// compare_5.go
package main
import "fmt"
func main() {
a := [10]byte{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}
b := [10]byte{'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'}
if a == b {
fmt.Printf("equal\n")
} else {
fmt.Printf("not equal\n")
}
}
答案是equal
objdump -s -j .rodata compare_5 | grep "equal"
489fa0 00000565 7175616c 00000565 72617365 ...equal...erase
静态编译产生的数据太多了,所以用了过滤
这个equal
答案真的是这么简单的吗?
go tool compile -S compare_5.go
0x00e0 00224 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $1
0x00e0 00224 ($GOROOT/src/fmt/print.go:208) MOVQ os.Stdout(SB), AX
0x00e7 00231 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $2
0x00e7 00231 ($GOROOT/src/fmt/print.go:208) LEAQ go.itab.*os.File,io.Writer(SB), CX
0x00ee 00238 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $1
0x00ee 00238 ($GOROOT/src/fmt/print.go:208) MOVQ CX, (SP)
0x00f2 00242 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $0
0x00f2 00242 ($GOROOT/src/fmt/print.go:208) MOVQ AX, 8(SP)
0x00f7 00247 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $1
0x00f7 00247 ($GOROOT/src/fmt/print.go:208) LEAQ go.string."equal\n"(SB), AX
0x00fe 00254 ($GOROOT/src/fmt/print.go:208) PCDATA $2, $0
0x00fe 00254 ($GOROOT/src/fmt/print.go:208) MOVQ AX, 16(SP)
0x0103 00259 ($GOROOT/src/fmt/print.go:208) MOVQ $6, 24(SP)
0x010c 00268 ($GOROOT/src/fmt/print.go:208) MOVQ $0, 32(SP)
0x0115 00277 ($GOROOT/src/fmt/print.go:208) XORPS X0, X0
0x0118 00280 ($GOROOT/src/fmt/print.go:208) MOVUPS X0, 40(SP)
0x011d 00285 ($GOROOT/src/fmt/print.go:208) CALL fmt.Fprintf(SB)
objdump -s -j .rodata compare_5 | grep "qual"
489fa0 00000565 7175616c 00000565 72617365 ...equal...erase
4b9f70 7175616c 0a657272 6e6f206f 626a6563 qual.errno objec
编译错误
最近还发现一件事,nsq
依赖的github.com/BurntSushi/toml
,使用go mod
时候,下载在$GOPATH/pkg/mod/github.com/'!burnt!sushi'/toml
,大写
变成了!小写
那么看看编译在哪?
而对于上面例子四的编译错误,那么看图的go/src/cmd/go/internal/work/build.go
备注:
编译最好加上-no-pie
,高版本gcc
默认开启pie
编译方式
gcc -no-pie compare_1.c -o compare_1
readelf -h compare_1
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x400470
程序头起点: 64 (bytes into file)
Start of section headers: 6440 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 29
字符串表索引节头: 28
否则会编译出来DYN
类的可执行程序,主要是内存地址无关方式,还不清楚何如做到加载的
gcc compare_1.c -o compare_1
readelf -h compare_1
ELF 头:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - System V
ABI 版本: 0
类型: DYN (共享目标文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x5a0
程序头起点: 64 (bytes into file)
Start of section headers: 6504 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 9
节头大小: 64 (字节)
节头数量: 29
字符串表索引节头: 28