乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Go 與 C 的指針

       F2967527 2021-09-16

      CGo 都是有指針概念的語言,這篇文章主要借這兩者之間的異同來加深對 Go 指針的理解和使用。

      運算符

      C 和 Go 都相同:

      • & 運算符取出變量所在的內(nèi)存地址

      • * 運算符取出指針變量所指向的內(nèi)存地址里面的值,也叫 “ 解引用

      C 語言版示例:

      #include <stdio.h>

      int main()
      {
          int bar = 1;
          // 聲明一個指向 int 類型的值的指針
          int *ptr;
          // 通過 & 取出 bar 變量所在的內(nèi)存地址并賦值給 ptr 指針
          ptr = &bar;
          // 打印 ptr 的值(為地址),*prt 表示取出指針變量所指向的內(nèi)存地址里面的值
          printf('%p %d\n', ptr, *ptr);
          return (0);
      }

      // 輸出結(jié)果:
      // 0x7ffd5471ee54 1

      Go 語言版示例:

      package main

      import 'fmt'

      func main() {
       bar := 1
       // 聲明一個指向 int 類型的值的指針
       var ptr *int
       // 通過 & 取出 bar 變量所在的內(nèi)存地址并賦值給 ptr 指針
       ptr = &bar
       // 打印 ptr 變量儲存的指針地址,*prt 表示取出指針變量所指向的內(nèi)存地址里面的值
       fmt.Printf('%p %d\n', ptr, *ptr)
      }

      // 輸出結(jié)果:
      // 0xc000086020 1

      Go 還可以使用 new 關(guān)鍵字來分配內(nèi)存創(chuàng)建指定類型的指針。

       // 聲明一個指向 int 類型的值的指針
       // var ptr *int
       ptr := new(int)
       // 通過 & 取出 bar 變量所在的內(nèi)存地址并賦值給 ptr 指針
       ptr = &bar

      數(shù)組名和數(shù)組首地址

      對于一個數(shù)組

      // C
      int arr[5] = {12345};
      // Go
      // 需要指定長度,否則類型為切片
      arr := [5]int{12345}

      在 C 中,數(shù)組名 arr 代表的是數(shù)組首元素的地址,相當于 &arr[0]

      &arr 代表的是整個數(shù)組 arr 的首地址

      // C
      // arr 數(shù)組名代表數(shù)組首元素的地址
      printf('arr -> %p\n', arr);
      // &arr[0] 代表數(shù)組首元素的地址
      printf('&arr[0] -> %p\n', &arr[0]);
      // &arr 代表整個數(shù)組 arr 的首地址
      printf('&arr -> %p\n', &arr);

      // 輸出結(jié)果:
      // arr -> 0061FF0C
      // &arr[0] -> 0061FF0C
      // &arr -> 0061FF0C

      運行程序可以發(fā)現(xiàn) arr&arr 的輸出值是相同的,但是它們的意義完全不同。

      首先數(shù)組名 arr 作為一個標識符,是 arr[0] 的地址,從 &arr[0] 的角度去看就是一個指向 int 類型的值的指針。

      &arr 是一個指向 int[5] 類型的值的指針。

      可以進一步對其進行指針偏移驗證

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

      // 輸出結(jié)果:
      // arr+1 -> 0061FF10
      // &arr+1 -> 0061FF20

      這里涉及到偏移量的知識:一個類型為 T 的指針的移動,是以 sizeof(T) 為移動單位的。

      • arr+1 : arr 是一個指向 int 類型的值的指針,因此偏移量為 1*sizeof(int)

      • &arr+1 : &arr 是一個指向 int[5] 的指針,它的偏移量為 1*sizeof(int)*5

      到這里相信你應(yīng)該可以理解 C 語言中的 arr&arr 的區(qū)別了吧,接下來看看 Go 語言

      // 嘗試將數(shù)組名 arr 作為地址輸出
      fmt.Printf('arr -> %p\n', arr)
      fmt.Printf('&arr[0] -> %p\n', &arr[0])
      fmt.Printf('&arr -> %p\n', &arr)

      // 輸出結(jié)果:
      // arr -> %!p([5]int=[1 2 3 4 5])
      // &arr[0] -> 0xc00000c300
      // &arr -> 0xc00000c300

      &arr[0]&arr 與 C 語言一致。

      但是數(shù)組名 arr 在 Go 中已經(jīng)不是數(shù)組首元素的地址了,代表的是整個數(shù)組的值,所以輸出時會提示 %!p([5]int=[1 2 3 4 5])

      指針運算

      指針本質(zhì)上就是一個無符號整數(shù),代表了內(nèi)存地址。

      指針和整數(shù)值可以進行加減法運算,比如上文的指針偏移例子:

      • n : 一個類型為 T 的指針,以 n*sizeof(T) 為單位向高位移動。

      • n : 一個類型為 T 的指針,以 n*sizeof(T) 為單位向低位移動。

      其中 sizeof(T) 代表的是數(shù)據(jù)類型占據(jù)的字節(jié),比如 int 在 32 位環(huán)境下為 4 字節(jié),64 位環(huán)境下為 8 字節(jié)

      C 語言示例:

      #include <stdio.h>

      int main()
      {
          int arr[] = {12345};
          // ptr 是一個指針,為 arr 數(shù)組的第一個元素地址
          int *ptr = arr;
          printf('%p %d\n', ptr, *ptr);

          // ptr 指針向高位移動一個單位,移向到 arr 數(shù)組第二個元素地址
          ptr++;
          printf('%p %d\n', ptr, *ptr);
          return (0);
      }

      // 輸出結(jié)果:
      // 0061FF08 1
      // 0061FF0C 2

      在這里 ptr++0061FF08 移動了 sizeof(int) = 4 個字節(jié)到 0061FF0C ,指向了下一個數(shù)組元素的地址

      Go 語言示例:

      package main

      import 'fmt'

      func main() {
       arr := [5]uint32{12345}

       // ptr 是一個指針,為 arr 數(shù)組的第一個元素地址
       ptr := &arr[0]
       fmt.Println(ptr, *ptr)

       // ptr 指針向高位移動一個單位,移向到 arr 數(shù)組第二個元素地址
       ptr++
       fmt.Println(ptr, *ptr)
      }

      // 輸出結(jié)果:
      // 編譯報錯:
      // .\main.go:13:5: invalid operation: ptr++ (non-numeric type *uint32)

      編譯報錯 *uint32 非數(shù)字類型,不支持運算,說明 Go 是不支持指針運算的。

      這個其實在 Go Wiki[1] 中的 Go 從 C++ 過渡文檔中有提到過:Go has pointers but not pointer arithmetic.

      Go 有指針但不支持指針運算。

      另辟蹊徑

      那還有其他辦法嗎?答案當然是有的。

      在 Go 標準庫中提供了一個 unsafe 包用于編譯階段繞過 Go 語言的類型系統(tǒng),直接操作內(nèi)存。

      我們可以利用 unsafe 包來實現(xiàn)指針運算。

      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 的內(nèi)置類型。是一個無符號整數(shù),用來存儲地址,支持數(shù)學(xué)運算。常與 unsafe.Pointer 配合做指針運算

      • unsafe.Pointer : 表示指向任意類型的指針,可以和任何類型的指針互相轉(zhuǎn)換(類似 C 語言中的 void* 類型的指針),也可以和 uintptr 互相轉(zhuǎn)換

      • unsafe.Sizeof : 返回操作數(shù)在內(nèi)存中的字節(jié)大小,參數(shù)可以是任意類型的表達式,例如 fmt.Println(unsafe.Sizeof(uint32(0))) 的結(jié)果為 4

      • unsafe.Offsetof : 函數(shù)的參數(shù)必須是一個字段 x.f,然后返回 f 字段相對于 x 起始地址的偏移量,用于計算結(jié)構(gòu)體成員的偏移量

      原理:

      Go 的 uintptr 類型存儲的是地址,且支持數(shù)學(xué)運算

      *T (任意指針類型) 和 unsafe.Pointer 不能運算,但是 unsafe.Pointer 可以和 *T 、 uintptr 互相轉(zhuǎn)換

      因此,將 *T 轉(zhuǎn)換為 unsafe.Pointer 后再轉(zhuǎn)換為 uintptruintptr 進行運算之后重新轉(zhuǎn)換為 unsafe.Pointer => *T 即可

      代碼實現(xiàn):

      package main

      import (
       'fmt'
       'unsafe'
      )

      func main() {
       arr := [5]uint32{12345}

       ptr := &arr[0]

       // ptr(*uint32) => unsafe.Pointer
       unsafePtr := unsafe.Pointer(ptr)
       // (*uint32)(unsafePtr) : unsafe.Pointer => *uint32
       fmt.Println(unsafePtr, *(*uint32)(unsafePtr))

       // unsafe.Pointer => uintptr
       // uintptr 向高位移動 unsafe.Sizeof(arr[0]) = 4 字節(jié)
       uintptrPtr := uintptr(unsafePtr) + unsafe.Sizeof(arr[0])

       // uintptr => unsafe.Pointer
       unsafePtr = unsafe.Pointer(uintptrPtr)
       // (*uint32)(unsafePtr) : unsafe.Pointer => *uint32
       fmt.Println(unsafePtr, *(*uint32)(unsafePtr))
      }


      // 輸出結(jié)果:
      // 0xc000012150 1
      // 0xc000012154 2

      小 Tips

      Go 的底層 slice 切片源碼就使用了 unsafe

      // slice 切片的底層結(jié)構(gòu)
      type slice struct {
       // 底層是一個數(shù)組指針
       array unsafe.Pointer
       // 長度
       len int
       // 容量
       cap int
      }

      總結(jié)

      • Go 可以使用 & 運算符取地址,也可以使用 new 創(chuàng)建指針

      • Go 的數(shù)組名不是首元素地址

      • Go 的指針不支持運算

      • Go 可以使用 unsafe 包打破安全機制來操控指針,但對我們開發(fā)者而言,是 'unsafe' 不安全的

      參考資料

      [1]

      Go Wiki: https://github.com/golang/go/wiki/GoForCPPProgrammers



        本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
        轉(zhuǎn)藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多