Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C Programming for microcontrollers (Joe Pardue, 2005).pdf
Скачиваний:
260
Добавлен:
12.08.2013
Размер:
4.55 Mб
Скачать

Chapter 8: C Pointers and Arrays

Chapter 8: C Pointers and Arrays

Addresses of variables

During the stone age of computers, when C was written, programming was done by positioning switches and looking at lights.

Figure 26: The PDP-11 could be programmed by switches, though Dennis Ritchie used a Teletype machine to write the C programming language.

One set of switches represented data another represented the address of a memory location that you wanted to stick the data. Addresses are sequential and represent contiguous memory locations.

153

Chapter 8: C Pointers and Arrays

I once hand built an 8051 microprocessor ‘system’ with SRAM memory attached to huge lantern battery (SRAM forgets if the power goes off) and switches attached to the data and address ports. I would set a data byte then set an address (two bytes) and push a button to write the data to the SRAM. When it was all loaded, I pressed another button to start the 8051. I bet you can guess what I had the program do. That’s right: blink LEDs. Later I wrote a program that allowed the 8051 to communicate with an original IBM PC and download large programs from the PC, and once loaded – run them. I carefully wrote my primitive bootloader on paper in assembly language, then translated it from assembly to machine code, then hand entered it. The bootloader was only 81 bytes long. I bragged about this incessantly and saw many a set of eyes glaze over. Anyone who knew anything about what I was doing suggested, after rolling his eyes to clear the glaze, that I get an EPROM programmer and write my code on a PC, like a normal person. They just didn’t get it -- I wanted to design the cheapest possible system and factored in my time as equal to zero dollars. Some of us prefer to do things the hard way if something is to be learned and I learned beyond any doubt just how hard it is to correctly enter 81 lousy bytes on a hand made computer. Fortunately for me I’m too darn stubborn to admit defeat and made the thing work until I accidentally disconnected the battery and had to reenter all the data. 81 bytes may not seem like much until you try to enter them and their addresses in binary on DIP switches. After all the cursing died down I retired my machine, bought an EPROM programmer, and joined the real world.

That experience more than any other, burned into my mind the relation of data and addresses, a seemingly trivial relation until you get to C where this topic causes more confusion and bugs than any other.

Data is stored in memory locations – real tangible things made of silicon. These locations have addresses – information about the whereabouts of memory locations. Memory is a place. Addresses tell us how to find the place. Sometimes confusion occurs when we realize that addresses are just numbers and can become data that can be stored in memory locations having… addresses. The data at one memory location can be the address of another memory location whose data may or may not be an address. Data is just a number, in our case an 8-bit byte. When the data is the address of another location of data it is called a pointer. This might seem simple, but pointers can become so confusing and error prone that many higher programming languages won’t let the programmer touch them. This ability

154

Chapter 8: C Pointers and Arrays

to confuse is why C gurus love pointers and will go to incredible lengths to obfuscate their code with them.

Pointers are the reason that many refer to C as a mid-level rather than a high level programming language. In high level languages the programmer only deals with data and the compiler makes all the decisions about the addresses. In low-level languages, like assemblers, the programmer assigns the addresses to the data. In C, we are not required to play with addresses, but are allowed to if we want to. And, dangerous as they are, some things can only be done using pointers. Pointers also allow us to do many things more efficiently and cleverly than would otherwise be the case.

There are many reasons to use pointers, as a simple example consider writing a function that will do something with data from a sequence of contiguous locations in memory. Say you have a string: “Say you have a string:” 22 characters followed by a null character ‘\0’ all contiguous following the first memory location, an address that we will give the alias of MemStart. We know that ‘S’ is located at MemStart and MemStart + 1 stores ‘a’, and so on to MemStart + 23 which stores ‘\0’. If we want our function to handle this sequence, we could send all 23 bytes as parameters to the function, meaning that they would all be pushed on the stack before the function call and pulled off by the function. But we know that we need to go light on the stack in microcontrollers, so we would like to us a different method. Easy, just send the variable MemStart as the parameter and have the function start looking there and increment sequentially through memory until it sees ‘\0’ which we will agree always ends this kind of sequence (like for example, a string which is defined to end with ‘\0’). Now instead of using 23 parameters and pushing 23 bytes on the stack we only have to use one parameter and push only two bytes (addresses are ints, which as you may remember are two bytes long).

Sounds simple, and it is once you get the hang of it, but unfortunately many novice programmers use pointers in much the same way a toddler would use an AK-47. To paraphrase the oft-stated defense of guns, ‘pointers don’t kill programs, programmers kill programs.’

To quote K&R, p 93: “Pointers have been lumped with the goto statement as a marvelous way to create impossible-to-understand programs. This is certainly true

155

Chapter 8: C Pointers and Arrays

when they are used carelessly, and it is easy to create pointers that point somewhere unexpected. With discipline, however, pointers can also be used to achieve clarity and simplicity.”

I once used a pointer to sequentially access the video buffer of an IBM PC. I made a simple ‘fence-post’ error, that is, I started a count with 1 instead of 0, and wrote the last data byte to an address that was one byte outside of the video buffer. That byte was only occasionally important enough to crash the system. When your computer crashes intermittently with no apparent rhyme or reason, you may well be suffering from a bad pointer use. It can be a damn hard bug to find.

To recap: variables (RAM stored data) that contain the address of other variables are called pointers. You can have pointers to data, pointers to pointers, pointers to pointers to pointers to... but let’s try to keep it as simple and clear as possible (whoops… too late).

We declare a variable to be a pointer by preceding its name with an *, called the indirection or dereferencing operator:

int *q; // q is a pointer to an int

We get the address of a variable using &, called the address operator:

q = &v; // put the address of v in the pointer q

Never in that annals of mnemonics have two worse choices been made for symbols. Instead of *, the letters ‘ptr’ could have been chosen for pointer, and for &, the letters ‘addof’. There is no indicator in the second use of the variable name ‘q’ that it is a pointer, but the compiler could have been written to require that some suffix follow the pointer everywhere, thus helping us know what we are dealing with. But NOOOO…. * and & were chosen, and confusion reigns eternal. We could fix this problem by adding a couple of defines to alias the * and & as ptr and addof, and we could require that we always name pointers in such a way that we always know it is a pointer, but since our goal is to learn C as Ritchie and ANSI intended and not my version of C, we’ll do it the hard way. What? you don’t think it will be hard to remember what the * and & do? Wait till you run into things like:

156