Как golang реализует преобразование между байтом [] и строкой? - PullRequest
1 голос
/ 27 мая 2020

Я не могу получить ответ, проверив сгенерированные сборки:

    {
        a := []byte{'a'}
        s1 := string(a)
        a[0] = 'b'
        fmt.Println(s1) // a
    }

    {
        a := "a"
        b := []byte(a)
        b[0] = 'b'
        fmt.Println(a) // a
    }

Почему наблюдается наблюдаемое поведение? Есть ли описание того, как go интерпретирует эти строки кода? Что делает компилятор go для преобразования типов?

1 Ответ

1 голос
/ 27 мая 2020

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

Как упоминалось в комментариях, спецификация языка определяет преобразование byte фрагментов в и из string типов, например:

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

Преобразование значения строкового типа в срез байтового типа дает срез, чьи последовательные элементы являются байтами строки.

Чтобы понять поведение ваших примеров, вы должны также прочитать определение типов string, также в спецификации :

Строки неизменяемы: после создания невозможно изменить содержимое строки.

Поскольку []byte является изменяемым, за кулисами go должен сделать копию соответствующих данных при преобразовании в string и обратно. Это можно проверить, напечатав адреса 0-го элемента объекта []byte и указателя на первый элемент данных в объекте string. Вот пример (и Go Версия игровой площадки ):

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    a := "a"
    b := []byte(a)
    ah := (*reflect.StringHeader)(unsafe.Pointer(&a))
    fmt.Printf("a: %4s @ %#x\n", a, ah.Data)
    fmt.Printf("b: %v @ %p\n\n", b, b)

    c := []byte{'a'}
    d := string(c)
    dh := (*reflect.StringHeader)(unsafe.Pointer(&d))
    fmt.Printf("c: %v @ %p\n", c, c)
    fmt.Printf("d: %4s @ %#x\n", d, dh.Data)
}

Результат выглядит следующим образом:

a:    a @ 0x4c1ab2
b: [97] @ 0xc00002c008

c: [97] @ 0xc00002c060
d:    a @ 0x554e21

Обратите внимание, что расположение указателя string и []byte не совпадают и не перекрываются. Поэтому нет никаких ожиданий, что изменение значений []byte каким-либо образом повлияет на значения string.


Хорошо, технически результат не должен был быть таким образом, потому что в моем примере я не вносил никаких изменений в значения b или c. Технически компилятор мог использовать ярлык и просто вызвать b a length = 1 []byte, начиная с того же адреса памяти, что и a. Но эта оптимизация была бы невозможна, если бы я сделал что-то вроде этого:

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    a := "a"
    b := []byte(a)
    b[0] = 'b'
    ah := (*reflect.StringHeader)(unsafe.Pointer(&a))
    fmt.Printf("a: %4s @ %#x\n", a, ah.Data)
    fmt.Printf("b: %v @ %p\n\n", b, b)
}

Вывод:

a:    a @ 0x4c1ab2
b: [98] @ 0xc00002c008

Посмотрите это в действии на Go Playground .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...