Function call information for the Go language

Source: Internet
Author: User
Tags closure definition types of functions
This is a creation in Article, where the information may have evolved or changed.

by chaishushan{at}gmail.com

Note: The first draft of this article was sent to Golang China blog, where the content is partially modified.

The call information of a function is the important run-time information in the program, which is used in many situations (such as debugging or logging).

runtime runtime.Caller runtime.Callers Several functions, such as the Go Language pack, runtime.FuncForPC provide a way to get information about a function caller.

The documentation links for these several functions:

    • http://docs.studygolang.com/pkg/runtime/#Caller
    • http://docs.studygolang.com/pkg/runtime/#Callers
    • http://docs.studygolang.com/pkg/runtime/#FuncForPC

This article mainly describes the use of these functions.

runtime.CallerUsage of

The signature of the function is as follows:

func runtime.Caller(skip int) (pc uintptr, file string, line int, ok bool)

runtime.CallerReturns the goroutine function call information on the current stack. There is pc information such as the current value and the called File and line number. If the information is not available, the ok value returned is false .

The input parameter skip is the number of stack frames to skip, 0 or the caller if it is runtime.Caller .

Note: Because of historical reasons, runtime.Caller and runtime.Callers in the skip meaning is not the same, will be discussed later.

Here is a simple example that prints the stack frame information for a function call:

