Пишу, по большей части, про историю, свою жизнь и немного про программирование.

«Go»: указатели и арифметика

Одна из особенностей «Go», о которой пишут в большинстве статей по языку, это наличие в языке указателей, но отсутствие арифметики с ними. Вот, например, цитата из статьи «Go For C++ Programmers»:

Go has pointers but not pointer arithmetic. You cannot use a pointer variable to walk through the bytes of a string.

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

package main
func main() {
    var pointer *[]uint8 = new([]uint8)
    pointer++ // invalid operation: pointer += 1 (mismatched types *[]uint8 and int)
}

Никакими преобразованиями базовых типов не добиться чтобы язык разрешил прибавить хоть что-то к указателю. Я написал «базовых» и это неслучайно. Язык действительно не содержит арифметики с указателями, но в языке есть стандартные модули.

В модуле «unsafe» есть специальный тип «Pointer», в руководстве по использованию которого написано следующее:

1) A pointer value of any type can be converted to a Pointer.
2) A Pointer can be converted to a pointer value of any type.
3) A uintptr can be converted to a Pointer. 4) A Pointer can be converted to a uintptr.

В свою очередь, uintptr встроен в язык и это не указатель, это обычный числовой тип, пусть вас не смущает слово «ptr». Указатель напрямую нельзя преобразовать к типу uintptr или обратно, но можно это сделать через unsafe.Pointer, как видно из цитаты выше.

И вот что получается:

package main

import "fmt"
import "unsafe"

func main() {
    e := [...]uint8{11,22,33} // создаю массив из трёх байт — 1, 2, 3
    p := &e[0] // p — указатель на первый элемент массива (тип *uint8)

    fmt.Println(*p) // выводит «11»

    p = (*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1)) // указатель преобразован в число, прибавляется «1», преобразуем обратно в указатель
    fmt.Println(*p) // выводит «22»

    s := "12345" // строка, содержащая «12345»
    *(*uintptr)(unsafe.Pointer(&s))++ // сдвигаем адрес, на который ссылается «s» 

    fmt.Println(s, len(s)) // «2345», но длина всё равно 5 (!), в последнем байте неотображаемый символ
}
10 комментариев
twitter.com/timurv 2011

Мне, кажется, что ты пытаешься писать на этом языке так же как на С++, к примеру, что не правильно.
Ну модуль же называется unsafe =)

Часто такое вижу, когда на ruby пишут как на C++ или как на Java, вроде код на Ruby, а все таки не на Ruby

Евгений Степанищев (bolknote.ru) 2011

Комментарий для twitter.com/timurv:

Мне, кажется, что ты пытаешься писать на этом языке так же как на С++, к примеру, что не правильно.

Я не знаю C++. Я не пытаюсь писать на этом языке как на C++ или чём-то подобном. Это статья просто показывает, что арифметики нет, но если очень надо, то она есть.

Мне это уже один раз понадобилось ( http://bolknote.ru/all/3197 ), когда я вызвал syscall ioctl, которому нужно было передать структуру, которую «Go» сдвигал на 4 байта в памяти (выравнивал по границе 8 байт). Вот тогда-то я и научился сдвигать указатель.

P.S. Кстати, прикрутил бы к своему блогу OpenID, это же просто ( http://bolknote.ru/all/1514/ ) и подписывался бы им?

Orcinus Orca (orcinus.ru) 2011

Или граватарку прицепил. Я вот OpenID лишь недавно победил на этом сайте.

А длина строковой переменной вероятно на единичку больше из-за того, что там Ноль в конце? Как в Си реализовано хранение строковых переменных. По строкам мне больше паскаль нравится, в ntmt строки можно хранить нулевой символ и в первом байте всегда есть указание длинны строки.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для orcinus.ru:

Или граватарку прицепил.

Для собственного сайта лучше использовать pavatar, мне кажется ( http://pavatar.com/ ).

Я вот OpenID лишь недавно победил на этом сайте.

Надо было всего лишь delegate сделать :)

А длина строковой переменной вероятно на единичку больше из-за того, что там Ноль в конце?

Не-а, там не сишные строки. Длина переменной просто осталась без изменений, потому что «Гоу» хранит её в другом месте.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для Евгения Степанищева:

То есть, если сдвинуть указатель на произвольное место, то там будет какой-нибудь мусор длины 5.

anonymous 2011

  А почему выбор пал на полумертвый Go, а не на Scala/Clojure или F#/Haskell? Во всех есть клевые фишки для распределенных/параллельных приложений. Или просто эти языки вам известны? Про ерланг не спрашиваю, потому что простой и неинтересный, мозг им не размять. Спасибо.

Евгений Степанищев (bolknote.ru) 2011

Комментарий для anonymous:

F# не хочу, мне как-то совершенно не хочется писать софт для Windows, мне чужда эта архитектура и туда не хочется. Мне интересен веб или command line.

Haskell возможно будет следующим.

Clojure — это Лисп, с Лиспом я баловался немного.

О Scala не задумывался, присмотрюсь, единственное, что меня смущает, говорят, что он похож на Java.

anonymous 2011

От f# один шаг к его старшему и аскетичному брату окамлу :)
Мне вот clojure не понравилась, но кажется что это из-за нелюбви к лиспам вообще. Но в clojure есть STM, например, только ради этого можно с ним поиграться. Хотя, пожалуй, лучше правда сразу хаскелл.
 
А Scala хороший язык, очень хороший. На голову выше Java.

Буду ждать с нетерпением постов про хаскелл! :)

Евгений Степанищев (bolknote.ru) 2011

Комментарий для anonymous:

Я так и не сказал почему именно Go.

Во-первых, мне в кои-то веки захотелось что-нибудь компилируемого, я давно ничего не пишу на компилируемых языках (редкое баловство с Си не считается)
Во-вторых, мне очень понравилось как язык легко обходится с многозадачностью
В-третьих, GC в компириемом языке, для меня это что-то необычное
В-четвёртых, Ваня Сагалаев этот язык очень хвалил в разговоре, а у меня, напротив, впечатление было так себе, решил узнать что он в нём нашёл :)

Буду ждать с нетерпением постов про хаскелл! :)

Это не раньше чем пойму, что знаю «Гоу» на крепком среднем уровне. У меня там несколько пробелов даже на уровне языка, не говоря уже о каком-то мышлении в рамках этого языка или понимания кишок, доступных программисту.

anonymous 2011

Получается, что в выборе Go решающую роль сыграл разговор с Ваней Сагалаевым :) т. к. по первым трем пунктам ни скала, ни хаскелл не проигрывают :) спасибо, за развернутые ответы!