• 技术文章 >后端开发 >Golang

    go语言和c语言在指针上有什么区别

    青灯夜游青灯夜游2022-11-30 19:50:46原创2637

    区别:1、go语言可以使用new关键字来分配内存创建指定类型的指针,而c语言不行。2、c语言中数组名arr代表的是数组首元素的地址,相当于“&arr[0]”;go语言中数组名arr不代表数组首元素的地址,代表的是整个数组的值。3、go语言不支持指针运算,而c语言支持指针运算。4、

    本教程操作环境:windows7系统、GO 1.18版本、Dell G3电脑。

    CGo 都是有指针概念的语言,这篇文章主要借这两者之间的异同来加深对 Go 指针的理解和使用。

    运算符

    C 和 Go 都相同:

    C 语言版示例:

    #include <stdio.h>
    
    int main()
    {
        int bar = 1;
        // 声明一个指向 int 类型的值的指针
        int *ptr;
        // 通过 & 取出 bar 变量所在的内存地址并赋值给 ptr 指针
        ptr = &bar;
        // 打印 ptr 的值(为地址),*prt 表示取出指针变量所指向的内存地址里面的值
        printf("%p %d\n", ptr, *ptr);
        return (0);
    }
    
    // 输出结果:
    // 0x7ffd5471ee54 1

    Go 语言版示例:

    package main
    
    import "fmt"
    
    func main() {
     bar := 1
     // 声明一个指向 int 类型的值的指针
     var ptr *int
     // 通过 & 取出 bar 变量所在的内存地址并赋值给 ptr 指针
     ptr = &bar
     // 打印 ptr 变量储存的指针地址,*prt 表示取出指针变量所指向的内存地址里面的值
     fmt.Printf("%p %d\n", ptr, *ptr)
    }
    
    // 输出结果:
    // 0xc000086020 1

    Go 还可以使用 new 关键字来分配内存创建指定类型的指针。

     // 声明一个指向 int 类型的值的指针
     // var ptr *int
     ptr := new(int)
     // 通过 & 取出 bar 变量所在的内存地址并赋值给 ptr 指针
     ptr = &bar

    数组名和数组首地址

    对于一个数组

    // C
    int arr[5] = {1, 2, 3, 4, 5};
    // Go
    // 需要指定长度,否则类型为切片
    arr := [5]int{1, 2, 3, 4, 5}

    在 C 中,数组名 arr 代表的是数组首元素的地址,相当于 &arr[0]

    &arr 代表的是整个数组 arr 的首地址

    // C
    // arr 数组名代表数组首元素的地址
    printf("arr -> %p\n", arr);
    // &arr[0] 代表数组首元素的地址
    printf("&arr[0] -> %p\n", &arr[0]);
    // &arr 代表整个数组 arr 的首地址
    printf("&arr -> %p\n", &arr);
    
    // 输出结果:
    // arr -> 0061FF0C
    // &arr[0] -> 0061FF0C
    // &arr -> 0061FF0C

    运行程序可以发现 arr&arr 的输出值是相同的,但是它们的意义完全不同。

    首先数组名 arr 作为一个标识符,是 arr[0] 的地址,从 &arr[0] 的角度去看就是一个指向 int 类型的值的指针。

    &arr 是一个指向 int[5] 类型的值的指针。

    可以进一步对其进行指针偏移验证

    // C
    // 指针偏移
    printf("arr+1 -> %p\n", arr + 1);
    printf("&arr+1 -> %p\n", &arr + 1);
    
    // 输出结果:
    // arr+1 -> 0061FF10
    // &arr+1 -> 0061FF20

    这里涉及到偏移量的知识:一个类型为 T 的指针的移动,是以 sizeof(T) 为移动单位的。

    到这里相信你应该可以理解 C 语言中的 arr&arr 的区别了吧,接下来看看 Go 语言

    // 尝试将数组名 arr 作为地址输出
    fmt.Printf("arr -> %p\n", arr)
    fmt.Printf("&arr[0] -> %p\n", &arr[0])
    fmt.Printf("&arr -> %p\n", &arr)
    
    // 输出结果:
    // arr -> %!p([5]int=[1 2 3 4 5])
    // &arr[0] -> 0xc00000c300
    // &arr -> 0xc00000c300

    &arr[0]&arr 与 C 语言一致。

    但是数组名 arr 在 Go 中已经不是数组首元素的地址了,代表的是整个数组的值,所以输出时会提示 %!p([5]int=[1 2 3 4 5])

    指针运算

    指针本质上就是一个无符号整数,代表了内存地址。

    指针和整数值可以进行加减法运算,比如上文的指针偏移例子:

    其中 sizeof(T) 代表的是数据类型占据的字节,比如 int 在 32 位环境下为 4 字节,64 位环境下为 8 字节

    C 语言示例:

    #include <stdio.h>
    
    int main()
    {
        int arr[] = {1, 2, 3, 4, 5};
        // ptr 是一个指针,为 arr 数组的第一个元素地址
        int *ptr = arr;
        printf("%p %d\n", ptr, *ptr);
    
        // ptr 指针向高位移动一个单位,移向到 arr 数组第二个元素地址
        ptr++;
        printf("%p %d\n", ptr, *ptr);
        return (0);
    }
    
    // 输出结果:
    // 0061FF08 1
    // 0061FF0C 2

    在这里 ptr++0061FF08 移动了 sizeof(int) = 4 个字节到 0061FF0C ,指向了下一个数组元素的地址

    Go 语言示例:

    package main
    
    import "fmt"
    
    func main() {
     arr := [5]uint32{1, 2, 3, 4, 5}
    
     // ptr 是一个指针,为 arr 数组的第一个元素地址
     ptr := &arr[0]
     fmt.Println(ptr, *ptr)
    
     // ptr 指针向高位移动一个单位,移向到 arr 数组第二个元素地址
     ptr++
     fmt.Println(ptr, *ptr)
    }
    
    // 输出结果:
    // 编译报错:
    // .\main.go:13:5: invalid operation: ptr++ (non-numeric type *uint32)

    编译报错 *uint32 非数字类型,不支持运算,说明 Go 是不支持指针运算的。

    这个其实在 Go Wiki[1] 中的 Go 从 C++ 过渡文档中有提到过:Go has pointers but not pointer arithmetic.

    Go 有指针但不支持指针运算。

    另辟蹊径

    那还有其他办法吗?答案当然是有的。

    在 Go 标准库中提供了一个 unsafe 包用于编译阶段绕过 Go 语言的类型系统,直接操作内存。

    我们可以利用 unsafe 包来实现指针运算。

    func Alignof(x ArbitraryType) uintptr
    func Offsetof(x ArbitraryType) uintptr
    func Sizeof(x ArbitraryType) uintptr
    type ArbitraryType
    func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType
    type IntegerType
    type Pointer
    func Add(ptr Pointer, len IntegerType) Pointer

    核心介绍:

    原理:

    Go 的 uintptr 类型存储的是地址,且支持数学运算

    *T (任意指针类型) 和 unsafe.Pointer 不能运算,但是 unsafe.Pointer 可以和 *Tuintptr 互相转换

    因此,将 *T 转换为 unsafe.Pointer 后再转换为 uintptruintptr 进行运算之后重新转换为 unsafe.Pointer => *T 即可

    代码实现:

    package main
    
    import (
     "fmt"
     "unsafe"
    )
    
    func main() {
     arr := [5]uint32{1, 2, 3, 4, 5}
    
     ptr := &arr[0]
    
     // ptr(*uint32类型) => one(unsafe.Pointer类型)
     one := unsafe.Pointer(ptr)
     // one(unsafe.Pointer类型) => *uint32
     fmt.Println(one, *(*uint32)(one))
    
     // one(unsafe.Pointer类型) => one(uintptr类型) 后向高位移动 unsafe.Sizeof(arr[0]) = 4 字节
     // twoUintptr := uintptr(one) + unsafe.Sizeof(arr[0])
     // !!twoUintptr 不能作为临时变量
     // uintptr 类型的临时变量只是一个无符号整数,并不知道它是一个指针地址,可能被 GC
     // 运算完成后应该直接转换回 unsafe.Pointer :
     two := unsafe.Pointer(uintptr(one) + unsafe.Sizeof(arr[0]))
     fmt.Println(two, *(*uint32)(two))
    }
    
    // 输出结果:
    // 0xc000012150 1
    // 0xc000012154 2

    甚至还可以更改结构体的私有成员:

    // model/model.go
    
    package model
    
    import (
     "fmt"
    )
    
    type M struct {
     foo uint32
     bar uint32
    }
    
    func (m M) Print() {
     fmt.Println(m.foo, m.bar)
    }
    
    // main.go
    
    package main
    
    import (
     "example/model"
     "unsafe"
    )
    
    func main() {
     m := model.M{}
     m.Print()
    
     foo := unsafe.Pointer(&m)
     *(*uint32)(foo) = 1
     bar := unsafe.Pointer(uintptr(foo) + 4)
     *(*uint32)(bar) = 2
    
     m.Print()
    }
    
    // 输出结果:
    // 0 0
    // 1 2

    小 Tips

    Go 的底层 slice 切片源码就使用了 unsafe

    // slice 切片的底层结构
    type slice struct {
     // 底层是一个数组指针
     array unsafe.Pointer
     // 长度
     len int
     // 容量
     cap int
    }

    总结

    更多编程相关知识,请访问:编程视频!!

    以上就是go语言和c语言在指针上有什么区别的详细内容,更多请关注php中文网其它相关文章!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    上一篇:go语言有微服务框架吗 下一篇:自己动手写 PHP MVC 框架(40节精讲/巨细/新人进阶必看)

    相关文章推荐

    • go语言中的rune怎么获取字符长度• go语言字符串怎么求指定子串• go语言项目是怎么配置Gitlab CI的• go语言和swoole的区别是什么• c语言中源文件编译后生成什么文件• c语言怎么将十进制转为二进制
    1/1

    PHP中文网