In C, there is a strong relationship between pointers and arrays.
The declaration int a[10];
defines an array of 10 integers.
The declaration int *p;
defines p as a "pointer to an int".
The assignment p = a;
makes p
an alias for the array and sets p
to point to the first element of the array.
p = &a[0];
or p=&(a[0]);
We can now reference members of the array using either a or p
a[4] = 9; p[3] = 7; int x = p[3] + a[4] * 2; //Same as int x = a[3] + p[4] * 2;
Let there be the declarations
int a[]={1,2,3,4,5,6,7,8}; int *intPtr;
a
is actually an address that can be referenced with an offset
a[0]
, so the square bracket operator [ ] is actually dereferencing with an offset
In fact, we can do the same with the pointer variable.
intPtr=a;
or
intPtr=&(a[0]);
result in
intPtr[0] is the value 1;
So, we have learned two of three ways to dereference pointers:
Syntax | Method |
---|---|
* | dereference |
[index] | dereference with an memory offset of index times sizeof(type) |
-> | this is a third that we will see soon for accessing members of struct/union |
If p
is a pointer to the type of the base element of an array, and p
points to a particular element of an array, then p + 1
points to the next element of the array and p + n
points n elements after p
The meaning a "adding 1 to a pointer" is that
p + 1
points to the next element in the array.
Example:
int * p;
The pointee type is intint a[10];
The element type is intint ** p;
The root pointee type is int , but p has an pointee type of int *The meaning of "adding an integer to a pointer" is that p + i
is pointing to memory at address p
with offset i*sizeof(pointee_type)
The name of an array is nearly equivalent to a pointer where pointer arithmetic is concerned
Therefore, if a is the name of an array, the expression a[i]
is functionally equivalent to *(a + i)
.
&a[ i ]
and (a + i)
access the same element. Both access the i-th element beyond the starting element of a
.
since p
is a pointer, it may also be used with a subscript as if it were the name of an array.
p[ i ]
is identical to *(p + i)
One difference is that an array name can only "point" to the first element of its array (there is an exception to this rule when the array is a function parameter). It can't be changed to point to anything else. A pointer may be changed to point to any variable or array of the appropriate type
int vec[3] = {1,2,3};
int i;
vec = &i;
We aren't free to change the address stored in "variable" vec
(may only be a compile time variable) like we can with an actual pointer. vec is more like a const pointer (int * const v=&(a[0]);
)
On the other hand, a pointer can point to an array, and then be pointed to other elements, and then be pointed to another location entirely
int g, grades[ ] = {10, 20, 30, 40 }, myGrade = 100, yourGrade = 85, *pGrade; /* grades can be (and usually is) used as array name */ for (g = 0; g < 4; g++) printf("%d\n",grades[g]); /* grades can be used as a pointer to its array if it doesn't change*/ for (g = 0; g < 4; g++) printf("%d\n", *(grades + g)); /* but grades can't point anywhere else */ grades = &myGrade; /* compiler error */ /* pGrades can be an alias for grades and used like an array name */ pGrades = grades; /* or pGrades = &(grades[0]); */ for( g = 0; g < 4; g++) printf( "%d\n", pGrades[g]); /* pGrades can be an alias for grades and be used like a pointer that changes */ for (g = 0; g < 4; g++) printf("%d\n",*(pGrades++)); //pGrades=pGrades+1 after each execution /* BUT, pGrades can point to something else entirely other than the grades array */ pGrades = &myGrade; printf( "%d\n", &pGrades); pGrades = &yourGrade; printf( "%d\n", &pGrades);
int image5x5[25];
int *middleRow = &arr[10];
int *middle = &arr[12];
middleRow[1]
accesses element 1 of the middle rowmiddle[-1]
accesses the element just before (to the left) the middle pixel of the imageIf you allocate an extra terminating element, you are allowed to get the address of it and work backwards:
int image5x5[26];
int *end = &arr[25];
end[-1]
is the last elementend[-2]
is the second-to-last elementptr=&arr[24]+1;
but the address is not well-defined and may not work as expected accord to the C standard. I imagine one could have another member of a struct following the array and at least avoid moving into an invalid space, but check your C standard carfully&
&array
generates a pointer to an array (e.g. (int (*)[8]) ), for which pointer arithmetic increments by the sizeof the entire array&pointer
generates a pointer to a pointer, for which pointer arithmetic increments by the sizeof a pointersizeof
sizeof(pointer)
returns the size of a pointersizeof(array)
returns the size of the array= {10,20}
or ="hello"
, the arrays can not be used with all the same syntax as a pointerWith respect to a function's formal parameters, C treats a one-level array just like a pointer (multi-dimensional and variable-length arrays will be discussed in a later lecture)
The following results from a function call from main to bubble(int arr[])
illustrate this as will as similarities and differences between arrays and pointers
File bubble.c:
void bubble(int arr[]){ int tmp; for (int i=0;i<(4-1);i++){ if (arr[i+1]<arr[i]){ tmp=arr[i]; arr[i+1]=arr[i]; arr[i]=tmp; } } } int main() { int arr[8]={10,30,20,40}; bubble(arr); return arr[0]; }
gcc -Wall -g -O0 bubble.c
gdb ./a.out
b bubble.c:3
run
gdb command | result after select-frame 1 caller main |
result after select-frame 0 function bubble |
note |
---|---|---|---|
print arr | {10, 30, 20, 40, 0, 0, 0, 0} | (int *) 0x7fffffffdb90 | gdb knows the length of the array the command explore arr will invite you to examine an element of the array |
print &arr | (int (*)[8]) 0x7fffffffdb90 | (int **) 0x7fffffffdb68 | pointer to array of eight integers vs. pointer to a pointer to an integer |
print sizeof(arr) | 32 | 8 | an array does not decay into a pointer in the context of the sizeof operator |
print *arr | 10 | 10 | an array decays into a pointer to the first element under most circumstances |
print &(arr[0]) | (int *) 0x7fffffffdb90 | (int *) 0x7fffffffdb90 | both point to the same first element |
print arr+0 | (int *) 0x7fffffffdb90 | (int *) 0x7fffffffdb90 | a shorthand to force decay into a pointer to the first element |
print arr+1 | (int *) 0x7fffffffdb94 | (int *) 0x7fffffffdb94 | pointer arithmetic is handled the same |
print (&arr)+1 | (int (*)[8]) 0x7fffffffdbb0 | (int **) 0x7fffffffdb70 | pointer to array vs pointer to pointer +32 (sizeof length-8 integer array) +8 (sizeof(pointer)) |
Work through the following code and find what it prints by practicing diagraming pointers.
ptrAdd.c:
int main() { char c, *cPtr = &c; int i, *iPtr = &i; double d, *dPtr = &d; printf("\nThe addresses of c, i and d are:\n"); printf("cPtr = %p, iPtr = %p, dPtr = %p\n", cPtr, iPtr, dPtr) ; cPtr = cPtr + 1 ; iPtr = iPtr + 1 ; dPtr = dPtr + 1 ; printf("\nThe values of cPtr, iPtr and dPtr are:\n") ; printf("cPtr = %p, iPtr = %p, dPtr = %p\n\n", cPtr, iPtr, dPtr) ; return 0; }
The code below shows how to use a parameter array name as a pointer.
Notice the expression *grades++ .
void printGrades( int grades[ ], int size ) //same as int * grades, int size { int i; for (i = 0; i < size; i++) printf( "%d\n", *grades++); //I prefer to write *(grades++) }
What about the following prototype? Is it compatible?
void printGrades( int *grades, int size);
Does this alternative body do the same?
{ int * gradesEnd = grades+size; for (; grades < gradesEnd; grades++) printf( “%d\n”, *grades); }
what about this?
{ int * gradesEnd = grades+size; for (; grades < gradesEnd;) //same as while printf( “%d\n”, *grades++); }
int sumArray( int a[ ], int size)is equivalent to
int sumArray( int *a, int size)
a
as a pointer within the function. In fact, any error message produced within the function will refer to a
as an int *
in either versionManaging array sizes in C is not a minor issue. (On an exam you should be prepare for questions about determining the size of an array or ask you about designing functions using arrays. )
Going outside the bounds of an array is not automatically checked and can lead to serious program or system crashes. The size of an array is also not usually available.
Basic approaches for approaching the design of functions using arrays in parameters are:
int SumArray( int a[ ], int length) { int k, sum = 0; for (k = 0; k < length; k++) sum += a[ k ]; return sum; }
Note the need to pass the size, which is not typically required in higher-level languages where the size of an array can be queried with expressions similar to
a.size()
or
length(a)
Avoid (careless) sizeof(array)
sizeof(a)/sizeof(int)
does would not work in the context within the SumArray function to determine the size of the array
which is why I recommend avoiding it in general
even though it works in the the following case
int a[]={3,1,4,1,5,9}; int len = sizeof(a)/sizeof(int);
since the length is known at compile time, it is better style to simply provide an explicit label with the length:
const int PI_PRECISION=6; int a[PI_PRECISION]={3,1,4,1,5,9}; int len = PI_PRECISION;
Use of pointer arithmetic with addition of variable offset
int SumArray( int arr[ ], int length) { int k, sum = 0; for (k = 0; k < length; k++) sum += *(a + k); //*** return sum; }
Using pointer arithmetic with fixed pointer increment:
int SumArray( int arr[ ], int length) { int k, sum = 0; for (k = 0; k < length; k++) sum += *a++; //** implies use of dereference with auto-increment return sum; }
char hello[ ] = "Smith,Sara"; char * ptrChar; ptrChar = &(hello[6]);
What is printed from each of the following?
printf("%s\n",hello);
printf("%s\n",ptrChar );
printf("%s\n",&(hello[6]));
printf("%s\n",hello + 6);
printf("%s\n",hello[6]); //see compiler note
What is wrong with this?
ptrChar[4]='h';
Would this be OK?
ptrChar[5]='\0';
Since a pointer is a variable type, we can create an array of pointers just like we can create any array of any other type.
A common use of an array of pointers is to create an array of strings.
The declaration below creates an initialized array of strings (char *) for some baby names.
The diagram below illustrates the memory configuration.
char *name[] = { "Bobby", "Sam", "Harold"};
#include <stdio.h> #include <stdlib.h> int main(){ char * name[]={"Bobby","Jim","Harold" }; //might be stored in read-only (e.g. program) memory printf("%s",name[1]); fflush(stdout); //needed to ensure output displayed before seg fault (useful to consider when debugging projects) name[1][2]='r'; // here printf("%s",name[1]); return 0; }
Code compiles with no errors or warnings.
Code Output:
JimSegmentation fault
Command line arguments are passed to your program as parameters for main.
int main( int argc, char *argv[ ] )
argc is the number of command line arguments (and hence the size of argv)
argv is an array of strings which are the command line arguments. Note that argv[0] is always the name of your executable program.
For example, typing myprog hello world 42 at the linux prompt results in
argc = 4; argv[0] = "myprog"; argv[1] = "hello"; argv[2] = "world"; argv[3] = "42";
Note that to use argv[3]
as an integer, you must convert it from a string to an int, perhaps by using the library function atoi( )
. E.g. int age = atoi( argv[3] );