Arrays and Pointers
R. Inkuluhttp://www.iitg.ac.in/rinkulu/
(Arrays and Pointers) 1 / 41
Motivation with Example usage
int func(void) {
int states[5], i; //defining
for (i=0; i<5; i++)
states[i]=i; //initializing
for (i=0; i<5; i++)
printf("%d", states[i]); //accessing the value
return 0;
}
• array is used in storing multiple objects of same type
• objects in the array are indexed starting from 0
• array scope is limited to the function in which it is defined
(Arrays and Pointers) 2 / 41
sizeof() operator
int func(void) {
int a[5];
printf("%d, %d, %d",
sizeof(int), sizeof(a[3]), sizeof(a));
//outputs 4, 4, 20
}
• sizeof(object or type name) is a compile-time unary operator to getsize of an object or a type in number of bytes
(Arrays and Pointers) 3 / 41
Two-dimensional arrays
int func(void) {
int a[2][3], i, j; //defining
for (i=0; i<2; i++)
for (j=0; j<3; j++)
a[i][j] = i+j; //initializing
for (i=0; i<2; i++)
for (j=0; j<3; j++)
printf("%d, ", a[i][j]); //accessing the value
printf("%d, %d, %d", sizeof(a),
sizeof(a[1]), sizeof(a[1][2]));
//prints 24, 12, 4
return 0;
}
(Arrays and Pointers) 4 / 41
Three-dimensional arrays
int func(void) {
int a[2][3][4], i, j, k; //defining
for (i=0; i<2; i++)
for (j=0; j<3; j++)
for (k=0; k<4; k++)
a[i][j][k] = i+j; //initializing
for (i=0; i<2; i++)
for (j=0; j<3; j++)
for (k=0; k<4; k++)
printf("%d, ", a[i][j][k]); //accessing
printf("%d, %d, %d, %d", sizeof(a), sizeof(a[1]),
sizeof(a[0][2]), sizeof(a[1][0][2]));
//prints ?
return 0;
}
(Arrays and Pointers) 5 / 41
Address vs Value
int a, b[2];
a = 10;
printf("%d, %p", a, &a);
//prints 10, 0xbfeac818
b[0] = 22; b[1] = 24;
printf("%d, %p, %d, %p", b[0], &b[0], b[1], &b[1]);
//prints 22, 0xbfeac820, 24, 0xbfeac824
• memory for arrays is allocated contiguously
(Arrays and Pointers) 6 / 41
Arrays are stored in row-major order
int c[2][3], i, j;
for (i=0; i<2; i++)
for (j=0; j<3; j++) {
printf("%p, ", &c[i][j]);
}
//prints 0xbfd8f650, 0xbfd8f654, 0xbfd8f658,
//0xbfd8f65c, 0xbfd8f660, 0xbfd8f664,
• contiguous memory is allocated for the first row, immediatelythereafter for the second row, etc.,
(Arrays and Pointers) 7 / 41
Arrays are stored in row-major order (cont)
int a[2][5][3], i, j, k;
for (i=0; i<2; i++)
for (j=0; j<5; j++)
for (k=0; k<3; k++)
printf("%p, ", &a[i][j][k]);
//observed: difference between any two successive
//addresses printed is four
(Arrays and Pointers) 8 / 41
Initializing arrays while defining
int a[2][5] = {
{0, 11, 22, 33, 44},
{1, 12, 23, 34, 45}
};
for (int i=0; i<2; i++)
for (int j=0; j<5; j++)
printf("%d, ", a[i][j]);
//prints 0, 11, 22, 33, 44, 1, 12, 23, 34, 45,
(Arrays and Pointers) 9 / 41
Introducing pointers
int x = 1, y = 2, z[2];
int *p = NULL;
//p is a pointer to int, initialized with NULL
p = &x; //p points to x
y = *p; //y is 1
*p = 0; //x is now 0
p = &z[1]; //p points to z[1]
*p = 5; //z[1] is 5
*p = *p + 5; //z[1] is 10
• pointer is jut a variable that saves the address of a memorylocation that contains a specific type
• when p points to a typeA, p is termed as a pointer to typeA
• dereferencing pointer p is denoted with ∗p
(Arrays and Pointers) 10 / 41
Incrementing a pointer
p z[0] z[1] z[2] z[3]
p z[0] z[1] z[3]q
z[2]
int z[4];
int *p = &z[0];
*(p+1) = 7; //*(p+(1*sizeof(int))) = 7
//i.e., z[1] has 7
p += 2; //p = p + (2*sizeof(int))
*p = 5; //z[2] has 5
*p = *p + 3; //z[2] has 8
*p += 2; //z[2] has 10
++(*p); //z[2] has 11
int *q = p; //q also points to z[2]
*q = 89; //z[2] has 89
• when p is a pointer to typeA, expression p+ i increments p byi ∗ sizeof(typeA)
(Arrays and Pointers) 11 / 41
Ways to access an array
z+0&z[0]
z[0] z[1] z[2] z[3]
z+2&z[2]
&z[1]z+1
&z[3]z+3
p
p+1
p+2
p+3
*(p+0) *(p+1) *(p+2) *(p+3)p[0] p[1] p[2] p[3]
&p[1]&p[2]
&p[3]
p+0 &p[0]
double z[4], *p = z;
...
z[3] = 10;
...
• ex. compiler replaces z[i] with ∗(z + (i ∗ sizeof(double)))same is true in case of multi-dimensional arrays
(Arrays and Pointers) 12 / 41
Array name is a pointer
float z[4];
printf("%d, %d, %d", sizeof(p),
sizeof(float), sizeof(*p)); //prints 4, 4, 4
printf("%p, %p, ", z, &z[0]);
//identical values are printed
printf("%p, %p, ", z+1, &z[0]+1);
//identical values (&z[1]) are printed
• name of an array (z) is a synonym for the address of z[0];further, &z[i] + j denotes the address of z[i+ j]
• however, statements like z = p and z++ are illegal as z is not avariable
(Arrays and Pointers) 13 / 41
Array name is a pointer (cont)
float z[4];
float *p = &z[0], *q;
q = z;
printf("%p, %p, %p", z, p, q);
//identical values are printed
*(z+2) = 20; //z[2] has 20
q[3] = 52; //z[3] has 52
*(q+3) = 58; //z[3] has 58
(Arrays and Pointers) 14 / 41
More on row-major order of arrays
z[0]
z[1]
z[2]
z[0], z[1], z[2] shown in figure do not occupy additional space
int z[3][4], i, j;
printf("%p, %p, %p, ", z[0], z[1], z[2]);
//prints addresses of three rows i.e, &z[i][0]
printf("%p, %p", &z[2]+1, &z[1]+2);
//prints identical values
printf("%p, %p", z[2]+1, &z[2][1]);
//prints identical values
• two-dimensional array is an array of rows:z[i] is the name of the array that comprises the ith row;and, &z[i] is the address of the first row
(Arrays and Pointers) 15 / 41
Passing arguments to functions (review)
void func(int y) {
//y has 10
y = 15;
//y has 15
return;
}
int main(void) {
int x=10;
func(x);
printf("%d", x); //prints 10 !
return 0;
}
• function parameters are passed by value
(Arrays and Pointers) 16 / 41
Simulating pass by reference with pass by value
void func(int *y) {
//now the value of y is the address of x
//(x is a variable defined in main)
*y = 15;
}
int main(void) {
int x=10;
func(&x); //address of x is passed to func
printf("%d", x); //prints 15
}
(Arrays and Pointers) 17 / 41
Passing arguments to functions: swap func
void swap(int v, int w) {
int tmp = v;
v = w;
w = tmp;
}
int main(void) {
int x=10, y=15;
swap(x, y);
printf("%d, %d", x, y); //prints 10, 15
}
(Arrays and Pointers) 18 / 41
Simulating pass by reference with pass by value:swap func
void swap(int *v, int *w) {
int tmp = *v;
*v = *w;
*w = tmp;
}
int main(void) {
int x=10, y=15;
swap(&x, &y);
printf("%d, %d", x, y); //prints 15, 10
}
(Arrays and Pointers) 19 / 41
Passing one-dimensional arrays to functions
int countNonZeros(int v[], int len) {
//v is a pointer to array x (defined in main), and
//len has 3
int count = 0, i;
for (i=0; i<len; i++)
if (v[i] != 0)
++count;
return count;
}
int main(void) {
int x[3] = {20, 30, 0};
int k = countNonZeros(x, 3);
printf("%d", k); //prints 2
}
• change of values of any element of v in countNonZeros gets reflectsin array x of main
(Arrays and Pointers) 20 / 41
Passing one-dimensional arrays to functions: analternative
int countNonZeros(int *v, int len) {
//v has &x[0], and len has 3
int count = 0, i;
for (i=0; i<len; i++)
if (v[i] != 0)
++count;
return count;
}
int main(void) {
int x[3] = {20, 30, 0};
int k = countNonZeros(x, 3);
printf("%d", k); //prints 2
}
• change of values of any element of v in countNonZeros gets reflectsin array x of main
(Arrays and Pointers) 21 / 41
Passing partial arrays to functions
int countNonZeros(int *v, int len) {
//v has &x[1], and len has 2
int count = 0, i;
for (i=0; i<len; i++)
if (v[i] != 0) //equivalently, if (*(v+i) != 0)
++count;
return count;
}
int main(void) {
int x[3] = {20, 30, 0};
int k = countNonZeros(&x[1], 2);
printf("%d", k); //prints 1
}
(Arrays and Pointers) 22 / 41
Passing two-dimensional arrays to functions
void func(double b[][4], int numRows, int numCols)
//equivalently, formal parameter can be b[3][4]
{
...
b[2][3] = 78;
//lvalue is (&b[0][0]+(2*4+2)*sizeof(double))
... }
int main(void) {
double a[3][4];
func(a, 3, 4);
return 0; }
• formal parameter need to include the number of columns; thenumber of rows are irrelevant: since what is passed is, a pointer toan array of rows, where each row is an array of [4] integers (ex. toaccess a particular element, compiler need to be able to calculatethe offset from the beginning of array b)
(Arrays and Pointers) 23 / 41
Passing multi-dimensional arrays to functions:an alternative
Homework!
(Arrays and Pointers) 24 / 41
Dynamic Memoryp
double *p = (double *) malloc(count*sizeof(double));
//allocates count*sizeof(double) bytes contiguously;
//p has the address of first byte of those allocated
int i;
for (i=0; i<count; i++) {
p[i] = i*10;
printf("%lf, ", p[i]);
}
//prints 0.000000, 10.000000, 20.000000, 30.000000,
free(p); //frees contigous memory referred by p
• useful when the number of objects to be allocated is not known atcompile-time
• system maintains a table of dynamically allocated memory blocksand their corresp. address ranges
(Arrays and Pointers) 25 / 41
Dynamic vs non-dynamic memory allocation
z p z[0] z[1] z[2] z[3]
double z[4]; //z is from stack
double *p = (double*) malloc(count*sizeof(double));
//p is from stack, and memory block is from heap
...
free(p);
• memory for auto variables (including arrays) comes from stack,
whereas the dynamic memory comes from heap
of the process address space
(Arrays and Pointers) 26 / 41
Pointer arithmatic
double *p = (double*)
malloc(count*sizeof(double));
double *q = NULL;
... //denotes irrelevant
//stmts
//i’m here (1)
p += 2;
...
q = p;
//i’m here (2)
...
free(p);
at (1):
p z[0] z[1] z[2] z[3]
q
NULL
at (2):
p z[0] z[1] z[3]q
z[2]
• pointer arithmatic is same whether the memory referred by apointer is either from the stack or heap
(Arrays and Pointers) 27 / 41
malloc and free pairs
double *p=NULL; int *q = NULL;
...
p = (double*) malloc(countA*sizeof(double));
...
q = (int*) malloc(countB*sizeof(int));
...
free(p);
...
free(q);
• in any program, malloc and free must exist as pairs
avoiding free call corresp. to any malloc call causes memory leak
• malloc and free corresp. to a block of memory
not necessarily be invoked in the same function
not necessarily bracketed
(Arrays and Pointers) 28 / 41
Dynamic memory: advantages anddisadvantages
advantages:
• useful when the number of objects to be allocated is not known atcompile-time
• gives flexibility to allocate and deallocate memory based on theneed; careful user of this primitive can extract benefits
disadvantages:
• slow due to free/allocated heap space maintainance involvedtogether with the defragmentation overhead
due to intermittent mallocs and frees
• forgetting to deallocate memory causes memory leak
(Arrays and Pointers) 29 / 41
Non-dynamic memory: advantages anddisadvantages
advantages:
• compiler will deallocate the memory automatically for all theglobal, static, and local memory that it allocated: no memory leakhassles
disadvantages:
• Memory allocation and deallocation are not in the control of user
(Arrays and Pointers) 30 / 41
Avoid dangling pointers after freeing
...
double *p = (double*)
malloc(count*sizeof(double));
...
//i’m here (1)
free(p);
...
//i’m here (2)
p = NULL;
//i’m here (3)
...
at (1):
p
at (2):
p
dangling
at (3):
p
NULL
• avoid dangling pointers by resetting pointer variable value afterthe free
(Arrays and Pointers) 31 / 41
Returning pointers from functionsdouble *func(int count) {
double *p = NULL;
...
p = (double*) malloc(count*sizeof(double));
...
return p; }
void func1(int countA) {
int count; double *q = NULL;
...
q = func(count);
...
free(q); //freeing heap memory allocated in func
...
return; }
• heap memory referred by a pointer may require to be freed by thecaller (precise protocol need to be defined)
• does not make sense to return the address of a local variable (ex.array) from a function
(Arrays and Pointers) 32 / 41
Returning pointers from functions (cont)
void *malloc(size_t sizeInBytes) {
...
}
• void func() returns nothingwhereas void* func() returns pointer to a block of memory (couldbe NULL too) that can contain objects of any type
(Arrays and Pointers) 33 / 41
Few useful dynamic memory related functionsfrom stdlib.h
void *malloc(size t numBytes)
void free(void *p)
void *calloc(size t numObj, size t sizeOfAObject)
— same as malloc but zeros the memory allocated
void *realloc(void *oldMem, size t numBytes)
— resizes and where necessary relocates the block pointed by p; moves the
contents of *p to the new location; frees the old memory
void *memcpy(void *to, void *from, int numBytes);
— cannot handle the overlap
void *memmove(void *to, void *from, int numBytes);
— same as memcpy but handles the overlap
(Arrays and Pointers) 34 / 41
Array of pointers to varying sized arrays
STACK HEAP
buf[0]
buf[1]
buf*(buf[0]+2) a.k.a. buf[0][2]
double *buf[2];
buf[0] = (double*) malloc(countA*sizeof(double));
buf[1] = (double*) malloc(countB*sizeof(double));
printf("%d, %d ", sizeof(buf[0]), sizeof(buf));
//prints sizeof(ptr), 2*sizeof(ptr)
...
free(buf[1]);
free(buf[0]);
• buf is an array[2] of pointers, each entry of which points to a blockof memory that contains objects of type double
(Arrays and Pointers) 35 / 41
Array of pointers to varying sized arrays (cont)
STACK
buf[0]
buf[1]
buf
STACK
z
*(buf[0]+2) or *(buf[1])
double z[4];
double *buf[2];
buf[0] = &z[0];
buf[1] = &z[2];
printf("%d, %d ", sizeof(buf[0]), sizeof(buf));
//prints 4, 8
...
//no need of free calls
(Arrays and Pointers) 36 / 41
Array of pointers to fixed size arrays
HEAP
*(buf+2)
*(buf+1)
*(buf+0)
(*(buf+1))[1]
buf
STACK
double (*buf)[2] =
(double (*)[2])malloc(count*sizeof(double [2]));
printf("%d, %d, %d, %d",
sizeof(double), sizeof(buf[0]),
sizeof(buf[2]), sizeof(buf));
//prints 8, 16, 16, 4 when count is 3
...
free(buf);
• buf points to count number of array[2]s of doubles
in other words, buf[0] is the zeroth arry[2]; buf[1] is the firstarray[2]; etc.,
(Arrays and Pointers) 37 / 41
Passing two-dimensional arrays to functions: analternative
void func(double (*b)[4])
//b points to contiguous sequence of double [4]s
{
...
}
int main(void) {
double a[3][4]; //a is an array[3] of array[4]s
func(a);
}
• as mentioned in the past, formal parameter must include thenumber of columns; number of rows are irrelevant
(Arrays and Pointers) 38 / 41
Passing multi-dimensional arrays to functions:an alternative
Homework!
(Arrays and Pointers) 39 / 41
Memory layouts of various declarations (review)
(i) double a[2];
double *p = &a[0];
(ii) double *p = (double *) malloc(count*sizeof(double));
(iii) double a[2][3];
(iv) double *a[2];
a[0] = (double *) malloc(countA*sizeof(double));
a[1] = (double *) malloc(countB*sizeof(double));
(v) double *a[2], b[2][3];
a[1] = b[0];
(vi) double (*b)[2];
b = (double (*)[2]) malloc(count*sizeof(double [2]));
(vii) double (*b)[2], c[4][2];
b = c;
homework: using these notions, engineer few more declarations
(Arrays and Pointers) 40 / 41
to be continued ...
(Arrays and Pointers) 41 / 41