как понять связь между uintptr и struct? - PullRequest
0 голосов
/ 07 апреля 2020

Я выучил код, подобный следующему

func str2bytes(s string) []byte {
    x := (*[2]uintptr)(unsafe.Pointer(&s))
    h := [3]uintptr{x[0], x[1], x[1]}
    return *(*[]byte)(unsafe.Pointer(&h))
}

, эта функция должна изменить string на []byte без этапа копирования данных. Я пытаюсь преобразовать num в reverseNum

type Num struct {
    name  int8
    value int8
}

type ReverseNum struct {
    value int8
    name  int8
}
func main() {
    n := Num{100, 10}
    z := (*[2]uintptr)(unsafe.Pointer(&n))
    h := [2]uintptr{z[1], z[0]}
    fmt.Println(*(*ReverseNum)(unsafe.Pointer(&h))) // print result is {0, 0}
}

, этот код не дает желаемого результата. Кто-нибудь может рассказать мне о

Ответы [ 2 ]

3 голосов
/ 07 апреля 2020

Это слишком сложно.
Более простой

package main

import (
    "fmt"
    "unsafe"
)

type Num struct {
    name  int8
    value int8
}

type ReverseNum struct {
    value int8
    name  int8
}

func main() {
    n := Num{name: 42, value: 12}

    p := (*ReverseNum)(unsafe.Pointer(&n))

    fmt.Println(p.value, p.name)
}

выводит "42, 12".


Но реальный вопрос в том, почему на Земле вы захотите go для такой хитрости вместо того, чтобы копировать два чертовых байта, которые выполняются мгновенно на любом работающем ЦП Go, запущенных программах?

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

Также считаю, что, казалось бы, безобидные вещи, такие как также , имеют дополнительное поле ( даже типа struct{}!) в вашем типе данных может сделать интересными вещами для размещения в памяти переменных этих типов, поэтому может быть совершенно опасно предположить, что вы можете Переосмыслить Память о Go переменных, как вы хотите.

2 голосов
/ 08 апреля 2020

... Я просто хочу узнать о принципе, лежащем в основе небезопасного пакета.

Это аварийный люк .

Все решительно типизированные, но скомпилированные языки имеют основную проблему c: реальные машины, на которых будут работать скомпилированные программы, не имеют той же системы типизации, что и компилятор. 1 То есть сама машина, вероятно, имеет линейное адресное пространство, в котором байты собраны в машинные слова , сгруппированные в страницы и т. д. Операционная система может также обеспечить доступ, скажем, к степени детализации страницы: если вам нужно больше памяти, ОС предоставит вам одну страницу - 4096 байт, или 8192 байт, или 65536 байт, или любого размера страницы - дополнительной памяти на время.

Есть много способов решить эту проблему. Например, можно написать код непосредственно на машинном (или ассемблерном) языке, используя набор инструкций оборудования, чтобы общаться с ОС для достижения вещей на уровне ОС. Затем этот код может взаимодействовать с скомпилированной программой, действуя как go -вместо. Если скомпилированная программа должна выделить 40-байтовую структуру данных, этот код машинного уровня может выяснить, как это сделать в рамках ограничений по размеру страницы ОС.

Но написание машинного кода затруднительно и кропотливый. Именно поэтому у нас есть языки высокого уровня и компиляторы. Что если бы у нас был способ, на языке высокого уровня, нарушить нормальные правила, навязанные языком? Нарушая определенные c требования определенными c способами, тщательно координируя эти способы со всем другим кодом, который также нарушает эти требования, мы можем, в коде, который мы отдаляем от обычного программирования приложений, писать большую часть нашего управления памятью, управление процессами и т. д. на нашем языке высокого уровня.

Другими словами, мы можем использовать unsafe (или что-то подобное в других языках) для намеренно break type- безопасность обеспечивается нашим языком высокого уровня. Когда мы делаем это - когда мы нарушаем правила - мы должны знать, каковы все правила, и что наши указанные здесь c нарушения будут работать правильно, в сочетании со всем нормальным кодом, который выполняет , подчиняется нормальному rules и в сочетании со всем специальным, небезопасным кодом, который нарушает правила.

Для этого часто требуется помощь самого компилятора. Если вы осмотрите источник runtime, поставляемый с Go, вы найдете подпрограммы с аннотациями, такими как go:noescape, go:noinline, go:nosplit и go:nowritebarrier. Вам нужно знать, когда и почему они требуются, если вы собираетесь широко использовать некоторые из программ аварийной штриховки.

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

Чтобы понять как, когда и почему они работают, вам нужно понять, как компилятор и среда выполнения выделяют и работают со строками и слайсами, а в некоторых случаях, как распределяется память на оборудовании, и некоторые правила о Go ' Сборщик мусора. В частности, код G C знает о unsafe.Pointer, но не uintptr. Многое из этого довольно сложно: см., Например, https://utcc.utoronto.ca/~cks/space/blog/programming/GoUintptrVsUnsafePointer и ссылку на https://github.com/golang/go/issues/19135, в которой запись nil в Go значение указателя вызывает * 1075 сборщик мусора * жаловался, потому что запись заставила G C проверить ранее сохраненное значение , которое было недопустимым.


1 См. эту статью в Википедии о Intel 432 , чтобы узнать о заметной попытке разработки аппаратного обеспечения для работы скомпилированных языков высокого уровня. В прошлом были и другие, часто с той же судьбой, хотя некоторые проекты IBM были более успешными.

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