Pointers, Data Types and Memory
Written by rdc
If you read the article Introduction to Pointers you know that pointers contain memory location addresses. You can manipulate the data in these memory locations using the dereference operator *. Using pointers with single data item isn't a problem, but what if you need to store multiple data items together and manipulate them using a pointer? It can get a bit tricky unless you understand how data is stored in memory.
A single memory location in a computer is 1 byte long. Big enough to hold a single ANSI character (as opposed to Unicode characters, which are wide characters and are two bytes. We won't be discussing Unicode characters in this article.) However, all data types are not a single byte in width. Here is a simple program that displays the length in bytes of each data type.
``
Dim a As Byte
Dim b As Short
Dim c As Integer
Dim d As LongInt
Dim au As UByte
Dim bu As UShort
Dim cu As UInteger
Dim du As ULongInt
Dim e As Single
Dim f As Double
Dim g As Integer Ptr
Dim h As Byte Ptr
Dim s1 As String * 10 'fixed string
Dim s2 As String 'variable length string
Dim s3 As ZString Ptr 'zstring
s1 = "Hello World!"
s2 = "Hello World from FreeBasic!"
s3 = Allocate( Len( s2 ) + 1 )
*s3 = s2
Print "Byte: ";Len(a)
Print "Short: ";Len(b)
Print "Integer: ";Len(c)
Print "Longint: ";Len(d)
Print "UByte: ";Len(au)
Print "UShort: ";Len(bu)
Print "UInteger: ";Len(cu)
Print "ULongint: ";Len(du)
Print "Single: ";Len(e)
Print "Double: ";Len(f)
Print "Integer Pointer: ";Len(g)
Print "Byte Pointer: ";Len(h)
Print "Fixed String: ";Len(s1)
Print "Variable String: ";Len(s2)
Print "ZString: ";Len(*s3)
Deallocate s3
Sleep
`
`
The output is (on 32bit systems):
``
Byte: 1
Short: 2
Integer: 4
LongInt: 8
UByte: 1
UShort: 2
UInteger: 4
ULongInt: 8
Single: 4
Double: 8
Integer Pointer: 4
Byte Pointer: 4
Fixed String: 10
Variable String: 27
ZString: 27
`
`
Notice that the length of a pointer is always 4 bytes long on 32bit systems or 8 bytes long on 64bit systems, the same as an integer, regardless of the data being pointed to, since a pointer contains a memory address and not data.
Looking at the length of the different data types, you can see that if you were to Allocate enough space for 10 integers, it would take 40/80 bytes of memory (on 32/64bit systems). Each integer takes up 4/8 bytes (on 32/64bit systems). So the question is, how do you access each integer value from the memory buffer? The answer, pointer math. Take a look at the following program.
``
Dim aptr As Integer Ptr
'Allocate enough space for 2 integers
aptr = Allocate(Len(Integer) * 2)
'Load our first integer
*aptr = 1
Print "Int #1:", "address: "; aptr, "value: "; *aptr
'Move the pointer to the next integer position
'aptr + 1
'Load our second integer
*(aptr + 1) = 2
Print "Int #2:", "address: "; aptr + 1, "value: "; *(aptr + 1)
Deallocate aptr
Sleep
`
`
In this program we dimension two variables, an Integer and an Integer Pointer, aptr. Aptr will point to our memory buffer that will contain two integers. The Allocate function requires the size of the buffer we need, so we multiply the size of an Integer by 2 to reserve 8/16 bytes of memory (on 32/64bit systems, each integer will take 4/8 bytes of space (on 32/64bit systems).
After the allocation process, aptr contains the address of the first byte of our memory buffer. Storing the first integer is simply a matter of using the dereference operator and setting the value to 1. To print out the value, we again just use *aptr.
Now, let me ask you a question: How does the compiler know that the value 1 requires 4/8 bytes (on 32/64bit systems) and not 1 or 2 bytes? Because we dimensioned aptr as an integer ptr. The compiler knows that an integer takes 4/8 bytes (on 32/64bit systems) and so loads the data into four bytes of memory. This is why when we print out the value we get 1 and not some strange number.
To load the second value into our buffer, we use:
``
*(aptr + 1) = 2
`
`
This may look a little strange at first glance. Aptr points to the first byte in our memory buffer. An integer is 4/8 bytes long (on 32/64bit systems), so to get to the next integer byte position, we must add 4/8 to the address (value of aptr). But the compiler knows that the data being used with this pointer is of size Integer or 4/8 bytes (on 32/64bit systems), so to access the second element, you need to only add 1 to the pointer, which can be expressed as *(aptr + 1).
We need the parenthesis around the add operation because the dereference operator * has a higher precedence than +. The parenthesis ensure that we perform the add operation first, and then apply the indirection operator.
Notice that we didn't increment aptr directly. If we did, aptr would no longer point to the start of the memory buffer and the program would crash when we deallocated the buffer since it would Deallocate memory outside the memory buffer. If the need arises to directly increment a pointer, then create a temporary pointer variable and increment that, rather than the pointer used in the original allocation.
Memory buffers and pointers are a powerful way to store and manipulate data in memory. Care must be taken though to ensure that you are accessing the data correctly according to the type of data being stored in the buffer.
Last reviewed by sancho3 on February 07, 2018