为了账号安全,请及时绑定邮箱和手机立即绑定

有没有办法从另一个包访问结构的私有字段?

有没有办法从另一个包访问结构的私有字段?

Go
30秒到达战场 2021-06-08 17:19:32
我在一个具有私有字段的包中有一个结构:package footype Foo struct {    x int    y *Foo}另一个包(例如,白盒测试包)需要访问它们:package barimport "../foo"func change_foo(f *Foo) {    f.y = nil}有没有办法声明bar为某种“朋友”包或任何其他能够访问foo.Foo的私有成员的方式bar,但仍将它们对所有其他包保持私有(也许在 中unsafe)?
查看完整描述

3 回答

?
喵喔喔

TA贡献1735条经验 获得超5个赞

有是一种方法来读取使用反映不导出成员


func read_foo(f *Foo) {

    v := reflect.ValueOf(*f)

    y := v.FieldByName("y")

    fmt.Println(y.Interface())

}

但是,尝试使用 y.Set 或以其他方式使用反射设置字段将导致代码恐慌,因为您试图在包外设置未导出的字段。


简而言之:未导出的字段应该出于某种原因不导出,如果您需要更改它们,请将需要更改的内容放在同一个包中,或者公开/导出一些安全的方式来更改它。


也就是说,为了完全回答这个问题,你可以这样做


func change_foo(f *Foo) {

    // Since structs are organized in memory order, we can advance the pointer

    // by field size until we're at the desired member. For y, we advance by 8

    // since it's the size of an int on a 64-bit machine and the int "x" is first

    // in the representation of Foo.

    //

    // If you wanted to alter x, you wouldn't advance the pointer at all, and simply

    // would need to convert ptrTof to the type (*int)

    ptrTof := unsafe.Pointer(f)

    ptrTof = unsafe.Pointer(uintptr(ptrTof) + uintptr(8)) // Or 4, if this is 32-bit


    ptrToy := (**Foo)(ptrTof)

    *ptrToy = nil // or *ptrToy = &Foo{} or whatever you want


}

这是一个非常非常糟糕的主意。它不是可移植的,如果 int 的大小发生变化,它将失败,如果您重新排列 Foo 中字段的顺序,更改它们的类型或大小,或者在预先存在的字段之前添加新字段,此函数将愉快地改变随机乱码数据的新表示,而无需告诉您。我也认为它可能会破坏这个块的垃圾收集。


请,如果您需要从包外部更改字段,请编写功能以从包内更改它或将其导出。


Edit2:既然你提到了白盒测试,请注意,如果你在你的目录中命名一个文件,<whatever>_test.go除非你使用它go test,否则它不会编译,所以如果你想做白盒测试,在顶部声明package <yourpackage>它会让你访问未导出的字段,如果你想进行黑盒测试,那么你可以使用package <yourpackage>_test.


但是,如果您需要同时对两个包进行白盒测试,我认为您可能会卡住,可能需要重新考虑您的设计。


查看完整回答
反对 回复 2021-06-21
?
富国沪深

TA贡献1790条经验 获得超9个赞

我假设您正在测试的是更改该包对象状态的包功能,但是您想验证更改后的内部结构以确认新状态是正确的。


可能有帮助的是私有字段的编写Get和Set函数,因此它们可以在包范围之外访问。


package foo


type Foo struct {

    x int

    y *Foo

}


func (f *Foo) GetY() *Foo {

    return f.y

}


func (f *Foo) SetY(newY *Foo) {

    f.y = newY

}

请注意,这些Get和的想法Set是限制对字段的读和/或写访问,而直接导出它们总是自动赋予它们读+写访问权限。一个细微的区别,但如果真正的目标是只读取私有字段而不是对它们进行操作(包内部会以自己的方式进行操作),则值得考虑


最后,如果您不习惯为包中的所有私有字段添加这些类型的包装器,那么您可以将它们写入该包内的一个新文件中,并使用构建标记在常规构建中忽略它,并将其包含在内在您的测试构建中(无论您在何处/以何种方式触发测试)。


// +build whitebox


// Get() and Set() function

go test --tags=whitebox

常规构建会忽略与它们一起构建的测试文件,因此这些不会出现在您的最终二进制文件中。如果这个包在完全不同的生态系统的其他地方使用,那么这个文件将不会因为构建标签约束而被构建。


查看完整回答
反对 回复 2021-06-21
?
慕慕森

TA贡献1856条经验 获得超17个赞

我刚开始使用 C++ -> Go 移植,我遇到了一对彼此是朋友的类。我很确定如果他们是同一个包裹的一部分,他们默认是朋友,有效。

标识符的大写首字母绑定在包内。因此,只要它们位于同一目录中,它们就可以位于不同的文件中,并且能够查看彼此的未导出字段。

使用reflect,即使它是Go stdlib,也是你应该始终仔细考虑的事情。它增加了很多运行时开销。如果您想成为朋友的两种结构类型,解决方案基本上是复制和粘贴,它们必须在同一个文件夹中。否则,您必须导出它们。(就个人而言,我认为关于导出敏感数据的“风险”的呼声过于夸大了,尽管如果您正在编写一个没有可执行文件的单独库,也许对此有一些意义,因为库的用户不会看到这些GoDoc 中的字段,因此认为它们不能依赖于它们的存在)。


查看完整回答
反对 回复 2021-06-21
  • 3 回答
  • 0 关注
  • 204 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信