Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Golang: analysis of the underlying structure of common data types

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)06/01 Report--

Although golang is implemented in C and is called the next-generation C language, golang is still very different from C. It defines a rich set of data types and data structures, which are either directly mapped to C data types or implemented in C struct. Understanding the underlying implementation of golang's data types and data structures will help us better understand golang and write better quality code.

Basic type

The source code is: $GOROOT/src/pkg/runtime/runtime.h. Let's first take a look at the basic types:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

seventeen

eighteen

nineteen

twenty

twenty-one

twenty-two

twenty-three

twenty-four

twenty-five

twenty-six

twenty-seven

twenty-eight

twenty-nine

thirty

thirty-one

/ *

* basic types

, /

Typedef signed char int8

Typedef unsigned char uint8

Typedef signed short int16

Typedef unsigned short uint16

Typedef signed int int32

Typedef unsigned int uint32

Typedef signed long long int int64

Typedef unsigned long long int uint64

Typedef float float32

Typedef double float64

# ifdef _ 64BIT

Typedef uint64 uintptr

Typedef int64 intptr

Typedef int64 intgo; / / Go's int

Typedef uint64 uintgo; / / Go's uint

# else

Typedef uint32 uintptr

Typedef int32 intptr

Typedef int32 intgo; / / Go's int

Typedef uint32 uintgo; / / Go's uint

# endif

/ *

* defined types

, /

Typedef uint8 bool

Typedef uint8 byte

Int8, uint8, int16, uint16, int32, uint32, int64, uint64, float32, float64 correspond to the type of C, which is easy to see as long as there is a C base. Uintptr and intptr are unsigned and signed pointer types, and ensure that it is 8 bytes on 64-bit platforms and 4 bytes on 32-bit platforms. Uintptr is mainly used for pointer operations in golang. Intgo and uintgo are not named int and uint because int is a type name in C, and uintgo must be used to correspond to the naming of intgo. Intgo and uintgo correspond to int and uint in golang. From the definition, you can see that int and uint are variable size types, accounting for 8 bytes on 64-bit platforms and 4 bytes on 32-bit platforms. So if there is a clear requirement, you should choose int32, int64 or uint32, uint64. The underlying type of the byte type is uint8. Take a look at the test:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

Package main

Import (

"fmt"

"reflect"

)

Func main () {

Var b byte ='D'

Fmt.Printf ("output:% v\ n", reflect.TypeOf (b) .Kind ())

}

?

one

two

three

four

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Output: uint8

Data types are divided into static types and underlying types. Relative to the variable b in the above code, byte is its static type and uint8 is its underlying type. This is very important, and this concept will be used frequently in the future.

Rune Typ

Rune is an alias for int32 and is used to represent unicode characters. You usually need to use it when dealing with Chinese, but you can also use the range keyword.

String Typ

At the bottom of the string type is a C struct.

?

one

two

three

four

five

Struct String

{

Byte* str

Intgo len

}

The member str is an array of characters and len is the length of an array of characters. Golang strings are immutable, and initialization of variables of type string means initialization of the underlying structure. As for why str uses the byte type instead of the rune type, this is because golang's for loop traverses the string based on bytes and, if necessary, can be converted to rune slices or iterated using range. Let's look at an example:

$GOPATH/src

-basictype_test

-main.go

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

Package main

Import (

"fmt"

"unsafe"

)

Func main () {

Var str string = "hi, Chen Yihui ~"

P: = (* struct {

Str uintptr

Len int

}) (unsafe.Pointer (& str))

Fmt.Printf ("% + v\ n", p)

}

?

one

two

three

four

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Output: & {str:135100456 len:14}

The operation of the built-in function len on the string type is to extract the lenvalue directly from the underlying structure without additional operation, of course, the value of len must be initialized at the same time of initialization.

Slice Typ

The underlying layer of the slice type is also a C struct.

?

one

two

three

four

five

six

Struct Slice

{/ / must not move anything

Byte* array; / / actual data

Uintgo len; / / number of elements

Uintgo cap; / / allocated number of elements

}

Including three members. Array is the underlying array, len is the number of actual storage, and cap is the total capacity. Use the built-in function make to initialize slice, or you can initialize it in an array-like way. When using the make function to initialize slice, the first parameter is slice type, the second parameter is len, and the third parameter is optional, if not passed in, cap equals len. The cap parameter is usually passed in to pre-allocate the size of the slice to avoid frequent memory reallocation.

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

seventeen

Package main

Import (

"fmt"

"unsafe"

)

Func main () {

Var slice [] int32 = make ([] int32, 5,10)

P: = (* struct {

Array uintptr

Len int

Cap int

}) (unsafe.Pointer (& slice))

Fmt.Printf ("output:% + v\ n", p)

}

?

one

two

three

four

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Output: & {array:406958176 len:5 cap:10}

Because slices point to an underlying array, and slices can be generated directly from the array through slice syntax, you need to understand the relationship between slices and arrays, otherwise you may unwittingly write code with bug. For example, there is the following code:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

Package main

Import (

"fmt"

)

Func main () {

Var array = [...] int32 {1, 2, 3, 4, 5}

Var slice = array [2:4]

Fmt.Printf ("before changing slice: array=%+v, slice=%+v\ n", array, slice)

Slice [0] = 234

Fmt.Printf ("after changing slice: array=%+v, slice=%+v\ n", array, slice)

}

?

one

two

three

four

five

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Before changing slice: array= [1 2 3 4 5], slice= [3 4]

After changing slice: array= [1 2 234 4 5], slice= [2 34 4]

You can clearly see that after changing slice, array has also been changed. This is because the slices created by slice through the array point to the array, which means that the underlying array of the slice is the array. So obviously, the change to slice is to change its underlying array. Of course, if you delete or add elements, then the len will also change, and the cap may change.

