Hurray, ROBOTC has support for pointers!  “That’s nice,” I hear you say, “but what are they?”  That’s a good question.  I’ll try to explain in this tutorial.  This tutorial is the first of a couple that will show you the kinds of things you’ll be able to do with pointers.

Standard varables

Up until now, ROBOTC only supported normal variables; in other words, a variable, be it an int, float or anything else, only had a value, like 712, 0.383 or “howdy”. For example:

long foo  = 76278; 
float baz = 2.9121; 
string greet= “hello”;

In effect, when you declare a variable, the compiler puts aside an appropriately sized amount of memory and gives it a symbolic name, like i or f or baz.  An int is 4 bytes large, a float is also 4 bytes but a string (in the case of ROBOTC) is usually 20 bytes large.  Take a look at the drawing below.  You see the memory blocks, the labels assigned to them, their contents and the addresses (the hex numbers under the blocks).

image

Dissecting a variable

A variable has two parts, an rvalue and an lvalue.  As you can guess, the rvalue is the part of the right side of the assignment and lvalue is the part on the left side.  Now consider this bit of code:

// ptr tutorial example 1 
task main() 
{ 
  int i, j; 
  i = 10; 
  j = 51; 
  // This should print out 
  // i: 10, j: 51 
  writeDebugStreamLine("i: %d, j: %d", i, j);

  // Assign i's rvalue to j 
  j = i; 
  // This should print out 
  // i: 10, j: 10 
  writeDebugStreamLine("i: %d, j: %d", i, j); 

  i++; 
  // This should print out 
  // i: 11, j: 10 
  writeDebugStreamLine("i: %d, j: %d", i, j); 
}

The output of this program should look like this:

i: 10, j: 51 
i: 10, j: 10 
i: 11, j: 10

When looking at the memory locations and their contents after each operation, it would look something like this:

image

On the first assignment, i’s rvalue is given the value 10, on the second assignment, j’s rvalue is given the value 51.  However, what happens on the line “j = i”?  Simple, the rvalue of i is copied and assigned to j’s rvalue.  Now both j and i’s rvalues are 10.  What happens when we increment i? Does it change j’s rvalue?  In short, no, the two rvalues are completely separate entities.

Getting to the point(er)

As I mentioned earlier, when a variable is declared, the compiler assigns an appropriately sized chunk of memory to it.  This chunk has an address, a number, much like a house.  Should you want to access this address in ROBOTC, you can do so with the reference (&) operator.  This isn’t very useful in itself, though.  Fear not, they didn’t add this ability to get the address without being able to doing something practical with it as well.  Enter the pointer (*) operator.

Previously, we saw that the rvalue of a variable contains the actual value that we assigned.  With a pointer, the rvalue is in fact the address of the variable we’re pointing at.  But what if we want to see the value of this variable we’re pointing at?  We can do that with the dereference operator, also a “*”.  Combining all this new-found knowledge, we get the following:

// ptr tutorial example 2
task main()
{
  int i = 10;
  int *iPtr;
  iPtr = &i;  // iPtr now points at the address of i
  // This should print out
  // i: 10, iPtr: 656F6968, *iPtr: 10
  // (value for iPtr may vary)
  writeDebugStreamLine("i: %d, iPtr: %p, *iPtr: %d", i, iPtr, *iPtr);

  i++;
  // This should print out
  // i: 11, iPtr: 656F6968, *iPtr: 11
  // (value for iPtr may vary)
  writeDebugStreamLine("i: %d, iPtr: %p, *iPtr: %d", i, iPtr, *iPtr);
}

The output should look like this:

i: 10, iPtr: 656F6968, *iPtr: 10
i: 11, iPtr: 656F6968, *iPtr: 11

If you look at it from a memory perspective, it looks like this:

image

Pointer arithmetic

Now that you know how what pointers are, let’s have some fun with them.  Consider the code below:

// ptr tutorial example 3
task main()
{
  ubyte arr[3] = {1, 2, 3};
  ubyte *arrPtr;

  arrPtr = &arr[0];  // arrPtr now points to the address of arr[0]

  // This should print out
  // arr[0]: 1, arrPtr: 656F6968, *arrPtr: 1
  // (value for arrPtr may vary)
  writeDebugStreamLine("arr[0]: %d, arrPtr: %p, *arrPtr: %d", arr[0], arrPtr, *arrPtr);

  arrPtr++;  // we're now pointing at arr[1]

  // This should print out
  // arr[1]: 2, arrPtr: 656F6969, *arrPtr: 2
  // (value for arrPtr may vary)
  writeDebugStreamLine("arr[1]: %d, arrPtr: %p, *arrPtr: %d", arr[1], arrPtr, *arrPtr);
}

That should print out something like this:

arr[0]: 1, arrPtr: 656F6968, *arrPtr: 1
arr[1]: 2, arrPtr: 656F6969, *arrPtr: 2

Please note that the address arrPtr may be different in your case.  It is important that on the second line, the address arrPtr points to is one higher than in the first one.

When you look at what it looks like in memory, you can get a good idea of what’s going on:

image

Interesting fact about pointers

When you use sizeof() on a normal variable, like a ubyte, int or long, you get the number of bytes this type takes up.  In the case of a ubyte, that’s one byte, int takes up two and a long uses four.  Doing a “sizeof” of a pointer is, well, pointless.  You won’t be getting the size of the item you’re pointing at, it’s always 4 in the case of ROBOTC.  That’s because the address stored in the pointer is a 4 byte number, a long.  Keep that in mind when you want to use sizeof() to get the number of bytes you want to wipe with a memset(), for example.

Tags