sync/atomic
standard package.
T
, we can call unsafe.Alignof(t)
to get its general alignment guarantee, where t
is a non-field value of type T
, and call unsafe.Alignof(x.t)
to get its field alignment guarantee, where x
is a struct value and t
is a field value of type T
.
unsafe
standard code packages are always evaluated at compile time.
t
of type T
, we can call reflect.TypeOf(t).Align()
to get the general alignment guarantee of type T
, and call reflect.TypeOf(t).FieldAlign()
to get the field alignment guarantee of type T
.
x
of any type: unsafe.Alignof(x)
is at least 1
.x
of struct type: unsafe.Alignof(x)
is the largest of all the values unsafe.Alignof(x.f)
for each field f
of x
, but at least 1
.x
of array type: unsafe.Alignof(x)
is the same as the alignment of a variable of the array's element type.
type alignment guarantee
------ ------
bool, uint8, int8 1
uint16, int16 2
uint32, int32 4
float32, complex64 4
arrays depend on element types
structs depend on field types
other types size of a native word
4
or 8
, depends on different build target architectures. This is also true for gccgo.
sync/atomic
package. Please read the following two sections for details.
type size in bytes
------ ------
uint8, int8 1
uint16, int16 2
uint32, int32, float32 4
uint64, int64 8
float64, complex64 8
complex128 16
uint, int implementation-specific,
generally 4 on 32-bit
architectures, and 8 on
64-bit architectures.
uintptr implementation-specific,
large enough to store
the uninterpreted bits
of a pointer value.
int8
are both one byte.
int16
are both two bytes.
int64
is 8 bytes, the alignment guarantee of type int64
is 4 bytes on 32-bit architectures and 8 bytes on 64-bit architectures.
T1
and T2
are their respective largest field alignment guarantees, a.k.a., the alignment guarantee of the int64
field. So their alignment guarantees are both 4 bytes on 32-bit architectures and 8 bytes on 64-bit architectures.
T1
and T2
must be multiples of their respective alignment guarantees, a.k.a., 4N bytes on 32-bit architectures and 8N bytes on 64-bit architectures.
type T1 struct {
a int8
// On 64-bit architectures, to make field b
// 8-byte aligned, 7 bytes need to be padded
// here. On 32-bit architectures, to make
// field b 4-byte aligned, 3 bytes need to be
// padded here.
b int64
c int16
// To make the size of type T1 be a multiple
// of the alignment guarantee of T1, on 64-bit
// architectures, 6 bytes need to be padded
// here, and on 32-bit architectures, 2 bytes
// need to be padded here.
}
// The size of T1 is 24 (= 1 + 7 + 8 + 2 + 6)
// bytes on 64-bit architectures and is 16
// (= 1 + 3 + 8 + 2 + 2) on 32-bit architectures.
type T2 struct {
a int8
// To make field c 2-byte aligned, one byte
// needs to be padded here on both 64-bit
// and 32-bit architectures.
c int16
// On 64-bit architectures, to make field b
// 8-byte aligned, 4 bytes need to be padded
// here. On 32-bit architectures, field b is
// already 4-byte aligned, so no bytes need
// to be padded here.
b int64
}
// The size of T2 is 16 (= 1 + 1 + 2 + 4 + 8)
// bytes on 64-bit architectures, and is 12
// (= 1 + 1 + 2 + 8) on 32-bit architectures.
T1
and T2
have the same field set, their sizes are different.
int64
or uint64
.
sync/atomic
documentation, it mentions:
make
function, or the value referenced by a value returned by the built-in new
function. If a slice value derives from an allocated array and the first element of the slice is the first element of the array, then the slice value can also be viewed as an allocated value.
type (
T1 struct {
v uint64
}
T2 struct {
_ int16
x T1
y *T1
}
T3 struct {
_ int16
x [6]int64
y *[6]int64
}
)
var a int64 // a is safe
var b T1 // b.v is safe
var c [6]int64 // c[0] is safe
var d T2 // d.x.v is unsafe
var e T3 // e.x[0] is unsafe
func f() {
var f int64 // f is safe
var g = []int64{5: 0} // g[0] is safe
var h = e.x[:] // h[0] is unsafe
// Here, d.y.v and e.y[0] are both safe,
// for *d.y and *e.y are both allocated.
d.y = new(T1)
e.y = &[6]int64{}
_, _, _ = f, g, h
}
// In fact, all elements in c, g and e.y.v are
// safe to be accessed atomically, though Go
// official documentation never makes the guarantees.
[15]byte
to determine the address for the 64-bit word at run time. For example,
package mylib
import (
"unsafe"
"sync/atomic"
)
type Counter struct {
x [15]byte // instead of "x uint64"
}
func (c *Counter) xAddr() *uint64 {
// The return must be 8-byte aligned.
return (*uint64)(unsafe.Pointer(
(uintptr(unsafe.Pointer(&c.x)) + 7)/8*8))
}
func (c *Counter) Add(delta uint64) {
p := c.xAddr()
atomic.AddUint64(p, delta)
}
func (c *Counter) Value() uint64 {
return atomic.LoadUint64(c.xAddr())
}
Counter
type can be embedded in other user types freely and safely, even on 32-bit architectures. The drawback of this solution is there are seven bytes being wasted for every value of Counter
type and it uses unsafe pointers.
sync/atomic
standard package. The types include atomic.Int64
and atomic.Uint64
, which values are guaranteed to be 8-byte aligned, even on 32-bit architectures. We may make use of this fact to make some 64-bit words always 8-byte aligned on 32-bit architectures. For example, in the following code, the x
field of any value of the type T
is always 8-byte aligned, in any situations, either on 64-bit or 32-bit architectures.
type T struct {
_ [0]atomic.Int64
x int64
}
The Go 101 project is hosted on Github. Welcome to improve Go 101 articles by submitting corrections for all kinds of mistakes, such as typos, grammar errors, wording inaccuracies, description flaws, code bugs and broken links.
If you would like to learn some Go details and facts every serveral days, please follow Go 101's official Twitter account @zigo_101.
reflect
standard package.sync
standard package.sync/atomic
standard package.