programowanieProgramista Backend

Opisz cechy pracy z wskaźnikami (pointers) w Go: kiedy warto ich używać, czym różnią się od referencji w innych językach i jakie są typowe pułapki związane z ich używaniem?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Go wskaźnik to zmienna przechowująca adres innej zmiennej. W przeciwieństwie do niektórych języków, w Go nie można wykonywać arytmetyki wskaźników, co czyni je bezpieczniejszymi. Wskaźniki w Go są często stosowane do:

  • Przekazywania dużych struktur do funkcji (aby uniknąć kopiowania).
  • Zmiany obiektu z zewnętrznej funkcji.
  • Realizacji złożonych struktur danych (na przykład list, drzew).

W Go brakuje wyraźnego wsparcia dla typów referencyjnych, jak na przykład w C++, jednak slices i maps są z natury referencyjne i często przekazuje się je przez wartość.

Przykład przekazywania wskaźnika i wartości:

package main import "fmt" type User struct { Name string } func changeNameByValue(u User) { u.Name = "Wania" } func changeNameByPointer(u *User) { u.Name = "Petja" } func main() { user := User{Name: "Iwan"} changeNameByValue(user) fmt.Println(user.Name) // Iwan changeNameByPointer(&user) fmt.Println(user.Name) // Petja }

Pytanie do przemyślenia.

Czy wskaźniki mogą wskazywać na stałe lub literały w Go?

Poprawna odpowiedź: Nie, w Go nie można bezpośrednio wziąć adresu ze stałej lub literału. Adres można wziąć tylko ze zmiennej:

x := 5 p := &x // OK p2 := &10 // Błąd kompilacji

Często myli się to z językami, w których coś takiego jest dozwolone.

Przykłady rzeczywistych błędów spowodowanych niewiedzą na temat tej tematyki.


Historia

Na jednym projekcie programista próbował przekazać wskaźnik na element slajsa, sądząc, że może bezpiecznie modyfikować slajs przez wskaźnik. Popełnił błąd: slajs sam w sobie zawiera wskaźnik, ale po append() adres podstawowej tablicy się zmienia, a zmiany nie zawsze były odzwierciedlane w danych źródłowych. Doprowadziło to do trudnych do zdiagnozowania błędów (dane były "zgubione").


Historia

W projekcie utworzono listę wskaźników na lokalne zmienne w pętli, a po wyjściu z pętli wszystkie wskaźniki wskazywały na tę samą zmienną (zmienną iteracyjną pętli). Błąd zauważono dopiero po awarii w produkcji.


Historia

Programista błędnie założył, że przypisanie wskaźnika zwalnia poprzedni obiekt z pamięci, i jawnie przypisywał wskaźnikowi nil, oczekując natychmiastowego działania GC. W praktyce w Go zbieracz śmieci sam decyduje, kiedy oczyszczać obiekty, ignorując przedwczesne zerowanie odniesień. Doprowadziło to do wycieków tam, gdzie oczekiwano na terminowe zwolnienie pamięci.