Sự khác biệt giữa * (* uintptr) và ** (** uintptr)


8

Trong Go's runtime/proc.go, có một đoạn mã hiển thị bên dưới:

// funcPC trả về PC nhập của hàm f.
// Giả sử f là giá trị func. Nếu không thì hành vi là không xác định.
// CẨN THẬN: Trong các chương trình có plugin, funcPC có thể trả về các giá trị khác nhau
// cho cùng một hàm (vì thực tế có nhiều bản sao của
// cùng một hàm trong không gian địa chỉ). Để an toàn, không sử dụng
// kết quả của hàm này trong bất kỳ biểu thức == nào. Chỉ an toàn khi
sử dụng kết quả làm địa chỉ để bắt đầu thực thi mã.

//go:nosplit
func funcPC(f interface{}) uintptr {
    return **(**uintptr)(add(unsafe.Pointer(&f), sys.PtrSize))
}

Điều tôi không hiểu là tại sao không sử dụng * (* uintptr) thay vì ** (** uintptr)?

Vì vậy, tôi viết một chương trình thử nghiệm dưới đây để tìm ra.

package main

import (
    "fmt"
    "unsafe"
)


func main(){
    fmt.Println()

    p := funcPC(test)

    fmt.Println(p)

    p1 := funcPC1(test)

    fmt.Println(p1)

    p2 := funcPC(test)

    fmt.Println(p2)
}

func test(){
    fmt.Println("hello")
}

func funcPC(f func()) uintptr {
    return **(**uintptr)(unsafe.Pointer(&f))
}

func funcPC1(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(&f))
}

nhập mô tả hình ảnh ở đây

Kết quả mà p không bằng p1 làm tôi bối rối. Tại sao giá trị của p không bằng giá trị của p1 trong khi loại của chúng giống nhau?


1
Bởi vì nó là một con trỏ đến một con trỏ?
zerkms

Không biết Go, nhưng tôi tự hỏi giá trị của funcPC(p)nó sẽ là gì. Dù sao thì việc có một con trỏ đến một con trỏ là gì.
LukStorms

@LukStorms: the, erm, point, của một con trỏ tới một con trỏ, là để trỏ đến con trỏ. Nếu pptrỏ đến p, viết để *ppviết pvà đọc từ *ppđọc từ p. Nếu ptrong phạm vi, điều đó tất nhiên là hơi ngớ ngẩn vì bạn chỉ có thể đọc từ, hoặc viết ptrực tiếp. Nhưng nếu pkhông trong phạm vi, hoặc những gì nếu ppđiểm để một trong hai p hoặc q (tùy thuộc vào logic trước đó), và bạn muốn sử dụng hoặc cập nhật bất cứ con trỏ ppđiểm đến?

@torek Oh ok, vậy có lẽ không phải là vô nghĩa.
LukStorms

Câu trả lời:


8

Giới thiệu

Một giá trị hàm trong Go biểu thị mã của chức năng. Từ xa, nó là một con trỏ tới mã của hàm. Nó hoạt động như một con trỏ.

Nhìn gần hơn, nó là một cấu trúc giống như thế này (lấy từ runtime/runtime2.go):

type funcval struct {
    fn uintptr
    // variable-size, fn-specific data here
}

Vì vậy, một giá trị hàm giữ một con trỏ tới mã của hàm là trường đầu tiên mà chúng ta có thể tham gia để lấy mã của hàm.

Giải thích ví dụ của bạn

Để lấy địa chỉ của hàm (mã của), bạn có thể sử dụng phản xạ:

fmt.Println("test() address:", reflect.ValueOf(test).Pointer())

Để xác minh chúng tôi có được địa chỉ đúng, chúng tôi có thể sử dụng runtime.FuncForPC().

Điều này cho giá trị tương tự như funcPC()chức năng của bạn . Xem ví dụ này:

fmt.Println("reflection test() address:", reflect.ValueOf(test).Pointer())
fmt.Println("funcPC(test):", funcPC(test))
fmt.Println("funcPC1(test):", funcPC1(test))

