Home  >  Article  >  Backend Development  >  What is the difference between go language and c language on pointers?

What is the difference between go language and c language on pointers?

青灯夜游
青灯夜游Original
2022-11-30 19:50:406712browse

Difference: 1. Go language can use the new keyword to allocate memory and create pointers of specified types, but C language cannot. 2. The array name arr in the C language represents the address of the first element of the array, which is equivalent to "&arr[0]"; the array name arr in the Go language does not represent the address of the first element of the array, but represents the value of the entire array. 3. Go language does not support pointer arithmetic, but C language supports pointer arithmetic. 4.

What is the difference between go language and c language on pointers?

The operating environment of this tutorial: Windows 7 system, GO version 1.18, Dell G3 computer.

C and Go are both languages ​​with pointer concepts. This article mainly uses the similarities and differences between the two to deepen the understanding and use of Go pointers.

Operator

Both C and Go are the same:

  • & Operator takes out the location of the variable The memory address

  • * operator takes out the value in the memory address pointed to by the pointer variable, also called "Dereference"

C language version example:

#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 language version example:

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 can also use the new keyword to allocate memory Creates a pointer of the specified type.

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

Array name and array first address

For an array

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

In C, the array name arr represents The address of the first element of the array is equivalent to &arr[0]

and &arr represents the first address of the entire array 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

Run the program It can be found that the output values ​​​​of arr and &arr are the same, but their meanings are completely different.

First of all, the array name arr as an identifier is the address of arr[0], viewed from the perspective of &arr[0] It is a pointer to a value of type int.

And &arr is a pointer to a value of type int[5].

You can further verify the pointer offset

// C
// 指针偏移
printf("arr+1 -> %p\n", arr + 1);
printf("&arr+1 -> %p\n", &arr + 1);

// 输出结果:
// arr+1 -> 0061FF10
// &arr+1 -> 0061FF20

This involves the knowledge of offset: the movement of a pointer of type T is based on sizeof(T) is the moving unit.

  • arr 1 : arr is a pointer to a value of type int, so the offset is 1*sizeof(int)

  • ##&arr 1 : &arr is a pointer pointing to int[5], its offset is 1*sizeof(int)*5

I believe you should be able to understand the difference between

arr and &arr in C language. Next, let’s take a look at Go language

// 尝试将数组名 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] and &arr are consistent with C language.

But the array name

arr in Go is no longer the address of the first element of the array, it represents the value of the entire array, so the output will prompt %!p([5 ]int=[1 2 3 4 5])

Pointer arithmetic

A pointer is essentially an unsigned integer, representing a memory address.

Pointers and integer values ​​can be added and subtracted, such as the pointer offset example above:

  • Add

    n: A type is The pointer of T moves to the high bit in units of n*sizeof(T).

  • minus

    n: A pointer of type T, in units of n*sizeof(T) Move low.

sizeof(T) represents the bytes occupied by the data type, for example int is 4 bytes in a 32-bit environment , 8 bytes in 64-bit environment

C language example:

#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
Here

ptr moved from 0061FF08 sizeof( int) = 4 bytes to 0061FF0C, pointing to the address of the next array element

Go language example:

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

核心介绍:

  • uintptr : Go 的内置类型。是一个无符号整数,用来存储地址,支持数学运算。常与 unsafe.Pointer 配合做指针运算

  • unsafe.Pointer : 表示指向任意类型的指针,可以和任何类型的指针互相转换(类似 C 语言中的 void* 类型的指针),也可以和 uintptr 互相转换

  • unsafe.Sizeof : 返回操作数在内存中的字节大小,参数可以是任意类型的表达式,例如 fmt.Println(unsafe.Sizeof(uint32(0))) 的结果为 4

  • unsafe.Offsetof : 函数的参数必须是一个字段 x.f,然后返回 f 字段相对于 x 起始地址的偏移量,用于计算结构体成员的偏移量

原理:

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

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

因此,将 *T 转换为 unsafe.Pointer 后再转换为 uintptr ,uintptr 进行运算之后重新转换为 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 可以使用 & 运算符取地址,也可以使用 new 创建指针

  • Go 的数组名不是首元素地址

  • Go 的指针不支持运算

  • Go 可以使用 unsafe 包打破安全机制来操控指针,但对我们开发者而言,是 "unsafe" 不安全的

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

The above is the detailed content of What is the difference between go language and c language on pointers?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn