Показаны сообщения с ярлыком указатели в Go. Показать все сообщения
Показаны сообщения с ярлыком указатели в Go. Показать все сообщения

суббота, 16 мая 2020 г.

Неверный адрес памяти или разыменование нулевого указателя в Golang

Почему эта программа паникует?

type Point struct {
    X, Y float64
}

func (p *Point) Abs() float64 {
    return math.Sqrt(p.X*p.X + p.Y*p.Y)
}

func main() {
    var p *Point
    fmt.Println(p.Abs())
}

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xd2c5a]

goroutine 1 [running]:
main.(*Point).Abs(...)
 ../main.go:6
main.main()
 ../main.go:11 +0x1a

Ответ

Неинициализированный указатель p в main функции равен nil, и вы не можете следовать за нулевым указателем.

Если x равен nil, попытка выполнить *x вызовет панику во время выполнения. Спецификация языка программирования Go: адресные операторы.

Вам необходимо создать Point. С помощью new:

func main() {
    var p *Point = new(Point)
    fmt.Println(p.Abs())
}

Либо с помощи литерала с оператором ссылки:

func main() {
    var p *Point = &Point{}
    fmt.Println(p.Abs())
}

Поскольку методы с получателями указателей принимают либо значение, либо указатель, вы также можете вообще пропустить указатель:

func main() {
    var p Point // имеет нулевое значение Point{X:0, Y:0}
    fmt.Println(p.Abs())
}


Читайте также:


среда, 29 апреля 2020 г.

Указатели по-простому в Golang

Указатель (pointer) - это переменная, которая содержит адрес объекта.

Структуры и массивы копируются при использовании в присваиваниях и передаются в качестве аргументов функциям. С помощью указателей этого можно избежать.

Указатели хранят адреса объектов. Адреса могут передаваться более эффективно, чем реальные объекты.

Указатель имеет тип *T. Ключевое слово new выделяет новый объект и возвращает его адрес.

type Human struct {
    Name string
}

var ps *Human = new(Human) 
// ps содержит адрес новой структуры

Объявление переменной можно записать более компактно.

ps := new(Human)

Адресный оператор

Оператор & возвращает адрес объекта.

s := Human{"Алиса"} // s содержит саму структуру
ps := &s            // ps содержит адрес структуры

Оператор & также может использоваться с составными литералами. Две строки выше могут быть записаны как

ps := &Human{"Алиса"}

Разыменование указателя

Для указателя x разыменование указателя *x обозначает значение, на которое указывает x. Разыменование указателя используется редко, поскольку Go может автоматически получить адрес переменной.

ps := new(Human)
ps.Name = "Алиса" 
// то же что и (*ps).Name = "Алиса"

Указатели как параметры

Используя указатель для изменения объекта, вы влияете на весь код, который использует объект.

// Ivan - это функция, которая не имеет никакого эффекта.
func Ivan(s Human) {
    s.Name = "Иван" // изменяет только локальную копию
}

// Oleg устанавливает pp.Name равным "Олег".
func Oleg(ps *Human) {
    ps.Name = "Олег"
}

func main() {
    s := Human{"Алиса"}

    Ivan(s)
    fmt.Println(s) // печатает {Алиса}

    Oleg(&s)
    fmt.Println(s) // печатает {Олег}
}


Читайте также:


понедельник, 15 июля 2019 г.

Команда cgo: передача указателей

Go - это язык со сборщиком мусора, и сборщик мусора должен знать расположение каждого указателя на память Go. Из-за этого существуют ограничения на передачу указателей между Go и C.

В этом посте термин указатель Go означает указатель на память, выделенную Go (например, с помощью оператора & или вызвав предопределенную функцию new), а термин C указатель означает указатель на память, выделенную C (например, путем вызова C.malloc). Является ли указатель указателем Go или указателем C, в любом случае это динамическое свойство, определяемое тем, как была выделена память; это не имеет ничего общего с типом указателя.

Обратите внимание, что значения некоторых типов Go, кроме нулевого значения типа, всегда включают указатели Go. Это верно для типов строки, среза, интерфейса, канала, карты и функций. Тип указателя может содержать указатель Go или указатель C. Типы массивов и структур могут включать или не включать указатели Go в зависимости от типов элементов. Все обсуждения ниже по поводу указателей Go применяются не только к типам указателей, но и к другим типам, которые включают указатели Go.

Код Go может передавать указатель Go в C, если память Go, на которую он указывает, не содержит указателей Go. Код C должен сохранять это свойство: он не должен хранить указатели Go в памяти Go, даже временно. При передаче указателя на поле в структуре рассматриваемая память Go - это память, занятая полем, а не вся структура. При передаче указателя на элемент в массиве или срезе рассматриваемая память Go представляет собой весь массив или весь базовый массив среза.

Код C может не сохранять копию указателя Go после возврата вызова. Это включает в себя тип _GoString_, который, как отмечено выше, включает в себя указатель Go; Значения _GoString_ не могут быть сохранены кодом C.

Функция Go, вызываемая кодом C, может не возвращать указатель Go (что означает, что она не может возвращать строку, срез, канал и т.д.). Функция Go, вызываемая кодом C, может принимать указатели C в качестве аргументов, и она может хранить данные не указателя или данные указателя C через эти указатели, но она не может хранить указатель Go в памяти, на которую указывает указатель C. Функция Go, вызываемая кодом C, может принимать указатель Go в качестве аргумента, но она должна сохранять свойство, что память Go, на которую указывает указатель, не содержит указателей Go.

Код Go не может хранить указатель Go в памяти C. Код C может хранить указатели Go в памяти C, в соответствии с приведенным выше правилом: он должен прекратить хранить указатель Go, когда функция C вернется.

Эти правила проверяются динамически во время выполнения. Проверка контролируется параметром cgocheck переменной среды GODEBUG. Значением по умолчанию является GODEBUG=cgocheck=1, которое реализует достаточно дешевые динамические проверки. Эти проверки могут быть полностью отключены с помощью GODEBUG=cgocheck=0. Полная проверка обработки указателя, за определенную плату во время выполнения, доступна через GODEBUG=cgocheck=2.

Можно победить это применение с помощью unsafe пакета, и, конечно, ничто не мешает коду C делать все, что ему нравится. Однако программы, нарушающие эти правила, могут потерпеть неудачу неожидаемым и непредсказуемым образом.

Примечание: текущая реализация имеет ошибку. В то время как коду Go разрешено записывать ноль или указатель C (но не указатель Go) в память C, текущая реализация может иногда вызывать ошибку времени выполнения, если содержимое памяти C выглядит как указатель Go. Поэтому избегайте передачи неинициализированной памяти C в код Go, если код Go будет хранить в ней значения указателя. Обнулите память в C, прежде чем передать ее в Go.


Читайте также:


вторник, 28 мая 2019 г.

Спецификация Go: типы указателя (pointer types), типы функций (function types)

Типы указателя (pointer types)

Тип указателя обозначает набор всех указателей на переменные данного типа, называемые базовым типом указателя. Значение неинициализированного указателя равно nil.

PointerType = "*" BaseType .
BaseType    = Type .

*Point
*[4]int

Типы функций (function types)

Тип функции (function type) обозначает набор всех функций с одинаковыми параметрами и типами результатов. Значение неинициализированной переменной типа функции равно nil.

FunctionType   = "func" Signature .
Signature      = Parameters [ Result ] .
Result         = Parameters | Type .
Parameters     = "(" [ ParameterList [ "," ] ] ")" .
ParameterList  = ParameterDecl { "," ParameterDecl } .
ParameterDecl  = [ IdentifierList ] [ "..." ] Type .

В списке параметров или результатов имена (IdentifierList) должны либо присутствовать, либо отсутствовать. Если присутствует, каждое имя обозначает один элемент (параметр или результат) указанного типа, и все непустые имена в подписи должны быть уникальными. Если отсутствует, каждый тип обозначает один элемент этого типа. Списки параметров и результатов всегда заключаются в круглые скобки, за исключением того, что если есть ровно один безымянный результат, он может быть записан как тип без скобок.

