首页 > 为什么C和go的地址空间范围不一致?

为什么C和go的地址空间范围不一致?

用C和go语言分别打印int类型地址的,进行对比。


C版本:

#include <stdio.h>

int main()
{
    int a=0;
    printf("%p\n", &a);
    return 0;
}

输出:
0x7fff5510b888
可以看到输出地址为12位16进制格式。

go版本:

package main

import (
    "fmt"
)

func main() {
    a := 1
    fmt.Printf("%p\n", &a)
}

输出:
0x8201c82d0
可以看到输出的地址为8位16进制格式。

问题来了:

为什么地址空间长度不一致?
是不是意味着go的地址空间范围比c小,
哦,go不支持指针运算。


这2个地址没啥可比性,一个是真实的地址(C&C++),一个是跑在GC上的地址。

package main

/*
#include <stdio.h>

void MyPrint(int arg){
        printf("calling from C %p\n", &arg);
}
*/
import "C"
import "fmt"

func main() {
        var a int = 0
        fmt.Printf("calling from go %p\n", &a)
        C.MyPrint(C.int(a))
}

输出

root@ubuntu:/test# ./main 
calling from go 0xc82000a300
calling from C 0x7ffc3cf58fdc

PS: 上面的例子其实并不是很适合,但能看出来同样的数据类型,在go和c里面地址空间是不同的。
之所以说这个例子不是很合适是因为:

1) c.int(a) 将a类型从go的int转换成c的int时, 中间可能是数据的拷贝,而非数据的引用。
2) MyPrint的参数是int,而非int*(go不支持对C.int取地址),中间在C的层面上肯定有数据拷贝。

所以在MyPrint里的参数和外面的参数不是引用到同样的一个内存空间。但该例子应该能说明同样的int类型,在go和C里面的地址格式是有区别的。


  1. 你的C语言估计编译的是64位程序,在x64位程序中,地址的长度为8个字节。

  2. go语言不支持指针操作,只能用unsafe.pointer封装。因此你看到的地址实际上并不存在于内存中,而是go语言封装后的结果。


刚看到题主更新了答案,似乎对我提出的go语言不支持指针运算有异议。
那么请题主谷歌后回复:

  1. windows系统下内存有大于0x7ffffff的吗?

  2. go语言指针在不启用unsafe.point封装的情况下支持算术运算吗?


《go源码剖析》中说,因为go的运行时使用mmap申请内存储存动态对象,并非使用系统提供的堆区,所以地址有点奇怪。

这段地址是 VA 还是 GC 的地址,要直接看反汇编。由于我电脑上没有 Go 的环境,我从书里面找了一段(26~27):

func test() *int {
    x := new(int)
    *x = 0xAABB
    return x
}

反汇编过来是:

TEXT main.test(SB) test.go
     test.go:5   SUBQ $0x10, SP     
     test.go:6   LEAQ 0x56d46(IP), BX     
     test.go:6   MOVQ BX, 0(SP)     
                 ; x := new(int)
     test.go:6   CALL runtime.newobject(SB)    
     test.go:6   MOVQ 0x8(SP), AX 
                 ; *x = 0xAABB    
     test.go:7   MOVQ $0xaabb, 0(AX)
                 ; return x   
     test.go:8   MOVQ AX, 0x18(SP)   
     test.go:8   ADDQ $0x10, SP     
     test.go:8   RET

大家可以看到,指针x直接扔给了 CPU,没有经过 runtime 的转化,所以是 VA 而不是 GC 虚拟出来的地址。

go源码剖析

【热门文章】
【热门文章】