darusuna.com

Understanding the Inner Workings of Functions in Golang

Written on

In this article, we won't delve into how to export functions or the differences between global and local functions. Instead, our focus will be on the internal mechanics of functions in Golang.

I have also created a YouTube video that elaborates on this topic from a different angle.

Basics of Go Functions

When we define a function in Go, it becomes accessible across the entire package. One key aspect to note is that you cannot have two variables or functions with the same name. In Java, it's possible to have multiple functions with identical names as long as their input or output types differ. However, this is not the case in Go. Why is that?

func a() {

}

func a(b string) {

}

// redeclaring 'a' in this block leads to an error

What’s happening behind the scenes? Let’s explore further.

When Go compiles code, it generates a symbol table that tracks the names of all variables and functions. If there are two global variables or functions with identical names, a conflict arises since the symbol table can only store one entry for that name within a single package.

Thus, having unique names in the symbol table is essential.

What is the Symbol Table in Go and How Do We Access It?

The symbol table acts like a logbook, keeping track of functions, variables, types, addresses, and types, facilitating smooth compilation and execution.

Refer to this blog for an in-depth look at the symbol table.

How to Generate the Symbol Table in a Go Binary?

You can generate the symbol table by using the following command:

go tool nm ./main &> logs.txt // where ./main is the Go binary

This command will redirect all logs to logs.txt, and the output for functions will resemble the following:

100343920 T main.getURL

10034390 b T main.main

100343f30 T main.main.func1

100343fd0 T main.main.func1.Println.1

100343d80 T main.main.func2

In this output, the first parameter is the address, the second indicates the type, and the third is the name.

Types of Symbols:

  • T: Text (code) segment symbol (typically functions).
  • B: Uninitialized data segment symbol (usually global variables).
  • D: Initialized data segment symbol.
  • R: Read-only data segment symbol.
  • U: Undefined symbol.
  • V: Weak symbol.

Global variables and functions are stored in the data segment of the compiled binary, whereas the actual function code resides in the text segment, which contains the program's executable code. When a function is invoked, the instruction pointer redirects to the function’s code location in the text segment.

Exported vs. Unexported Identifiers

If a variable or function's name begins with an uppercase letter (like GlobalVar or GlobalFunction), it can be accessed from other packages. In contrast, if the name starts with a lowercase letter (like globalVar or globalFunction), it is limited to the package where it was declared.

Let’s create a binary with the following structure and examine the function names in the symbol table.

/CODEDEPLOY

??? /projects

??? /app1

??? app1.go

??? /app2

??? app2.go

??? main.go

Since Go optimizes its builds, the Go compiler may inline small functions, meaning they may not appear as distinct symbols in the binary. To prevent inlining, use the //go:noinline directive above the function definition.

//go:noinline

func Apple() {

fmt.Println("id")

}

Next, we build the binary using the command below to retrieve the symbol table.

go build -gcflags="all=-N -l" -o myapp

The resulting symbol table will appear as follows:

1004530b0 T codeDeploy/projects/app1.Apple

100453150 T codeDeploy/projects/app2.Apple

This allows two exported functions with the same name to coexist since their complete paths serve as unique identifiers in the symbol table.

Local Scope vs Global Scope

In our previous discussion, we covered exported and unexported functions in Go. But what about local functions? A local function is defined within another function, establishing its own scope. Similarly, variables declared inside a function are local to that function.

How Does a Local Function Work?

When you define a function or variable inside another function, a new scope is created. This scope is restricted to the containing function, meaning the inner function or variable cannot be accessed from outside its parent function.

What Happens During Compilation?

During compilation, the Go compiler organizes memory for all local variables and functions within that function’s stack frame.

  • Stack Frame Layout: The compiler allocates specific memory locations or offsets for each variable and local function in the stack frame, ensuring efficient memory management and variable access during execution.
  • Symbol Table Use: Variable names and their locations are temporarily stored in a symbol table during compilation to facilitate accurate code generation. However, this mapping is only necessary at compile time.

Runtime Execution

Once compiled, the program utilizes the memory addresses from the symbol table to directly access variables. The actual names of the variables are no longer needed since the compiled machine code uses memory locations.

Each function call has its own stack frame. Thus, even if two functions contain variables with the same name, they do not conflict as they exist in different stack frames. Each stack frame is independent, ensuring that variables and local functions are confined to their respective scopes.

Sequence of Operations:

  1. Stack Pointer: Before a function call, the stack pointer (SP) indicates the current top of the stack, where new data will be added.
  2. Function Call and Stack Frame Creation: When a function is called, a new stack frame is created that includes space for all local variables, function parameters, and the return address (where to return after the function completes).
  3. Stack Allocation: The stack pointer is adjusted (moved down) to allocate space for the new stack frame, which contains all data specific to the function call. The stack frame includes:
    • Function Parameters: The values passed to the function are stored here.
    • Local Variables: Any variables declared within the function are included.
    • Return Address: The address to return to after the function completes is recorded in the stack frame.

Managing Local Variables

During function execution, local variables are accessed relative to the stack pointer. The compiler generates instructions that utilize the stack pointer to locate these variables within the stack frame.

Let’s clarify this with an example:

func main() {

tempFunc := func(count int) int {

return count + 1

}

tempVal := tempFunc(0)

fmt.Println(tempVal)

}

  • The instructions for tempFunc are stored in the text segment of the binary.
  • The reference (pointer) to tempFunc is stored on the stack since it is a local variable within main.

When tempFunc(0) is invoked: - A new stack frame is created for this function call, which includes:

  • Function Arguments: In this case, count is 0, stored in the stack frame of tempFunc.
  • Return Address: The location in main to return to post-execution of tempFunc is stored on the stack.
  • Local Variables in `tempFunc`: count is a parameter of tempFunc, stored in the stack frame for tempFunc.

Conclusion

This concludes our discussion on how functions are stored, the operation of local variables, and addressing the initial questions posed in the article.

Thank you for reading!

Feel free to check out these related blogs: 1. https://readmedium.com/golang-how-to-use-ticker-and-timer-dde84644bb46 2. https://readmedium.com/mastering-scram-authentication-in-mongodb-a-developers-guide-3da02d66a18c 3. https://readmedium.com/garbage-management-java-vs-go-45241b6304d4

Share the page:

Twitter Facebook Reddit LinkIn

-----------------------

Recent Post:

Navigating Career Uncertainty: Insights from a Developer's Journey

A developer reflects on career planning and the importance of setting achievable goals.

Finding Your Initial Clients in the Translation Sector

Discover effective strategies for securing your first clients in the translation industry, including networking and building an online presence.

Title: The Prevalence of 'Spin' in Psychiatry Journal Abstracts

Over half of psychiatry journals exhibit 'spin' in abstracts, raising concerns about its impact on treatment decisions.

50 Essential Lessons Every Man Should Embrace for Success

Discover 50 crucial lessons that every man should learn to thrive in life, covering wealth, health, and personal growth.

# Why

This article explores the shortcomings of

Navigating the Challenge of Quitting a Job: A Personal Journey

Quitting a job can be daunting. This personal account reveals the struggles and lessons learned in the process of making a life-changing decision.

Embracing Digital Minimalism: 5 Simple Habits to Adopt

Discover five straightforward habits to simplify your digital life and enhance your well-being while reducing stress.

# The Iconic 'Pale Blue Dot': A Reflection on Humanity's Place

Explore the significance of the 'Pale Blue Dot' image and its reminder of humanity's fragility and unity in the vast universe.