by Gary Roberts | Updated: 03/23/2017 | Comments: 2
Pointers are a great tool for CRBasic programs. If you use them carefully, pointers can reduce the amount of program code you need to write, thereby increasing your program’s efficiency and enabling you to use less memory. (Your program can run faster because it does not have to duplicate the data in memory).
To learn how to use pointers, it would be helpful to first talk about variables and how they are stored. Variables are stored in memory cells inside the data logger’s memory. The data logger’s memory is made up of consecutive memory cells, a byte long, each with a unique address. To better understand how a data logger uses its memory, I am going to use the analogy below, which should help when we start talking about pointers.
Imagine that the data logger’s memory is a large warehouse that can store lots of things inside it. In our example, we are going to fill our warehouse with buckets of different sizes and types. (Depending on our needs, we may fill the entire warehouse or only a portion of it.) All the buckets we use hold things of one type or another. Larger buckets are used to hold larger things, and smaller buckets are used for smaller things. The number and size of buckets used, as well as the amount of space used in the warehouse, depend on you as the programmer and the needs of the project you are working on.
When we start writing our CRBasic program, we declare variables. In our warehouse scenario, when we declare a variable, we are creating a bucket (using memory cells) in the data logger’s warehouse to hold the piece of information (variable) that we want to work with. This bucket can only hold certain types of variables. For example, one small bucket holds a float variable and takes up about 4 bytes of the data logger’s free warehouse (memory) space. Another small bucket holds a Boolean variable, and it also takes up 4 bytes of floor space in the warehouse. We then add a large bucket that holds a string variable, which takes up 24 bytes of warehouse space.
With a warehouse full of buckets, how do we know which bucket holds which piece of information we want? Fortunately, we have the ability to give meaningful names to our buckets that help to describe the piece of information they hold. For example, we can give the name temperature to a bucket that holds a float variable by declaring our float variable as temperature. As we continue to add and declare variables, the data logger’s warehouse (memory) starts filling up.
If our warehouse is getting full, how do we easily find the bucket we need? Fortunately, the data logger’s processor is a fantastic warehouse manager. It knows where every bucket is stored. Each bucket has a specific, unique address with an aisle, section, and shelf within the data logger’s warehouse. (No two buckets share the same address.) The warehouse manager can use the unique address to quickly find the bucket in the warehouse. When we come to the warehouse wanting the bucket holding the float variable we named temperature, the data logger efficiently decodes the address and gets the right bucket (data logger memory cells) we are looking for and the information (variable) it contains. We can then read or change what is inside the temperature bucket (such as, temperature = 32.14).
Now here is where pointers come into play. Pointers are a way for us to help our warehouse manager (the data logger’s processor) be even more effective at its job and be a better steward of the limited warehouse space (memory).
A pointer is a variable whose value is the address of another variable. Just like any variable or constant, you must declare a pointer before you can work with it. When you declare a pointer in CRBasic, it needs to be declared as a type Long:
Public my_pointer As Long
This line of code is called the pointer declaration or pointer variable. The pointer variable stores the address of the other variable that we want to point to.
To use or initialize the new pointer, we then need to tell CRBasic to point it at a specific piece of memory that is tied to the temperature variable. To do this, we can use the following syntax:
my_pointer = @temperature
The memory (warehouse) address of temperature is now stored in the pointer variable my_pointer. The @ symbol is known as the address-of operator. It is a unary operator that returns the memory address of its operand. Using the example above, my_pointer is now equal to the address of temperature.
This now allows us to do several things in our CRBasic program. For example:
Public my_pointer As Long Public mike As Long, tom As Long, melissa As Long my_pointer = @mike mike = 42 tom = mike melissa = my_pointer
In this short CRBasic program, the following are true:
If we want the variable melissa to contain the same value as mike (and not mike’s memory address), we need to use the pointer dereference or indirection operator ! as shown here:
Public my_pointer As Long
Public mike As Long, tom As Long, melissa As Long
my_pointer = @mike
mike = 42
tom = mike
melissa = !my_pointer
Now melissa equals 42—just the same as mike and tom.
Well that is neat and all, but wouldn’t it just be easier to set melissa equal to mike? Most of the time, yes. There are times when a pointer is handier, but that’s a more advanced topic.
Now to put pointers into action.
In the “6 Steps to Easily Parse Data from a Trusted Source” blog article, I shared with you how to parse XML from other sources using a CRBasic data logger. Now I am going to show you how we can use pointers to improve and shorten the program, as well as save precious data logger memory.
In that earlier blog article, the program I shared with you looked for and read one variable from the XML file. To get more information from the returned XML, we are going to have to add more IF statements to get the information we need (such as humidity):
If xml_response_code = XML_END_OF_ELEMENT AND xml_element_name = "relative_humidity" Then noaa_releative_humidity = xml_value EndIf
To get all the data from the XML file, we’ll add thirty-four additional IF statements to get it all parsed. Now, using pointers, we can get the work done in a single IF statement!
If xml_response_code = XML_END_OF_ELEMENT Then
pointer = 0
pointer = @(xml_element_name)
If(pointer > 0) Then !pointer = xml_value
EndIf
What we have done is declare the variables we want to store information in using the same exact names as the elements in the XML file. By doing that, we take advantage of pointers in our data logger and make our programming more compact and precise.
With pointers, our program downloads the XML as it did before and starts parsing it using the XMLParse() instruction. When it gets to the end of an element name, we start into our IF statement. The first line in that IF statement sets the pointer to memory address 0. This is just good programming practice, as the data logger will never return a reference to a variable at address 0. This helps us (and the data logger) know if the dereference in the next line of code worked or failed.
The next line sets the pointer to the address-of operator (@) for the variable with the same name as is stored in the variable xml_element_name. The parentheses around the variable name xml_element_name tell the data logger not to look at the address of xml_element_name, but to look at what is stored in xml_element_name. For example, when xml_element_name = “relative_humidity” the variable pointer points to the memory address of the variable named relative_humidity and not to xml_element_name. That is why our variable names must be named the same as the elements in the XML file we are parsing.
The last line in the statement says that if the pointer is not pointing to memory address zero, pour the information in xml_value into the variable at the address that pointer is pointing at. Using our previous example, the value stored in xml_value would be dumped into the variable relative_humidity.
Aren’t pointers handy?
I have included a working example of a program using XMLParse() and pointers. Just download the CRBasic program into your data logger (ensuring the data logger has an active Internet connection), run it, and watch the program update using pointers.
I hope this information was helpful to you. If you have any questions, please post them below.
Comments
kelly | 02/16/2018 at 04:56 AM
This is a few lines from the help file.
"
Pointers can be accessed within expressions:
!(Pointer + X)
will access the value at Pointer + X.
"
so, using this call:
UnpackArray(PkAryVal, @UnPack(),12)
Why does this work:
Sub UnpackArray(Val As Long, AryPtr As Long, Elements As Long)
Dim Ref As Long
Dim x As Long
For x = 0 To Elements - 1
Ref = AryPtr + x
!Ref = (Val >> x AND 1)
Next x
EndSub
but not this:
Sub UnpackArray(Val As Long, AryPtr As Long, Elements As Long)
Dim x As Long
For x = 0 To Elements - 1
!(AryPtr + x) = (Val >> x AND 1)
Next x
EndSub
Testing on CR6 os 6.08
Thanks for any light that can be shed on this issue
crs | 04/07/2018 at 03:51 PM
I would love to see more blog posts / tutorials on using pointers in CRBasic.
I am trying to write a function that can accept any type of variable (or array) and perform some operation on it (e.g., using TypeOf() and SprintF()), but not having any luck recognizing the original variable within my function. How does the following, from the CRBasic help file, apply?
By default, pointer variables are of type Long, but they can be typed using Float!, Long!, Boolean!, or String! (e.g., Dim P as Float!). If pointers are not typed, the summation of the values pointed to truncates the floats before the addition.
Also, how would I handle an array within the function? Does the pointer offset have to be scaled for each successive array element by the size of that element?
Please log in or register to comment.