Указатель это переменная в которой указан адрес в памяти. В языке С (и в Go) параметры функции передаются по значению и если мы передадим указатель, то внутри функции получим новый указатель на тот же адрес. Из-за этого возникает проблема, когда мы создаем нулевой указатель, передаем его в функцию, а внутри функции этому нулевому указателю присвается адрес. Т.к. внутри функции у нас другой указатель, то переданный в функцию указатель так и остается нулевым после выхода из функции. Для решения данной проблемы используется двойные указатели (указатель на указатель, передача адреса указателя). Рассмотрим подробнее на примерах.

Создаем файл main.go :

package main

/*
int SetNewValueOne(void* p)
{
    printf("p=%p &p=%p \n", p, &p);
    if(!p)
    {
        int i=5;
        p= &i;
	printf("p=%p &p=%p *p=%p \n", p, &p, *(int *)p);
        return 1;
    }
    return 0;
}

int SetNewValue(void** p)
{
    printf("p=%p &p=%p *p=%d \n", p, &p, *p);
    if(!*p)
    {
	int i=5;
        *p= &i;
	printf("p=%p &p=%p *p=%p \n", p, &p, *p);
	printf("**p=%d \n", *(int*)(*p));
        return 1;
    }
    return 0;
}

*/
import "C"
import (
	"fmt"
	"unsafe"
)

func callOne() {
  println("--- callOne ---")
  println("------------------------------------------------------------")
  var ptr *int32
  
  fmt.Printf("ptr=%p (%d) &ptr=%p \n", ptr, ptr, &ptr)
    
  C.SetNewValueOne((unsafe.Pointer)(ptr))

  fmt.Printf("ptr=%p (%d) &ptr=%p \n", ptr, ptr, &ptr)
  if ptr != nil {
    fmt.Printf("*ptr=%d *ptr(bits)=%b \n", *ptr, *ptr)
  }
  println("------------------------------------------------------------")
  
}

func callDouble() {
  println("--- callDouble ---")
  println("------------------------------------------------------------")

  var ptr *int32
  
  fmt.Printf("ptr=%p (%d) &ptr=%p \n", ptr, ptr, &ptr)

  uptr := unsafe.Pointer(&ptr)
  
  fmt.Printf("uptr=%p (%d) &uptr=%p \n", uptr, uptr, &uptr)

  C.SetNewValue((*unsafe.Pointer)(uptr))

  fmt.Printf("ptr=%p (%d) &ptr=%p \n", ptr, ptr, &ptr)
  fmt.Printf("*ptr=%d *ptr(bits)=%b \n", *ptr, *ptr)
  println("------------------------------------------------------------")
}

func main() {
  callOne()
  callDouble()
}

В первом случае передается обычный указатель. Вывод будет такой:

--- callOne ---
------------------------------------------------------------
ptr=0x0 (0) &ptr=0xc00004e020
p=0000000000000000 &p=00000035337FF838
p=00000035337FF834 &p=00000035337FF838 *p=0000000000000005
ptr=0x0 (0) &ptr=0xc00004e020
------------------------------------------------------------

Т.к. параметром С функции у нас является void*, то в Go мы должны привести наш указатель к unsafe.Pointer. Видно, что в Go мы создали один указатель, а внутри С функции уже другой (копирование параметра по значению) и после выхода из функции С у нас так и остался нулевой указатель.

Во втором случае передаем двойной указатель. Вывод будет таким:

--- callDouble ---
------------------------------------------------------------
ptr=0x0 (0) &ptr=0xc00004e030
uptr=0xc00004e030 (824634040368) &uptr=0xc00004e038
p=000000C00004E030 &p=0000006FE23FFD80 *p=0
p=000000C00004E030 &p=0000006FE23FFD80 *p=0000006FE23FFD6C
**p=5
ptr=0x6fe23ffd6c (480537214316) &ptr=0xc00004e030
*ptr=5 *ptr(bits)=101
------------------------------------------------------------

Мы создаем в Go указатель на на наш нулевой указатель и этот двойной указатель (uptr) передаем в С функцию. Внутри С функции получаем первый нулевой указатель и делаем его указывающим на ячейку памяти со значением int. После выхода из С функции наш Go указатель указывает на число 5.

Есть еще следующий нюанс. В Go тип int 64 бита, а в C 32 бита (в Windows 11). Если в Go сделать тип указателя int, а не int32, то вывод будет такой:

--- callDouble ---
------------------------------------------------------------
ptr=0x0 (0) &ptr=0xc00004e030
uptr=0xc00004e030 (824634040368) &uptr=0xc00004e038
p=000000C00004E030 &p=00000054AA3FFD80 *p=0
p=000000C00004E030 &p=00000054AA3FFD80 *p=00000054AA3FFD6C
**p=5
ptr=0x54aa3ffd6c (363633573228) &ptr=0xc00004e030
*ptr=-6178941403171651579 *ptr(bits)=-101010111000000000000100111011111111111111111111111111111111011
------------------------------------------------------------

компилятор ошибки не выдаст.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *