highlighter

Wednesday, July 31, 2013

A few pointer pointers

 This post assumes the code is executed and the reader is observing the output while reading.

Light Version
Raw Text

package main

import "fmt"

func main() {

 a := 0
 b := 0

 // a and b's address that points to where they are located in memory.
 fmt.Printf("main: &a = %v &b = %v\n\n", &a, &b)

 /*
  Note that when we pass by value, the 'a' declared in foo()'s arguments has a different
  address than the 'a' variable declared in main().

  Which means that we are actually passing in a copy of main()'s 'a' variable.
  Any changes made to the value of foo()'s 'a' will not change the value of main()'s a.
 */
 fmt.Println("[pass by value]")
 fmt.Println("main: before foo(a), a =", a)
 foo(a)
 fmt.Println("main: after foo(a), a =", a)

 /*
  Note that when we pass by pointer, the VALUE of bar()'s argument bptr equals
  the address of main()'s b. Also note that the main()'s bptr address is different
  than the addresses of main()'s b and bar()'s bptr.

  This means that a pointer is just a integer data type that stores an address in memory
  as it's value. When passing a pointer into a function, instead of passing in a copy of the
  variable being pointed to, we pass in a copy of the pointer itself. The new pointer still
  points to the same variable because the pointer's value (the variable's address)
  doesnt change.

  Since a pointer's value is only an integer representing the address of a variable in memory
  we cant directly modify the value of the variable being pointed at, by modifying the
  pointer's value.
  For example, lets say we wanted to increment variable 'b' (b+=1). If we tried,

  b := 0
  bptr := &b // bptr == 0x01000008
  bptr++
  fmt.Println(b, bptr) // b == 0, bptr == 0x01000012

  the value of b wont be incremented when 'bptr++' is called. What actually happens is that the
  value stored in bptr (the address bptr is currently pointing at) will be incremented to point
  to a new contigious block of memory.

  [NOTE: Unlike C/C++, developers arent actually allowed to directly modify the value of a pointer
   using arithmetic in go. Pointer arithmetic is considered an unsafe language feature, therefore
   developers must use go's "unsafe" pkg to manipulate a pointer's value.]

  In go and many other C-like language, accessing the variable through one of its pointers
  requires dereferencing the pointer by prefixing the dereference operator ('*') onto
  the pointer's identifier (e.g '*bptr'). In computer science terms this is called indirection.

  [NOTE: In C/C++/Go/etc, The asterisk symbol ('*') has several semantical meanings depending on
  the context in which it is used. Most developers will be familiar with it's use as the
  multiplication operator. However, for developers unfamiliar with C-style pointer syntax,
  the asterisk is a notoriously common source of confusion when first learning about pointers.
  I assume the reason for this confusion stems from the fact that the asterisk is not only used to
  dereferences a pointer, but to specify a pointer type as well. The confusion is only made much
  worse when the address-of operator ('&') and pointer pointers (i.e. xptr_ptr => xptr => x) are
  thrown in the mix.

  For example, check out this function literal.

  func(b uint32) float32 {
   return *(*float32)(unsafe.Pointer(&b))
  }

  Despite only being one line of code, it's not uncommon for even an experienced go developer
  to require a few moments to fully digest whats going on the first time they stumble upon
  code like this. I assume most developers, who are still new to pointers, will
  find code like this to be intimidating and very difficult to conceptually visualize whats
  going on semantically.

  If you, the reader of this blog post, are struggling to understand
  what this code does. I recommend taking it slow and using the language's spec as a codex to
  decipher what is going on.

  Here is code example to help point you in the right direction.
  http://play.golang.org/p/myunEgQPRG]

 */

 bptr := &b
 fmt.Printf("\nmain: bptr = %v (aka &b), &bptr = %v, *bptr (aka b) = %v \n\n", bptr, &bptr, *bptr)
 fmt.Println("[pass by pointer]")

 fmt.Println("main: before bar(bptr), b =", b)
 bar(bptr)
 fmt.Println("main: after bar(bptr), b =", b)

 /*
  To be continued....
 */
}
func foo(a int) {
 fmt.Printf("foo: a = %v, &a = %v, \n", a, &a)
 fmt.Println("foo: a++")
 a++
 fmt.Printf("foo: a = %v, &a = %v, \n", a, &a)
}
func bar(bptr *int) {
 fmt.Printf("bar: bptr = %v, &bptr = %v, *bptr (aka b) = %v \n", bptr, &bptr, *bptr)
 fmt.Println("bar: *bptr++")
 *bptr++
 fmt.Printf("bar: bptr = %v, &bptr = %v, *bptr (aka b) = %v \n\n", bptr, &bptr, *bptr)
}


No comments:

Post a Comment