[Go] Советы Golang: почему указатели на срезы полезны и как их игнорирование может привести к хитрым ошибкам (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
СомненияСегодня, пока я работал, возник хороший вопрос:
Почему во многих встроенных функциях и библиотеках часто можно увидеть в качестве аргументов указатели на срезы, ведь срезы всегда передаются по ссылке, не так ли?
Например, в реализации api-machinery Kubernetes мы можем увидеть функцию со следующей сигнатурой:
func ConvertSlicestringTostring (input * [] string, out * string, s conversion.Scope) error
И в примере очередей с приоритетом мы снова можем найти нечто подобное:
func (pq * PriorityQueue) Pop () interface {};
Разве срезы уже не являются указателями на хранимые в нем данные?
Давайте немного разберемся, используя Go-Playground, чтобы проверить поведение кода.Во время всего объяснения я буду использовать один и тот же пример: функция, которая инициализирует срез, передает его в качестве аргумента второй функции, изменяет его, а затем печатает срез для проверки содержимого.
Принято считать, что срезы передаются по ссылке. В следующем примере, на самом деле, будут напечатаны [b, b]и [b, b] даже если срез был инициализирован как [a, a], поскольку он был изменен во время выполнения анонимной функции, и изменение видно в main.
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice[0]="b";
slice[1]="b";
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
Использование указателей приводит к тому же по сути результату:
func main() {
slice:= []string{"a","a"}
func(slice *[]string){
(*slice)[0]="b";
(*slice)[1]="b";
fmt.Print(*slice)
}(&slice)
fmt.Print(slice)
}
Этот код также напечатает [b, b]и [b, b] Поэтому передача по указателю выглядит бесполезной, и кажется, что срез все равно передается по ссылке, а содержимое изменяется в обоих случаях.Так … почему же у этих функций такая сигнатура?ОбъяснениеВы можете примерно представить реализацию среза так:
type sliceHeader struct {
Length int
Capacity int
ZerothElement *byte
}
При передаче среза в функцию по значению все поля копируются, и только данные могут быть изменены и доступны извне через копию указателя.Однако имейте в виду, что если указатель будет перезаписан или изменен (из-за копирования, присвоения или добавления), никакие изменения не будут видны вне функции, более того, никакие изменения длины или емкости не будут видны для исходной функции.
Таким образом, ответ на вопрос прост, но он скрыт внутри реализации самого среза:
Почему во многих встроенных функциях и библиотеках часто можно увидеть в качестве аргументов указатели на срезы, ведь срезы всегда передаются по ссылке, не так ли?
Указатель на срез является неизменяемым в момент, когда функция собирается изменить структуру, размер или расположение среза в памяти. А эти изменения должны быть видны тому, кто вызывает функцию.Когда мы передаем срез функции в качестве аргумента, значения среза передаются по ссылке (поскольку мы передаем копию указателя), но все метаданные, описывающие сам срез, являются просто копиями.Мы можем изменить данные среза в анонимной функции, однако, если указатель на данные изменяется по какой-либо причине или метаданные среза изменяются, то это изменение может быть частично или вообще невидимым для внешней функции.Например, если под срез снова происходит выделение памяти, то используется новое место в памяти; даже если значения совпадают, срез указывает на новое место, и поэтому никакие изменения значений не будут видны, поскольку срезы указывают на два разных места (указатель в копии среза был перезаписан).Следовательно, в том же примере, но с принудительным повторным выделением среза будут напечатаны [b, b, a]и [a, a] Переместив append()ниже в место после манипулирования срезом, мы можем заметить, что поведение отличается, поскольку срез был перераспределен после манипуляции значениями, а указатель все еще указывает на начальный адрес памяти.
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice= append(slice, "a")
slice[0]="b";
slice[1]="b";
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
Проверим это на коде ниже:
func main() {
slice:= []string{"a","a"}
func(slice []string){
slice[0]="b";
slice[1]="b";
slice= append(slice, "a")
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
Он печатает [b, b, a]и [b, b]по объясненным выше причинам.Такое поведение может привести к трудно обнаруживаемым ошибкам, поскольку результат зависит от размера исходного массива, например, следующий код:
func main() {
slice:= make([]string, 2, 3)
func(slice []string){
slice= append(slice, "a")
slice[0]="b";
slice[1]="b";
fmt.Print(slice)
}(slice)
fmt.Print(slice)
}
Напечатает [b, b, a]и [b, b] поскольку на массив больше не происходит выделение памяти и указатель остается прежним.Однако при добавлении еще одной строки slice = append (slice, «a», «a») под массив снова выделяется память, и результатом будет [b, b, a, a]и [](пустой массив, поскольку он не был инициализирован).
Выявить такие ошибки среди сотен или тысяч строк может быть довольно сложно.Поэтому не забывайте, что вы можете передавать срез по значению, если хотите изменить только значения элементов, а не их количество или порядок, иначе время от времени будут возникать странные ошибки.
Теперь вы готовы понять, что выведет следующий код:
func main() {
slice:= make([]string, 1, 3)
func(slice []string){
slice=slice[1:3]
slice[0]="b"
slice[1]="b"
fmt.Print(len(slice))
fmt.Print(slice)
}(slice)
fmt.Print(len(slice))
fmt.Print(slice)
}
Можете запустить код на GoPlayground или написать ответ в комментариях.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Paolo Gallina
===========Похожие новости:
- [Тестирование IT-систем, Java, Google Chrome, API] Замена UI авторизации на API для автотестов
- [Информационная безопасность, Разработка под Android, Софт, IT-компании] Google добавила сервис VPN для Android в самый дорогой тариф подписки One
- [Клиентская оптимизация, Интернет-маркетинг, Контент-маркетинг, Поисковая оптимизация] How to rank higher on Google?
- [Работа с видео, Алгоритмы, Искусственный интеллект] Нейросеть Google переводит веб-страницы в видео
- [JavaScript, Программирование, Google Chrome, Управление медиа] Я никогда не писал расширения для Хрома, но меня допекли
- [Разработка систем связи, Сотовая связь, Будущее здесь] 312 дней в воздухе: аэростат Loon поставил рекорд по длительности полёта в стратосфере
- [Информационная безопасность, IT-компании] Конфиденциальные данные сотрудников Google, которых проверяла юридическая фирма Fragomen, скомпрометированы
- [Google API, Биллинговые системы, Развитие стартапа, Искусственный интеллект, Видеоконференцсвязь] Как мы в очередной раз пытаемся заменить людей на роботов
- [Реверс-инжиниринг, Разработка под Arduino] О декодировании протокола погодных датчиков Oregon Scientific
- [Поисковые технологии, IT-компании] FT: Apple решила создать свой поисковик
Теги для поиска: #_go, #_go, #_slices, #_pointer, #_gotcha, #_oshibki (ошибки), #_go
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:01
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
СомненияСегодня, пока я работал, возник хороший вопрос: Почему во многих встроенных функциях и библиотеках часто можно увидеть в качестве аргументов указатели на срезы, ведь срезы всегда передаются по ссылке, не так ли?
func ConvertSlicestringTostring (input * [] string, out * string, s conversion.Scope) error
func (pq * PriorityQueue) Pop () interface {};
Давайте немного разберемся, используя Go-Playground, чтобы проверить поведение кода.Во время всего объяснения я буду использовать один и тот же пример: функция, которая инициализирует срез, передает его в качестве аргумента второй функции, изменяет его, а затем печатает срез для проверки содержимого.
func main() {
slice:= []string{"a","a"} func(slice []string){ slice[0]="b"; slice[1]="b"; fmt.Print(slice) }(slice) fmt.Print(slice) } func main() {
slice:= []string{"a","a"} func(slice *[]string){ (*slice)[0]="b"; (*slice)[1]="b"; fmt.Print(*slice) }(&slice) fmt.Print(slice) } type sliceHeader struct {
Length int Capacity int ZerothElement *byte } Таким образом, ответ на вопрос прост, но он скрыт внутри реализации самого среза: Почему во многих встроенных функциях и библиотеках часто можно увидеть в качестве аргументов указатели на срезы, ведь срезы всегда передаются по ссылке, не так ли?
func main() {
slice:= []string{"a","a"} func(slice []string){ slice= append(slice, "a") slice[0]="b"; slice[1]="b"; fmt.Print(slice) }(slice) fmt.Print(slice) } func main() {
slice:= []string{"a","a"} func(slice []string){ slice[0]="b"; slice[1]="b"; slice= append(slice, "a") fmt.Print(slice) }(slice) fmt.Print(slice) } func main() {
slice:= make([]string, 2, 3) func(slice []string){ slice= append(slice, "a") slice[0]="b"; slice[1]="b"; fmt.Print(slice) }(slice) fmt.Print(slice) } Выявить такие ошибки среди сотен или тысяч строк может быть довольно сложно.Поэтому не забывайте, что вы можете передавать срез по значению, если хотите изменить только значения элементов, а не их количество или порядок, иначе время от времени будут возникать странные ошибки.
func main() {
slice:= make([]string, 1, 3) func(slice []string){ slice=slice[1:3] slice[0]="b" slice[1]="b" fmt.Print(len(slice)) fmt.Print(slice) }(slice) fmt.Print(len(slice)) fmt.Print(slice) } =========== Источник: habr.com =========== =========== Автор оригинала: Paolo Gallina ===========Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 20:01
Часовой пояс: UTC + 5