So how does this slice point to array? The underlying array pointer to slice points to the element in array with index 2 (because the slice is generated through array [2:4]), len records the number of elements, and cap equals len.

The reason why cap may change is that cap represents total capacity, and adding or removing operations do not necessarily change total capacity. Let's move on to another example:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

Package main

Import (

"fmt"

)

Func main () {

Var array = [...] int32 {1, 2, 3, 4, 5}

Var slice = array [2:4]

Slice = append (slice, 6,7,8)

Fmt.Printf ("before changing slice: array=%+v, slice=%+v\ n", array, slice)

Slice [0] = 234

Fmt.Printf ("after changing slice: array=%+v, slice=%+v\ n", array, slice)

}

?

one

two

three

four

five

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Before changing slice: array= [1 2 3 4 5], slice= [3 4 6 7 8]

After changing slice: array= [1 234 5], slice= [234 4 6 7 8]

After the append operation, the changes to slice did not affect array. The reason is that the operation of append causes slice to reassign the underlying array, so the underlying array of slice no longer points to the previously defined array.

But obviously, this rule is the same for slices generated from slices, see the code:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

Package main

Import (

"fmt"

)

Func main () {

Var slice1 = [] int32 {1,2,3,4,5}

Var slice2 = slice1 [2:4]

Fmt.Printf ("before changing slice2: slice1=%+v, slice2=%+v\ n", slice1, slice2)

Slice2 [0] = 234

Fmt.Printf ("after changing slice2: slice1=%+v, slice2=%+v\ n", slice1, slice2)

}

?

one

two

three

four

five

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Before changing slice2: slice1= [1 2 3 4 5], slice2= [3 4]

After changing slice2: slice1= [1 2 234 4 5], slice2= [2 34 4]

Slice1 and slice2 share an underlying array, and modifying the elements of slice2 causes the slice1 to change as well.

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

Package main

Import (

"fmt"

)

Func main () {

Var slice1 = [] int32 {1,2,3,4,5}

Var slice2 = slice1 [2:4]

Fmt.Printf ("before changing slice2: slice1=%+v, slice2=%+v\ n", slice1, slice2)

Slice2 = append (slice2, 6,7,8)

Fmt.Printf ("after changing slice2: slice1=%+v, slice2=%+v\ n", slice1, slice2)

}

?

one

two

three

four

five

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Before changing slice2: slice1= [1 2 3 4 5], slice2= [3 4]

After changing slice2: slice1= [1 2 3 4 5], slice2= [3 4 6 7 8]

The append operation causes slice1 or slice2 to reassign the underlying array, so append operations on slice1 or slice2 do not affect each other.

Interface Typ

The implementation of the interface in golang is more complex, as defined in $GOROOT/src/pkg/runtime/type.h:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

Struct Type

{

Uintptr size

Uint32 hash

Uint8 _ unused

Uint8 align

Uint8 fieldAlign

Uint8 kind

Alg * alg

Void * gc

String * string

UncommonType * x

Type * ptrto

}

Defined in $GOROOT/src/pkg/runtime/runtime.h:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

seventeen

eighteen

nineteen

Struct Iface

{

Itab* tab

Void* data

}

Struct Eface

{

Type* type

Void* data

}

Struct Itab

{

InterfaceType* inter

Type* type

Itab* link

Int32 bad

Int32 unused

Void (* fun []) (void)

}

Interface is actually a structure consisting of two members, one is a pointer to the data, and the other contains the type information of the member. Eface is the data structure used at the bottom of interface {}. Because type information is stored in interface, reflection can be implemented. Reflection is actually finding the metadata of the underlying data structure. The complete implementation is in: $GOROOT/src/pkg/runtime/iface.c.

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

Package main

Import (

"fmt"

"unsafe"

)

Func main () {

Var str interface {} = "Hello World!"

P: = (* struct {

Tab uintptr

Data uintptr

}) (unsafe.Pointer (& str))

Fmt.Printf ("% + v\ n", p)

}

?

one

two

three

four

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Output: & {tab:134966528 data:406847688}

Map Typ

The map implementation of golang is hashtable, and the source code is: $GOROOT/src/pkg/runtime/hashmap.c.

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

Struct Hmap

{

Uintgo count

Uint32 flags

Uint32 hash0

Uint8 B

Uint8 keysize

Uint8 valuesize

Uint16 bucketsize

Byte * buckets

Byte * oldbuckets

Uintptr nevacuate

}

The test code is as follows:

?

one

two

three

four

five

six

seven

eight

nine

ten

eleven

twelve

thirteen

fourteen

fifteen

sixteen

seventeen

eighteen

nineteen

twenty

twenty-one

twenty-two

twenty-three

twenty-four

twenty-five

twenty-six

Package main

Import (

"fmt"

"unsafe"

)

Func main () {

Var m = make (map [string] int32, 10)

M ["hello"] = 123

P: = (* struct {

Count int

Flags uint32

Hash0 uint32

B uint8

Keysize uint8

Valuesize uint8

Bucketsize uint16

Buckets uintptr

Oldbuckets uintptr

Nevacuate uintptr

) (unsafe.Pointer (& m))

Fmt.Printf ("output:% + v\ n", p)

}

?

one

two

three

four

$cd $GOPATH/src/basictype_test

$go build

$. / basictype_test

Output: & {count:407032064 flags:0 hash0:134958144 barr 192 keysize:0 valuesize:64 bucketsize:30063 buckets:540701813 oldbuckets:0 nevacuate:0}

There are still many holes in golang, so we need to study the bottom layer deeply, otherwise it is easy to fall into the pit.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report