Последний входящий параметр в сигнатуре функции может иметь тип с ... префиксом. Функция с таким параметром называется variadic и может вызываться с нулевым или большим количеством аргументов для этого параметра.

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)


Читайте также:


пятница, 22 февраля 2019 г.

Go FAQ: Почему в Go нет арифметики с указателями?

Безопасность. Без арифметики указателей можно создать язык, который никогда не может получить незаконный адрес, который завершается некорректно. Компилятор и аппаратные технологии продвинулись к точке, в которой цикл, использующий индексы массива, может быть таким же эффективным, как цикл использующий арифметику указателей. Кроме того, отсутствие арифметики указателей может упростить реализацию сборщика мусора.


Читайте также:


пятница, 15 февраля 2019 г.

Go FAQ: Следует ли определять методы для значений или указателей?

// метод для указателя
func (s *MyStruct) pointerMethod() { } 

// метод для значения
func (s MyStruct)  valueMethod()   { } 

Для программистов, не привыкших к указателям, различие между этими двумя примерами может сбивать с толку, но на самом деле ситуация очень проста. При определении метода для типа получатель (s в приведенных выше примерах) ведет себя точно так же, как если бы это был аргумент метода. Определять ли получатель как значение или как указатель - это тот же вопрос что и, должен ли аргумент функции быть значением или указателем? Есть несколько соображений по этому поводу.

Во-первых, и это наиболее важно, метод должен изменить получатель? Если это так, получатель должен быть указателем. (Срезы и карты выступают в качестве ссылок, поэтому их история немного более тонкая, но, например, для изменения длины среза в методе получатель должен быть указателем.) В приведенных выше примерах, если pointerMethod изменяет поля s, вызывающая сторона увидит эти изменения, но valueMethod вызывается с копией аргумента вызывающей стороны (это определение передачи значения), поэтому изменения, которые он вносит, будут невидимы для вызывающей стороны.

Кстати, в Java приемники методов всегда указатели, хотя их указательный характер несколько замаскирован (существует предложение добавить приемники значения к языку).

Во-вторых, учет эффективности. Если приемник большой, например, большая struct, это будет намного дешевле использовать указатель приемника.

Следующее - последовательность. Если некоторые из методов типа должны иметь приемник указателя, остальные тоже должны, поэтому набор методов соответствует независимо от того, как используется тип.

Для таких типов, как базовые типы, срезы и небольшие struct, получатель значения очень дешев, поэтому, если семантика метода требует указателя, получатель значения эффективен и понятен.


Читайте также:


Go FAQ: Когда следует использовать указатель на интерфейс?

Почти никогда. Указатели на интерфейсные значения возникают только в редких, сложных ситуациях, связанных с маскировкой типа значения интерфейса для отложенной оценки.

Это распространенная ошибка - передать указатель на значение интерфейса в функцию, ожидающую интерфейс. Компилятор пожалуется на эту ошибку, но ситуация все еще может сбивать с толку, потому что иногда указатель необходим для удовлетворения интерфейса. Понимание того, что, хотя указатель на конкретный тип может удовлетворить интерфейс, за одним исключением указатель на интерфейс, никогда не сможет удовлетворить интерфейс.

Рассмотрим объявление переменной,

var w io.Writer

Функция печати fmt.Fprintf принимает в качестве первого аргумента значение, которое удовлетворяет io.Writer - то, что реализует канонический метод Write. Таким образом, мы можем написать

fmt.Fprintf(w, "hello, world\n")

Однако если мы передадим адрес w, программа не скомпилируется.

// Compile-time error (Ошибка компиляции).
fmt.Fprintf(&w, "hello, world\n") 

Единственным исключением является то, что любое значение, даже указатель на интерфейс, может быть назначено переменной пустого типа интерфейса (interface{}). Тем не менее, это почти наверняка ошибка, если значение является указателем на интерфейс; результат может сбивать с толку.


Читайте также: