在 Go 中获取结构体的大小

37

我正在研究Go语言,它看起来非常有前途。 我正在尝试弄清楚如何获取go结构体的大小,例如:

type Coord3d struct {
    X, Y, Z int64
}

当然我知道它是24字节,但我想通过编程的方式来获取它。

你有任何想法如何实现这个吗?

6个回答

53
Roger已经展示了如何使用SizeOf方法,这个方法来自于unsafe包。在依赖该函数返回值之前,请确保您已经阅读了以下内容:

该大小不包括x可能引用的任何内存。例如,如果x是一个切片,则Sizeof返回切片描述符的大小,而不是切片引用的内存的大小。

除此之外,我想解释一下如何使用一些简单的规则轻松计算任何结构体的大小,以及如何使用有用的service验证您的直觉。

结构体的大小取决于其类型和结构体中字段的顺序(因为会使用不同的填充)。这意味着具有相同字段的两个结构体可能具有不同的大小。

例如,此结构体将具有大小为32

struct {
    a bool
    b string
    c bool
}

稍作修改后,其大小为24(仅因字段更紧凑的排序而导致25%的差异)

struct {
    a bool
    c bool
    b string
}

enter image description here enter image description here

如您所见,在第二个示例中,我们删除了一个填充并移动了一个字段以利用先前的填充。对齐方式可以是1、2、4或8。填充是用来填充变量以填充对齐方式的空间(基本上是浪费空间)。
知道这个规则并记住:
- bool、int8/uint8 占用 1 个字节 - int16、uint16 占用 2 个字节 - int32、uint32、float32 占用 4 个字节 - int64、uint64、float64、指针 占用 8 个字节 - 字符串占用 16 个字节(2 个 8 字节的对齐方式) - 任何切片占用 24 个字节(3 个 8 字节的对齐方式)。因此,[]bool、[][][]string 是相同的(不要忘记重新阅读我在开头添加的引文) - 长度为 n 的数组占用 n * 它所占用的类型的字节。

掌握填充、对齐和字节大小的知识后,您可以快速找出如何改进您的结构体(但仍然有必要使用该服务验证您的直觉)。


没错。我能看到不同的大小。{bool, int8} 是2,{bool, int16} 是4,{bool, int32} 是8。 - firelyu
2
关于一个包含另一个结构体的结构体 - 比如 sync.Mutex,它的排序是怎样的? - hewiefreeman
6
答案中提供的服务似乎无法访问。 - 425nesp
编译器不会为您优化结构字段的顺序以减少内存利用率?这对我来说似乎是一个很大的疏忽。 - jacob
显然,原因是因为现在人们经常希望通过将相关字段放在一起来优化以避免缓存未命中,并且对内存的关注较少。因此,他们将字段的排序留给程序员。请参见https://github.com/golang/go/issues/10014#issuecomment-91436342。 - jacob

47
import unsafe "unsafe"

/* Structure describing an inotify event.  */
type INotifyInfo struct {
    Wd     int32  // Watch descriptor
    Mask   uint32 // Watch mask
    Cookie uint32 // Cookie to synchronize two events
    Len    uint32 // Length (including NULs) of name
}

func doSomething() {
    var info INotifyInfo
    const infoSize = unsafe.Sizeof(info)
    ...
}

注意:提问者误解了。在 Coord3d 结构体示例中,unsafe.Sizeof 确实返回 24。请参见下面的评论。


2
OP明确表示:“我尝试了unsafe.Sizeof,但它没有起作用。” - Chris Lutz
如果不使用64位整数,则应将其视为错误。在Go库代码中,unsafe.Sizeof的使用出现在许多地方,并且旨在作为这样做的规范方式,特别是在与C库和操作系统API调用进行交互时。无论如何,在我编写使用它的代码中,它肯定可以正常工作。 - RogerV
1
所以我检查了下面显示的代码。原帖作者是错的。unsafe.Sizeof 对于他的示例数据结构确实返回24。(我的Go编译器在2010年1月的第一周更新。)package mainimport ( unsafe "unsafe" fmt "fmt" )type Coord3d struct { X, Y, Z int64 }func main() { var pt Coord3d; const size = unsafe.Sizeof(pt); fmt.Printf("Coord3d 的大小为:%d\n",size) } - RogerV
你说得对,我搞错了,我用二进制的TotalSize做了一些实验,把它和我的Sizeof实验混淆了。 感谢你提供的演示解决方案,我无法得出正确的使用方法,因为我几天前才开始使用Go,现在正在尝试如何做“通常的事情”。 在Go中找到有用的示例并不容易,尽管阅读库源代码非常有教益(例如Python)。 - Jörg Haubrichs

10

binary.TotalSize也是一种选择,但请注意它与unsafe.Sizeof之间的行为略有不同:binary.TotalSize包括切片内容的大小,而unsafe.Sizeof仅返回顶层描述符的大小。以下是如何使用TotalSize的示例。

package main

import (
    "encoding/binary"
    "fmt"
    "reflect"
)

type T struct {
    a uint32
    b int8
}

func main() {
    var t T
    r := reflect.ValueOf(t)
    s := binary.TotalSize(r)

    fmt.Println(s)
}

在Go release.r60.3中,NewValue被重命名为ValueOf。 - Daniel YC Lin
3
binary.Sizeunsafe.Sizeof 完全不同,并且存在于完全不同的原因。这与 OP 所要求的完全不同。 - Dave C
1
函数binary.TotalSize在Go中已不再可用。我认为代码应该是:var t T; s := binary.Size(t); fmt.Println(s) - Komu

1

这可能会有所变化,但据我所知,存在一个与结构对齐相关的编译器错误(bug260.go)。最终结果是打包结构可能不会产生预期的结果。这是针对编译器6g版本5383 release.2010-04-27 release的情况。它可能不会影响您的结果,但这是需要注意的事项。

更新:截至2010-05-04发布,go测试套件中仅剩下上述提到的bug260.go这个错误。

Hotei


我相信你指的是issue/482,该问题已于2010年12月关闭。既然那时还没有Go1.0,这个“答案”是否仍然存在? - Dave C

0
为了避免初始化结构体的开销,使用Coord3d指针会更快:
package main

import (
    "fmt"
    "unsafe"
)

type Coord3d struct {
    X, Y, Z int64
}

func main() {
    var dummy *Coord3d
    fmt.Printf("sizeof(Coord3d) = %d\n", unsafe.Sizeof(*dummy))
}

3
如果您打算依赖取消引用空指针(这仅在此处起作用,因为当前 unsafe.Sizeof 实现的实现细节,我怀疑这是合法代码),那么您可能会想要完全消除任何变量 - Dave C

0
/*
    returns the size of any type of object in bytes
*/

func getRealSizeOf(v interface{}) (int, error) {
    b := new(bytes.Buffer)
    if err := gob.NewEncoder(b).Encode(v); err != nil {
        return 0, err
    }
    return b.Len(), nil
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接