func main() {    for skip := 0; ; skip++ {        pc, file, line, ok := runtime.Caller(skip)        if !ok {            break        }        fmt.Printf("skip = %v, pc = %v, file = %v, line = %v\n", skip, pc, file, line)    }    // Output:    // skip = 0, pc = 4198453, file = caller.go, line = 10    // skip = 1, pc = 4280066, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 220    // skip = 2, pc = 4289712, file = $(GOROOT)/src/pkg/runtime/proc.c, line = 1394}

skip = 0this is the function of the current file ("Caller.go") main.main , along with the corresponding line number. The extraneous code is omitted here, so the line number of the output and the location of the page display are somewhat different.

The additional skip = 1 and skip = 2 also correspond to 2 function calls respectively. By looking runtime/proc.c up the code of the file, we can know that the corresponding function is the runtime.main and runtime.goexit .

After finishing, you can know that the boot sequence of Go's normal program is as follows:

    1. runtime.goexitFor the real function entry (not main.main )
    2. Then runtime.goexit call the runtime.main function
    3. Final runtime.main call to user-written main.main functions

runtime.CallersUsage of

The signature of the function is as follows:

func runtime.Callers(skip int, pc []uintptr) int

runtime.CallersFunctions and runtime.Caller functions have a similar name (one suffix s ), but the function's parameter/return value differs greatly from the parameter's meaning.

runtime.CallersFill in the slices with the program counter on the go stacks that invokes it pc . The parameter skip is the number of stack frames to skip before starting to record in the PC, and if 0 represents runtime.Callers its own stack frame, 1 represents the caller's stack frame. The function returns a write to pc the The number of items in the slice (limited by the capacity of the tile).

Here is runtime.Callers An example for outputting information for each stack frame pc :

func main() {    pc := make([]uintptr, 1024)    for skip := 0; ; skip++ {        n := runtime.Callers(skip, pc)        if n <= 0 {            break        }        fmt.Printf("skip = %v, pc = %v\n", skip, pc[:n])    }    // Output:    // skip = 0, pc = [4304486 4198562 4280114 4289760]    // skip = 1, pc = [4198562 4280114 4289760]    // skip = 2, pc = [4280114 4289760]    // skip = 3, pc = [4289760]}

The output new pc length and skip size have inverse correlation. skip = 0 runtime.Callers information for itself.

This example outputs a stack frame more than the previous example, because runtime.Callers there is more information on a stack frame (the previous example is no runtime.Caller information ( Note: no s suffix )).

So runtime.Callers runtime.Caller What are the correlations and differences?

runtime.Callersand runtime.Caller the similarities and differences

Because the previous 2 examples are different programs, the output pc values are not reference. Now let's look at how the output in the same example looks:

  func Main () {for Skip: = 0;; skip++ {pc, file, line, OK: = runtime. Caller (Skip) if!ok {break} fmt. Printf ("skip =%v, PC =%v, file =%v, line =%v\n", skip, PC, file, line)}//Output://skip = 0, PC = 4198456  , file = caller.go, line = ten//skip = 1, PC = 4280962, File = $ (goroot)/src/pkg/runtime/proc.c, line =//Skip = 2, PC = 4290608, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394 PC: = Make ([]uintptr, 1024x768) for Skip: = 0; ; skip++ {n: = runtime. Callers (skip, PC) if n <= 0 {break} fmt.    Printf ("skip =%v, PC =%v\n", skip, Pc[:n])}//Output://skip = 0, PC = [4305334 4198635 4280962 4290608] Skip = 1, PC = [4198635 4280962 4290608]//skip = 2, PC = [4280962 4290608]//skip = 3, PC = [4290608]}  /pre>

For example, the output can be found, 4280962 and 4290608 two pc values are the same. They correspond runtime.main and runtime.goexit function respectively.

runtime.CallerThe output is not the same as the 4198456 runtime.Callers output 4198635 . This is because the call locations of the two functions are not the same, resulting in a value that is pc not exactly the same.

The last is runtime.Callers a multiple output 4305334 value, corresponding to runtime.Callers the internal call location.

Because the Go language (Go1.2) uses a segmented stack, the pc size relationship between the different is not obvious.

runtime.FuncForPCThe use of

The signature of the function is as follows:

func runtime.FuncForPC(pc uintptr) *runtime.Funcfunc (f *runtime.Func) FileLine(pc uintptr) (file string, line int)func (f *runtime.Func) Entry() uintptrfunc (f *runtime.Func) Name() string

Returns runtime.FuncForPC pc the function that contains the given address, if it is not valid pc nil .

runtime.Func.FileLineReturns the pc corresponding source file name and line number. The description of the installation document, if pc not within the function frame range, the result is indeterminate.

runtime.Func.EntryThe address of the corresponding function. runtime.Func.Namereturns the name of the function.

Here's runtime.FuncForPC an example:

Func Main () {for Skip: = 0;; skip++ {PC, _, _, OK: Runtime. Caller (Skip) if!ok {break} P: = Runtime. FUNCFORPC (PC) file, line: = P.fileline (0) fmt. Printf ("skip =%v, PC =%v\n", skip, PC) FMT. Printf ("file =%v, line =%d\n", file, line) FMT. Printf ("entry =%v\n", P.entry ()) fmt.    Printf ("name =%v\n", P.name ())}//Output://skip = 0, PC = 4198456//File = Caller.go, line = 8// Entry = 4198400//name = Main.main//skip = 1, PC = 4282882//File = $ (goroot)/SRC/PKG/RUNTIME/PROC.C, line = 179//Entry = 4282576//name = Runtime.main//skip = 2, PC = 4292528//File = $ (goroot)/src/p  KG/RUNTIME/PROC.C, line = 1394//Entry = 4292528//name = Runtime.goexit PC: = Make ([]uintptr, 1024x768) for Skip: = 0;; skip++ {n: = runtime. Callers (skip, PC) if n <= 0 {break} fmt. Printf ("skip =%v, PC =%v\ n ", skip, Pc[:n]) for J: = 0; J < N; J + + {p: = runtime. FUNCFORPC (Pc[j]) file, line: = P.fileline (0) fmt. Printf ("skip =%v, PC =%v\n", skip, Pc[j]) fmt. Printf ("file =%v, line =%d\n", file, line) FMT. Printf ("entry =%v\n", P.entry ()) fmt.  Printf ("name =%v\n", P.name ())} break}//Output://skip = 0, PC = [4307254 4198586 4282882 4292528]//skip = 0, PC = 4307254//File = $ (goroot)/src/pkg/runtime/runtime.c, line = 315//Entry = 4307168//name = runtime. Callers//skip = 0, PC = 4198586//File = Caller.go, line = 8//Entry = 4198400//name = Mai N.main//skip = 0, PC = 4282882//File = $ (goroot)/src/pkg/runtime/proc.c, line = 179//Entry = 4282  576//Name = Runtime.main//skip = 0, PC = 4292528//File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394//Entry = 4292528//name = Runtime.goexit} 

Depending on the test, if it is not valid pc (for example 0 ), the runtime.Func.FileLine start line number of the current function is generally output. However, in practice, it is common runtime.Caller to get the file name and line number information, runtime.Func.FileLine seldom used (how to get pc parameters independently?).

Custom CallerName functions

Based on the previous functions, we can easily customize a CallerName function. The function CallerName returns user-friendly information such as the caller's letter/file name/line number.

The function is implemented as follows:

func CallerName(skip int) (name, file string, line int, ok bool) {    var pc uintptr    if pc, file, line, ok = runtime.Caller(skip + 1); !ok {        return    }    name = runtime.FuncForPC(pc).Name()    return}

Where the call is executed runtime.Caller , the parameter is skip + 1 used to counteract CallerName the call of the function itself.

Here is an CallerName example of the output based on:

func main() {    for skip := 0; ; skip++ {        name, file, line, ok := CallerName(skip)        if !ok {            break        }        fmt.Printf("skip = %v\n", skip)        fmt.Printf("  file = %v, line = %d\n", file, line)        fmt.Printf("  name = %v\n", name)    }    // Output:    // skip = 0    //   file = caller.go, line = 19    //   name = main.main    // skip = 1    //   file = $(GOROOT)/src/pkg/runtime/proc.c, line = 220    //   name = runtime.main    // skip = 2    //   file = $(GOROOT)/src/pkg/runtime/proc.c, line = 1394    //   name = runtime.goexit}

This makes it easy to output the information of the function caller.

Types of functions in the Go language

In the go language, in addition to the normal function calls defined by the language, there are different function invocation types such as the/init function/global variable initialization of the closure function.

To facilitate testing of different types of function calls, we wrap a PrintCallerName function. This function is used to output the caller's information.

func PrintCallerName(skip int, comment string) bool {    name, file, line, ok := CallerName(skip + 1)    if !ok {        return false    }    fmt.Printf("skip = %v, comment = %s\n", skip, comment)    fmt.Printf("  file = %v, line = %d\n", file, line)    fmt.Printf("  name = %v\n", name)    return true}

Then write the following test code (function closure call/global variable initialization/init function, etc.):

var a = PrintCallerName(0, "main.a")var b = PrintCallerName(0, "main.b")func init() {    a = PrintCallerName(0, "main.init.a")}func init() {    b = PrintCallerName(0, "main.init.b")    func() {        b = PrintCallerName(0, "main.init.b[1]")    }()}func main() {    a = PrintCallerName(0, "main.main.a")    b = PrintCallerName(0, "main.main.b")    func() {        b = PrintCallerName(0, "main.main.b[1]")        func() {            b = PrintCallerName(0, "main.main.b[1][1]")        }()        b = PrintCallerName(0, "main.main.b[2]")    }()}

The output results are as follows:

// Output:// skip = 0, comment = main.a//   file = caller.go, line = 8//   name = main.init// skip = 0, comment = main.b//   file = caller.go, line = 9//   name = main.init// skip = 0, comment = main.init.a//   file = caller.go, line = 12//   name = main.init·1// skip = 0, comment = main.init.b//   file = caller.go, line = 16//   name = main.init·2// skip = 0, comment = main.init.b[1]//   file = caller.go, line = 18//   name = main.func·001// skip = 0, comment = main.main.a//   file = caller.go, line = 23//   name = main.main// skip = 0, comment = main.main.b//   file = caller.go, line = 24//   name = main.main// skip = 0, comment = main.main.b[1]//   file = caller.go, line = 26//   name = main.func·003// skip = 0, comment = main.main.b[1][1]//   file = caller.go, line = 28//   name = main.func·002// skip = 0, comment = main.main.b[2]//   file = caller.go, line = 30//   name = main.func·003

Observing the output results, we can find the following laws:

    • The initialization of a global variable is called by a main.init function
    • The custom init function has a numeric suffix that is numbered according to the order in which it appears. For example, main.init·1 and main.init·2 so on.
    • The closure function is main.func·001 named in the format and the position of the end of the installation closure definition is numbered sequentially.

For example, the following global variables initialize the caller as a main.init function:

var a = PrintCallerName(0, "main.a")var b = PrintCallerName(0, "main.b")

The following two init functions correspond respectively according to the order main.init·1 of occurrence and main.init·2 :

func init() { // main.init·1    //}func init() { // main.init·2    //}

The following three closures are based on the defined ending order 001 , respectively/ 002 / 003 :

func init() {    func(){        //    }() // main.func·001}func main() {    func() {        func(){            //        }() // main.func·002    }() // main.func·003}

Because of the existence of these special function invocation methods, we need to further refine the CallerName function.

Improved CallerName functions

Two special types of calls are init class function calls and closure function calls.

The improved CallerName function init uniformly handles the caller of the class function as a init function. Call the closure function This process is the name of the function of the caller.

// caller types:// runtime.goexit// runtime.main// main.init// main.init·1// main.main// main.func·001// code.google.com/p/gettext-go/gettext.TestCallerName// ...func CallerName(skip int) (name, file string, line int, ok bool) {    var (        reInit    = regexp.MustCompile(`init·\d+$`) // main.init·1        reClosure = regexp.MustCompile(`func·\d+$`) // main.func·001    )    for {        var pc uintptr        if pc, file, line, ok = runtime.Caller(skip + 1); !ok {            return        }        name = runtime.FuncForPC(pc).Name()        if reInit.MatchString(name) {            name = reInit.ReplaceAllString(name, "init")            return        }        if reClosure.MatchString(name) {            skip++            continue        }        return    }    return}

Treatment of the idea:

    1. If it is a init type of function call (matching regular expression "init·\d+$" ), return directly as a init function fan
    2. If it is a func closure type (match regular expression "func·\d+$" ), skip the current stack frame and continue with recursion
    3. Returns the normal function call type

CallerNameThe shortcomings of the function

The following code is available:

func init() {    var _ = myInit("1")}func main() {    var _ = myInit("2")}var myInit = func(name string) {    b = PrintCallerName(0, name + ":main.myInit.b")}

myInitis a global variable and is assigned a closure function. The init main myInit result of this closure function output is then called in the and function separately
Vary depending on the calling environment.

Intuitively, it is myInit best to output function names when the closure functions are executed main.myInit . But it's main.myInit just a variable bound to a closure function, and the real name of the closure is main.func·??? . This name is not available at run time main.myInit .

Therefore, the functions used internally in Gettext-go are callerName main.func·??? treated uniformly as main.func the context of the gettext.Gettext translation function.

The Gettext-go callerName function is implemented here: Caller.go. test file here: Caller_test.go.

Different Go program START process

Based on the function caller information, it is easy to verify the program initiation process in various environments.

We need to create a separate caller directory with three test codes.

caller/main.goMain program:

package mainimport (    "fmt"    "regexp"    "runtime")func main() {    _ = PrintCallerName(0, "main.main._")}func PrintCallerName(skip int, comment string) bool {    // 实现和前面的例子相同}func CallerName(skip int) (name, file string, line int, ok bool) {    // 实现和前面的例子相同}

caller/main_test.goTest files for the main program (same as in a main package):

package mainimport (    "fmt"    "testing")func TestPrintCallerName(t *testing.T) {    for skip := 0; ; skip++ {        name, file, line, ok := CallerName(skip)        if !ok {            break        }        fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line)    }    t.Fail()}

caller/example_test.goThe caller of the main program's package (in the new main_test package):

package main_testimport (    myMain "."    "fmt")func Example() {    for skip := 0; ; skip++ {        name, file, line, ok := myMain.CallerName(skip)        if !ok {            break        }        fmt.Printf("skip = %v, name = %v, file = %v, line = %v\n", skip, name, file, line)    }    // Output: ?}

Then go to the caller directory, run go run test can get the following output results:

Skip = 0, name = caller. Testprintcallername, file = caller/main_test.go, line = 10skip = 1, name = Testing.trunner, File = $ (goroot)/src/pkg/testi Ng/testing.go, line = 391skip = 2, name = runtime.goexit, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394---fail:te Stprintcallername (0.00 seconds)---fail:example (2.0001ms) got:skip = 0, name = caller_test. Example, file = caller/example_test.go, line = 10skip = 1, name = testing.runexample, File = $ (goroot)/src/pkg/testing/exa Mple.go, line = 98skip = 2, name = testing. Runexamples, File = $ (goroot)/src/pkg/testing/example.go, line = 36skip = 3, name = testing. Main, File = $ (goroot)/src/pkg/testing/testing.go, line = 404skip = 4, name = Main.main, File = $ (TEMP)/go-build365033523/  Caller/_test/_testmain.go, line = 51skip = 5, name = Runtime.main, File = $ (goroot)/src/pkg/runtime/proc.c, line = 220skip = 6, name = runtime.goexit, File = $ (goroot)/src/pkg/runtime/proc.c, line = 1394want:? Failexit Status 1FAIL caller 0.254s

Analyzing the output data we can see that the start-up process for the test code and the example code is not the same as the normal program flow.

To test the startup process of the code:

    1. runtime.goexitor the entrance.
    2. But runtime.goexit runtime.main instead of calling the function, it calls the testing.tRunner function
    3. testing.tRunnerFunctions go test are generated by commands and are used to execute individual test functions

Example code to start the process:

    1. runtime.goexitor the entrance.
    2. Then runtime.goexit call the runtime.main function
    3. The final runtime.main call to go test the command-generated main.main function , in the _test/_testmain.go file
    4. Then call testing.Main , change function to execute each example function

In addition, we can see from this example that the packages we write ourselves with main.main main can also be imported by other packages. But the function in the package after the import of the other package main main is no longer a main.main function. So the entrance to the program is not written by itself. The c22/> function.

Summarize

The Go language runtime Pack's runtime.Caller / runtime.Callers /functions, runtime.FuncForPC while seemingly simple, are very powerful.

These functions can not only solve some practical engineering problems (such as the context information used in gettext-go to get the translation), but also are well suited for debugging and analyzing the runtime information of various go programs.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.