I was just playing around with Go interfaces and structs that suddenly found something weird to me. This is the case:
https://play.golang.org/p/FgvRFV9Lij9
package main
import (
"fmt"
)
func main() {
scopedInt := 100
fmt.Printf("%p\n", &scopedInt)
globalInt = 100
fmt.Printf("%p\n", &globalInt)
}
var globalInt int
output:
0xc0000ba010
0x57b2a8
The value of the addresses doesn't matter. The point is that Why is the number of digits of first address more than second?
I think I have missed a point about the concept of global variables in Go.
Global variables are stored in the data section. Unlike the stack, the data region does not grow or shrink — storage space for globals persists for the entire run of the program.
An address is a non-negative integer. Each time a program is run the variables may or may not be located in same memory locations. Each time you run the program above may or may not result in the same output.
The global Keyword Normally, when you create a variable inside a function, that variable is local, and can only be used inside that function. To create a global variable inside a function, you can use the global keyword.
In C, global variables are stored with the program code. I.e. the space to hold them is part of the object file (either in the data or bss section), instead of being allocated during execution (to either the stack or heap).
Variables that are created outside of a function (as in all of the examples above) are known as global variables. Global variables can be used by everyone, both inside of functions and outside.
By default, global variables are of global scope. Which means we can access a global variable everywhere in same as well as other C programs (using extern ). First let us create a C program that contains only global variables, save the below program with name global.c.
Properties of a global variable Global variables are allocated within data segment of program instead of C stack. Memory for global variable is allocated once and persists throughout the program. They are accessible to all function of the same and other programs (using extern).
The debugger interprets the name of a global variable as a virtual address. Any command that accepts an address as a parameter also accepts the name of a variable. Therefore, you can use all of the commands that are described in Accessing Memory by Virtual Address to read or write global variables. In addition, you can use the ?
The "length" of the addresses differs by many digits because those variables are allocated on different areas of the memory, which have different offsets (starting locations).
scopedInt
will likely be allocated on the heap because it "escapes" from main()
(its address is passed to fmt.Printf()
), while globalInt
is a package level variable and as such will be allocated in one of the fixed sized segments, the data segment.
The "length" of addresses doesn't matter as long as they point to a valid memory area. Go has automatic memory management, so unless you touch package unsafe
, you don't have to worry about addresses and pointers being valid or not.
To read more about memory management, see Doug Richardson: Go Memory Management. Quoting from it:
What Goes Where?
The Go Programming Language Specification does not define where items will be allocated. For example, a variable defined as
var x int
could be allocated on the stack or the heap and still follow the language spec. Likewise, the integer pointed to byp
inp := new(int)
could be allocated on the stack or the heap.However, certain requirements will exclude some choices of memory in certain conditions. For instance:
- The size of the data segment cannot change at run time, and therefore cannot be used for data structures that change size.
- The lifetime of items in the stack are ordered by their position on the stack. If the top of the stack is address X then everything above X will be deallocated while everything below X will remain allocated. Memory allocated by a function can escape that function if referenced by an item outside the scope of the function and therefore cannot be allocated on the stack (because it’s still being referenced), and neither can it be allocated in the data segment (because the data segment cannot grow at runtime), thus it must be allocated on the heap – although inlining can remove some of these heap allocations.
Global uninitialized symbols like globalInt
are stored in the BSS segment, close to the data segment, in the lower address space of the program.
You can check the addresses of program symbols with the nm
utility:
$ go build main.go
$ go tool nm main | grep globalInt
Outputs:
118e210 B main.globalInt
The first hex in the output is the address of the symbol, which is what will be printed when you run the program. E.g. on my machine:
$ ./main
0xc000124008
0x118e210 <--- same as nm output
The letter B
after the hex stands for bss segment symbol
.
If you explicitly initialize the variable in the source code, like var globalInt int = 600
the output of nm
will show:
114b268 D main.globalInt
where now D
stands for data segment symbol
.
Anyway all these are in the lower address space of the program. The scopedInt
doesn't pass escape analysis and is allocated on the heap, where its address will be higher than the global var.
Consider that all this is implementation-dependent. The specs don't mandate where to allocate objects.
If you compile and run the same program with TinyGo, the output will not look alike:
$ tinygo build -o tinymain main.go
$ ./tinymain
0x1158bf040
0x1061f3ca8
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With