fmt.Println("func name for reflect ptr:",
    runtime.FuncForPC(reflect.ValueOf(test).Pointer()).Name())

Nó xuất ra (thử trên Sân chơi Go ):

reflection test() address: 919136
funcPC(test): 919136
funcPC1(test): 1357256
func name for reflect ptr: main.test

Tại sao? Bởi vì bản thân một giá trị hàm là một con trỏ (nó chỉ có một loại khác với một con trỏ, nhưng giá trị mà nó lưu trữ là một con trỏ) cần được hủy đăng ký để lấy địa chỉ mã .

Vì vậy, những gì bạn sẽ cần để có được điều này uintptr(địa chỉ mã) bên trong funcPC()sẽ chỉ đơn giản là:

func funcPC(f func()) uintptr {
    return *(*uintptr)(f) // Compiler error!
}

Tất nhiên, nó không biên dịch, quy tắc chuyển đổi không cho phép chuyển đổi giá trị hàm thành *uintptr.

Một nỗ lực khác có thể là chuyển đổi nó trước unsafe.Pointer, sau đó thành *uintptr:

func funcPC(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(f)) // Compiler error!
}

Một lần nữa: quy tắc chuyển đổi không cho phép chuyển đổi giá trị hàm thành unsafe.Pointer. Bất kỳ loại con trỏ và uintptrgiá trị có thể được chuyển đổi thành unsafe.Pointervà ngược lại, nhưng không phải là giá trị hàm.

Đó là lý do tại sao chúng ta phải có một giá trị con trỏ để bắt đầu. Và giá trị con trỏ nào chúng ta có thể có? Có, địa chỉ của f: &f. Nhưng đây sẽ không phải là giá trị hàm, đây là địa chỉ của ftham số (biến cục bộ). Vì vậy, về mặt &fsơ đồ không phải là (chỉ) một con trỏ, nó là một con trỏ tới con trỏ (cả hai cần phải được hủy đăng ký). Chúng ta vẫn có thể chuyển đổi nó thành unsafe.Pointer(vì bất kỳ giá trị con trỏ nào cũng đủ điều kiện cho điều đó), nhưng đó không phải là giá trị hàm (như một con trỏ), mà là một con trỏ tới nó.

Và chúng tôi cần địa chỉ mã từ giá trị hàm, vì vậy chúng tôi phải sử dụng **uintptrđể chuyển đổi unsafe.Pointergiá trị và chúng tôi phải sử dụng 2 hội nghị để lấy địa chỉ (và không chỉ con trỏ trong f).

Đây chính xác là lý do tại sao funcPC1()đưa ra một kết quả khác, bất ngờ, không chính xác:

func funcPC1(f func()) uintptr {
    return *(*uintptr)(unsafe.Pointer(&f))
}

Nó trả về con trỏ f, không phải địa chỉ mã thực tế.


1
Tôi không thể tìm thấy bất kỳ thông tin nào conversion rulesvề việc không cho phép chuyển đổi function valuethành uintptr.
Polar9527

3
@ Polar9527 Chính xác vì không có. Những gì được phép được liệt kê rõ ràng trong thông số kỹ thuật và những gì không được phép thì không được phép. Tìm danh sách bắt đầu bằng: "Giá trị không cố định xcó thể được chuyển đổi để nhập Tvào bất kỳ trường hợp nào trong số này ..."
icza

0

Nó trả về một giá trị khác vì ** (** uintptr) không giống với * (* uintptr). Trước đây là một tình cảm đôi, sau là một sự gián tiếp đơn giản.

Trong trường hợp trước, giá trị là một con trỏ tới một con trỏ tới một con trỏ tới một uint.


2
Bạn có quan tâm để giải thích sự khác biệt giữa ** (** uintptr) và chỉ (uintptr) không? Tại sao cú pháp ** (** uintptr) này được sử dụng ở đây?
minitauros
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.