Consider a scenario in main where some data has been captured, but it must be converted from signed-6 bit to signed 8-bit
assembly code loop:
byte dataHigh [500];
byte dataLow [500];
...
for(i=0;i<500;i++) {
dataLow [i]=PINA;
dataHigh [i]=PINB;
delay_ms(1);
}
Registers?
Stack?
This is not a good approach, this represents a thousand or more unnecessary moves of data to and from the stack.
What if at the time of the call, we could just point the function to the place in memory where the data is stored and have it access and manipulate it directly? We can do this with variables called pointer variables in C for storing memory addresses and referencing memory locations.
Use of pointers:
One way is to store the 500 data points in a contiguous block of memory. Then, the only information need is
Information required to use a contiguous data blob representing an array:
Arrays are set up this way in C. In C, a simple data array is stored in contiguous block of memory (so far as the program can tell since any memory paging is abstracted).
So, if you know data[0] exist at memory location 0x0F10, where is the second point?
So, how is the size of the base data type known by the function?
I want you to think about writing assembly here. There are several options.
One option is that the array is passed to a function as two pieces of information at RUN TIME:
Pop size from stack to a register Rs
Pop address from stack to register pair Z
Loop: LD Rd, Z
Do some processing
ST Z, Rd
ADD Rs to Z
Evaluate exit condition and potentially loop
Typically we are in a better situation, where the data type and therefore size is determined at COMPILE TIME.
Dynamic Element Size:
Pop size from stack to Rs Pop addr from stack to register pair Z Loop: LD Rd, Z Do processing using variable wordsize Rs ST Z, Rd Add Rs to Z Evaluate exit condition potentially loop
Using a size:
Pop addr from stack to register pair Z Loop: LD Rd, Z Do processing using hard-coded,compile-time wordsize SIZEOFTYPE ST Z, Rd ADDIW Z, SIZEOFTYPE Evaluate exit condition and potentially loop
So if you know where the first point of data is in an array, you know where the second point is (address of first point + size of datatype) on so on for the rest of the array
What about the length of the array?
Length of Array
Remember, the end of the array must always be encoded via some mechanism or convention:
So, we’ve discussed one reason to use pointers: sharing large sets of data with functions without unnecessary copying
(we discussed arrays, later we will discuss other large data structures like structs, lists, trees etc…)
Later we will use pointers to keep track of and refer to dynamically allocated (run-time) arrays and structures as opposed to arrays and variables that are defined at compile time and can have there location known at compile-time.
Now we need some syntax in C to provide away to code all of this. Lets look at some function prototypes and what parameters are pushed on the stack (assuming parameters passed on the stack)
int myFunction1(int data);
Single data value is pushedint myFunction2(int data[]);
A single address is pushedThe alternative syntax for the second is
int myFunction(int * data);
A single address is pushed
indicates that an address is being passed and that data is a pointer variable
So, we have a new variable type (int *).
Example:
int count; int * ptrCount;
We can “point to”/reference/“store address of” count like this:
ampersand prefix means return a pointer to, or “address of”
ptrCount = & count;
We can use ptrCount as a regular int variable through dereferencing.
*
is the dereference operator
(*ptrCount) = (*ptrCount) + 1; // modifies count
A pointer variable can be used to modify other variables. It can also be passed to functions to do the same.
Here is an example:
void SameMax( int * ptrA, int * ptrB){ if (*ptrA > *ptrB){ *ptrB = *ptrA; } else { *ptrA = *ptrB; }
…somewhere in main…
int a = 1, b=2;
int * ptrInt;
ptrInt = & a;
Now we can pass the pointer
SameMax(ptrInt, &b);
or
SameMax(&a, &b);
Both result in a=2, b=2. (&a creates a pointer)
In a generic sense, a “pointer” tells us where something can be found.
In programming, a pointer variable contains the memory address of
In Java, a reference variable is used to give a name to an object. The reference variable contains the memory address at which the object is stored.
Truck ford = new Truck( );
Since C does not support objects, it does not provide reference variables. However, C provides pointer variables which contain the memory address of another variable (sometimes called the “pointee”). A pointer may point to any kind of variable.
To declare a pointer variable, we must do two things
Use the “*” (star) character to indicate that the variable being defined is a pointer type.
Indicate the type of variable to which the pointer will point (the pointee). This is necessary because C provides operations on pointers (e.g., *, ++, etc) whose meaning depends on the type of the pointee.
General syntax for declaration of a pointer:
type *nameOfPointer;
int * ptrInt;
ptrInt
to be a pointer to a variable of type int
. ptrInt
will contain the memory address of some int
variable or int
array.int *ptrInt, ptrInt2;
ptrInt
is a pointer to an int, but ptrInt2
is NOT A POINTER!The two primary operators used with pointers are
*
(star)&
(ampersand)The *
operator is used to declare pointer variables and to deference a pointer. “Dereferencing” a pointer means to use the value of the pointee.
The &
operator gives the address of a variable.
int x = 1, y = 2, z[10]; int *ip; /* ip is a pointer to an int */ ip = &x; /* ip points to (stores the memory address of) x */ y = *ip; /* y is now 1, indirectly copied from x using ip */ *ip = 0; /* x is now 0 */ ip = &z[5]; /* ip now points to z[5] */
When in doubt about order of operations, or to increase readability and conveyance of intent, use parenthesis
ip = &(z[5]);
If ip
points to x
, then *ip
can be used anywhere x can be used so for the above example, the following are equivalent:
*ip = *ip + 10;
x = x + 10;
The unary operators (one operand) *
and &
are higher precedence than binary arithmetic operators (two operands). So y = *ip + 1;
takes the value of the variable to which ip points, adds 1 and assigns it to y
The statements *ip += 1;
and ++*ip;
and (*ip)++;
each increment the variable to which ip points.
I will now introduce a terminology for this class referring to the type being pointed to or the type of an array. I will call this the “objective type” to since “pointee type” is difficult to grammatically distinguish from “type of pointee”
The objective type of a pointer and the type of its pointee must match in assignments.
int a = 42; int *ip; double d = 6.34; double *dp; ip = &a; /* ok -- types match */ dp = &d; /* ok */ ip = &d; /* compiler error -- type mismatch */ dp = &a; /* compiler error */
int a = 1, *ptr1; /* show value and address of a ** and value of the pointer */ ptr1 = &a ; printf("a = %d, &a = %p, ptr1 = %p, *ptr1 = %d\n", a, &a, ptr1, *ptr1) ; /* change the value of a by dereferencing ptr1 ** then print again */ *ptr1 = 35 ; printf("a = %d, &a = %p, ptr1 = %p, *ptr1 = %d\n", a, &a, ptr1, *ptr1) ;
NULL is a special value which may be assigned to a pointer
NULL indicates that this pointer does not point to any variable (there is no pointee)
Often used when pointers are declared
int *pInt = NULL;
Often used as the return type of functions that return a pointer to indicate function failure
int *myPtr = myFunction( ); if (myPtr == NULL){ /* something bad happened */ }
A NULL pointer is a pointer with the NULL value.
This version of the swap function doesn’t work. WHY NOT?
/* calling swap from somewhere in main() */ int x = 42, y = 17; Swap( x, y ); /* wrong version of swap */ void Swap (int a, int b) { int temp; temp = a; a = b; b = temp; }
The desired effect can be obtained by passing pointers to the values to be exchanged.
/* calling swap from somewhere in main( ) */ int x = 42, y = 17; Swap( &x, &y ); /* correct version of swap */ void Swap (int *px, int *py) { int temp; temp = *px; *px = *py; *py = temp; }
Passing the address of variable(s) to a function can be used to have a function “return” multiple values.
The pointer arguments point to variables in the calling code which are changed (“returned”) by the function.
void ConvertTime (int time, int *pHours, int *pMins) { *pHours = time / 60; *pMins = time % 60; } int main( ) { int time, hours, minutes; printf("Enter a time duration in minutes: "); scanf ("%d", &time); ConvertTime (time, &hours, &minutes); printf("HH:MM format: %d:%02d\n", hours, minutes); return 0; }
What is the output from this code?
void F (int a, int *b) { a = 7 ; *b = a ; b = &a ; *b = 4 ; printf("%d, %d\n", a, *b) ; } int main() { int m = 3, n = 5; F(m, &n) ; printf("%d, %d\n", m, n) ; return 0; }
3, 7
We still have one more syntax for pointers to learn ->
, but we this will be covered after struct
and union
are covered
/* define a struct for related student data */ typedef struct student { char name[50]; char major [20]; double gpa; } STUDENT; STUDENT bob = {"Bob Smith", "Math", 3.77}; STUDENT sally = {"Sally", "CSEE", 4.0}; STUDENT *pStudent; /* pStudent is a "pointer to struct student" */ /* make pStudent point to bob */ pStudent = &bob; /* use -> to access the members */ printf ("Bob's name: %s\n", pStudent->name); printf ("Bob's gpa : %f\n", pStudent->gpa); /* make pStudent point to sally */ pStudent = &sally; printf ("Sally's name: %s\n", pStudent->name); printf ("Sally's gpa: %f\n", pStudent->gpa); Note too that the following are equivalent. Why?? pStudent->gpa and (*pStudent).gpa /* the parentheses are necessary */
void PrintStudent(STUDENT *studentp) { printf("Name : %s\n", studentp->name); printf("Major: %s\n", studentp->major); printf("GPA : %4.2f", studentp->gpa); }
Passing a pointer to a struct to a function is more efficient than passing the struct itself. Why is this true?