Relearning MSX #45: Pointers, arrays and strings (Part 2)Posted by Javi Lavandeira in Development, How-to, MSX, Retro, Technology | June 20, 2016
In the previous post we learnt what pointers are, their properties, and how to use them. However, most likely this didn’t help understand why pointers are so useful or why we even need them at all. Today we’re going to take care of that.
Pointers are most valuable when combined with other functionalities of the C language. Let’s start by taking a look at how they work with arrays.
Working with arrays using pointers
In the previous chapter we saw that if we have a pointer to int and we increment it, now the pointer contains the address of the next int, and the same happened with pointers to char. Let’s see a short example to refresh our memory:
The program above declares two variables (an int and a char) and a pointer to each of them. Integer variables take up two bytes in memory, so incrementing a pointer to int makes it point to an address two bytes higher. Similarly, character variables only take up one byte and incrementing a pointer to char makes it point to an address one byte higher:
However, note that in this particular example it isn’t very useful to increment these pointers. Showing a diagram of what’s in the computer’s memory will help understand why:
In this case the variables and pointers are arranged in memory in the order they were defined in the program. Remember that a pointer is also a variable and it also takes up two bytes in memory. The reason why pointer arithmetic isn’t useful in this scenario is that incrementing the pointer doesn’t make it point to the correct type of data. See this:
On the left diagram we see what happens when we increment pi:
- pi + 1 doesn’t point to an integer, but to a pointer to integer
- pi + 2 points to a char instead of an integer
- pi + 3 points to the high byte of a pointer to integer
- pi + 4 points to an unused memory address
The same goes for pc on the right:
- pc + 1 points to the low byte of a pointer to integer
- pc + 2 points to the high byte of a pointer to integer
- pc + 3 points to an unused memory address
Pointers didn’t help much in this case.
However, consider what happens when we’re working with an array. All the elements in an array are perfectly aligned in memory one after the other. This is when things get interesting: if we have an array of integers called a, and a pointer p to the element a[i], then p+1 will certainly point to the next element in the array, a[i+1].
Let’s see an example. The program below defines an array a that contains three integers, and p is a pointer to int. We can use p to access all the elements in the array:
In this case the memory is arranged as in the diagram below. It’s easy to see that incrementing the pointer will allow us to easily access each of the elements in the array:
Compile and run the program to confirm that it works as expected:
We can optimize this program a bit. In the example we used the i variable to iterate inside the for loop, but we can simplify and use the pointer as the loop variable instead:
This version will do exactly the same as the previous one (compile and run it to confirm), but using one less variable. At the beginning it’s normal to feel that pointers make the program a bit more difficult to follow, but when we get used to working with pointers they often feel more natural and are easier to use.
We can still optimize the previous program a bit more, because…
Array names are actually pointers
In the program POINT3.C we referred to the address of the first element of the array a like this:
However, we could also have written the same thing like this:
That’s not a typo. That’s just the name of the array a. This is because in C, the name of an array is always a pointer to its first element. Let’s confirm this with another example. The program below should print the same value for both &a and a:
So, if a is a pointer to a then a+1 will be a pointer to a, or, in other words, is equivalent to &a. Based on this it follows that in general, a+i is exactly the same thing as &a[i] regardless of the type of the array a.
Maybe you can see already how we can use this to make C functions even more powerful…
Using pointers in function declarations
We learnt some time ago that we can’t use an array as a function parameter. However, we can achieve the same thing by passing a pointer to an array. Similarly, we can use pointers to implement functions that return more than one parameter and also use them to modify local variables that belong to other functions.
Let’s see how these techniques work.
Passing pointers as function arguments
Using pointers as function arguments works just like passing any other value. We just need to declare the type of the argument so the compiler knows we’re passing a pointer and not something else. For example, the program below defines a function called cleari() that takes a pointer to an integer and clears the value pointed to:
This program follows a very simple flow:
- When main() starts the variable i has some memory address already assigned, but its content is still undefined (random) because the program hasn’t initialized it. At this point the pointer p doesn’t even exist yet.
- main() calls cleari() and passes it the address of variable i. Inside cleari() the pointer p is created and assigned the address of i.
- cleari() sets the integer value pointed to by p to 0.
- cleari() ends and now main()‘s i variable is set to 0.
Notice what happened here: inside the cleari() function main()‘s local variable i was reset to 0. We already know that a function’s local variables can’t be accessed by other functions. However, using pointers allows us to bypass this restriction and perform operations with memory addresses that belong to other parts of the program.
Using pointers to implement functions that return more than one value
We’ve just seen that functions accept pointers as parameters and that in this way we can modify variables that belong to other functions. We can use this technique to our advantage in order to implement functions that return more than one value.
As an example, consider the addsub() function below. It takes two integer parameters and returns both their addition and their substraction:
This addsub() function takes four arguments. The first two are the two integer values that we want to add and substract. The other two (s and d) are pointers to integer that the function will use to store the results. Notice that in this case we’re declaring the return type of the function as VOID to indicate that it doesn’t return any value. Instead, the results are stored directly in the addresses pointed by s and d.
To use this function we just need to pass it the two integer numbers to add/substract and the addresses of two integer variables to store the results. For example, to add and substract the numbers 100 and 700 and store the results in the variables sum and dif we would call it like this:
addsub(1000, 700, &sum, &dif);
After this function call the variable sum will contain 1700 and dif will contain 300.
Implementing functions that work with arrays
We can’t pass an array as a parameter to a function, but we can pass a pointer to any element in it and use pointer arithmetic to access each of the elements in turn. Consider the nprint() function below:
nprint() takes two arguments: a pointer to an integer (p) and an integer number (n). It then prints n integer numbers on the screen starting from the one pointed by p. For example, if we have an integer array a that contains 5 elements we can print all of them by just calling nprint(a, 5) as in the program below:
In this simple way we can pass a whole array as an argument and access its elements from inside a function. Compile and run the program to confirm that it works as expected:
Actually, we can use the same function to print part of the array by passing it the address of some element in the middle and the number of elements to print from there. For example, to print the three elements in the middle of the array:
Note that in the POINT6.C program we passed nprint() the name of the array a. We could also have called it passing the address of the first element: nprint(&a, 5). In practice they’re both the same, though each notation has its nuance:
- a refers to the start address of the array as a whole.
- &a refers to the address of an element in the array, in this case the first one.
The compiler doesn’t care which notation you use. Feel free to write it in whichever way makes you feel more comfortable.
In this chapter we’ve learnt how to use pointer arithmetic to access each of the elements of an array and we’be seen that array names are actually pointers to the first element in the array. We’ve also seen how to pass pointers as arguments to functions and how to use this to expand the capabilities of C functions.
In the next post
Now that we’ve learnt about pointers and their relationship with arrays, next we’ll take a deeper look at strings in C.
This series of articles is supported by your donations. If you’re willing and able to donate, please visit the link below to register a small pledge. Every little amount helps.
Javi Lavandeira’s Patreon page
Brilliant post Javi!
Is MSX-C able to return pointers (no as parameters , I mean in a return statement into a function)
Of course it can. That’s how most string management functions are written.
Just declare a function as returning a pointer and return the value as usual. For example, to return a pointer to int declare the function like this:
The iter() function in the exampe above just takes a pointer to an int and returns it incremented.
The while() loop iterates through all the values until it finds one that’s equal to zero.
Good example Javi, well done!
Thanks a lot
Really a well explained post, pointers are one of most difficult C concepts.
About returning a pointer as far as I know you can even use VOID * as wildcard to return any pointer type, maybe? This is, if you want a function that depending of certain conditions return one type or other, you could declare return type as VOID * and makes casting in call function.
char *var = (char *)func(1);
Well, in MSX-C the VOID type is just a redefinition of char (remember that void didn’t exist until later versions of the C language).
But yes, casts will work just fine.
Yuki is adorable. I’m glad you have more fuzzie in your life. I still remember you and Luna. Just wanted to say hello after many years on the webs.
Thanks a lot. Unfortunately, Yuki passed away a few months ago, and Kuro (another ferret I had together with Yuki) a year and a half ago. Both were 6 years old.
If you add me on Facebook you can see lots of their photos (and stories) in there.
Hi again, this time I get a question not related with this specific topic but a general MSX-C compiler question (sorry if it’s not the correct place, I think it’s a interesting issue).
In MSX-C compiler 1.2 seems that there is a bug with symbol table assignement and has some kind of memory leak at certain point. I compile my source with -r3:2:1 options (Pool:Symbol table:Hash). With this configuration, this is the memory available:
symbol table: 5283
First strange think: no mather how big the code is, allways uses 72/5283 symbols. I suposse that this is a bug, but I can’t get a real meassure of symbols used. Well, at certain point of my code simply adding a new variable I get “Symbol table overflow”, so I modify -r options to -r2:3:1 and I get the following values:
symbol table: 7929
This is about 2700 extra space for symbols. Well, I still get symbol table overflow althought it was caused only by a extra variable. It clearly seems a compiler bug.
Any idea to solve it? Thanks.
Please add the -m flag to CF.COM. This will display memory usage statistics and give you more visibility about what’s going on during the compile process, like in this example:
Please post here the memory statistics with -m before and after adding the variable.
In any case, I think that when a program becomes big enough to require tweaking the compiler’s pool/symbol/hash tables it’s time to split it into several files and compile per parts. This will also speed up your development. Will you need help with this?
Sorry for delay, really busy with working :-(. I use -m flag, curiously as you can see in your attached PNG the symbol table allways is fixed to 72, no matter how big is the code, and suddenly it produces “Symbol table overflow”.
Once it appears, no matter what -r configuration you use, error remains, so it seems some kind of memory leak with symbols. I’ve seen a little trick to save symbols: if your code has main function, don’t use local variables in main, they really uses a lot of symbols.
With the “main trick” I’ve avoided it, but as I say I really think it’s a compiler bug.
Code is splitted since months ago, I’ve started to have symbol table overflow recently even with splitted code ;-).
Thanks for your help, your articles are really useful ;-)
I wouldn’t call it a memory leak until all other options have been tested and discarded. After all, if there’s an actual leak the compiler wouldn’t see the symbol table growing. It would just run out of available memory at some point while not using it.
Are you sure that adding the variable is the only change? You aren’t including any other header file?
any idea when the following article will be published?
Hello Javi, Thank You very much for these tutorials.
I started to lear C language with them. Thanks a lot.
wondering if you’ll ever get back to this project… best, Vic