ECE 209 Arrays and Pointers

Initialization

int V[10] = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 } ;
double X[100] = { 1.0e15, 3.7, 20 } ;

Constant arrays

When an array is declared with const, its elements cannot be changed.

const DaysOfMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } ;

Multi-dimensional arrays

Declaration

int D2[5][3] ;
double D3[2][4][6] ;

Meaning

If X is declared with int X[5][3] ; it is an array of 5 elements that are arrays of three elements that are integers.

Consequently, arrays are stored in row-major order. So, the first seven elements of X, as stored in memory, are X[0][0], X[0][1], X[0][2], X[1][0], X[1][1], X[1][2], and X[2][0].

Initialization on declaration

int A[3][2] = { { 2, 4}, {3, 9}, {4, 16 } } ;
int B[3][2] = { 2, 4, 3, 9, 4, 16 } ;

Initialization of identity matrix by for-loop

double M[1000][1000], i, j ;
for (i=0; i<1000; ++i) {
  for (j=0; j<1000; ++j) {
    if (i==j)
      M[i][j] = 1.0 ;
    else
      M[i][j] = 0.0 ;
  }
}

Other examples -- if time permits

Arrays as arguments and pointer

Arrays as constants

The array variable itself is a constant, but you can change its elements.

If V is a one-dimensional array, assigning to V is not allowed but assigning to V[5] is alloweed.

If X is a two-dimensional array, assigning to X[5] is not allowed but assigning to X[5][5] is allowed.

Arrays as pointers

C treats are array like a constant reference to its 0'th elements; that is, V and &V[0] are the same. For a two-dimensional X, X[5] and &X[5][0] are the same.

Precedence and associativity

The postfix array index has a higher precedence that the prefix address operator. Therefore, &X[5][0] is &((X[5])[0]).

Attempts at generalization

Only good for an array of size 10

void reverse(int V[10]) {
  int i, j ;
  for (i=0, j=9; i<j; ++i, ++j) {
    int t ;
    t = V[i] ;
    V[i] = V[j] ;
    V[j] = t ;
  }
}

Only good for an array size fixed at compile time

#define SIZE 209

void reverse(int V[SIZE]) {
  int i, j ;
  for (i=0, j=SIZE-1; i<j; ++i, ++j) {
    int t ;
    t = V[i] ;
    V[i] = V[j] ;
    V[j] = t ;
  }
}

Good for an array of any size

void reverse(int V[], int n) {
  int i, j ;
  for (i=0, j=n-1; i<j; ++i, ++j) {
    int t ;
    t = V[i] ;
    V[i] = V[j] ;
    V[j] = t ;
  }
}

Problems with generalization

Let's try a generic identity function.

identity(int X[][], int n) ;

This is not allowed! Suppose identity was called with two different array sizes.

int Y[300][300] ;
int Z[400][400] ;

Y[1][0] is 300 integers past the start of Y. Z[1][0] is 400 integers past the start of Z. Since the compiler can't "see" the calls, it doesn't know what code to compile.

C99 solution

identity(int n, int X[n][n]) ;

ANSI C non-solution

ANSI C allows the safe passing if arrays if all but the first dimension is fixed.

identity(int X[][100]) ;

The complications of array/pointer declarations

int R[3][5] ; R is a 3×5 array R[0][0]
int *S[5] ; S is an array of 5 pointers to integers *S[0]
int (*T)[5] ; T is a pointer to an array of 5 integers (*T)[0]
int U[][5] ; U is a pointer to an array of 5 integers U[0][0]
int **V ; V is a pointer to a pointer to an integer **V

Pointer arithmetic

If p is an integer pointer (which could just be the first element of an array), what is p[i]?

If p is an integer pointer and i is an integer, p+i is the address of the i'th integer after the one p is addressing.

So p[i] is *(p+i).

C code once thought to be fast

reverse(int *p, int *q) {
  if (p>q) {
    int *t ;
    t = p ;
    p = q ;
    q = t ;
  }
  while (p<q) {
    int t ;
    t  = *p ;
    *p = *q ;
    *q = t ;
    ++p ;
    --q ;
  }
}

It is unlikely that p++ adds one to the bits stored in p. It adds the size of an integer.

Take care with assignments

  int *p ;
  double *q ;
q = p ; illegal
*q = *p ; allowed, *p is made into a double
q = (double *)p ; allowed, but probably not a good idea

You didn't see this here...

Let's try to get identity to work in a unsavory manner. Just pass in the address of the first element of the two-dimensional array along with the size of its dimensions.

identity(&X[0][0], n) ;

Now do some really ugly pointer manipulation. Remember that every n+1 elements must be set equal to 1. Also n+1×n-1 is n2-1.

identity(int *p, int n) {
  int i, j ;
  for (i=0; i<n-1; ++i) {
    *p = 1 ;
    ++p ;
    for (j=0; j<n; ++j) {
      *p = 0 ;
      ++p ;
    }
  }
  *p = 1 ;
}

Or even slightly more obscurely.

identity(int *p, int n) {
  int i, j ;
  for (i=0; i<n-1; ++i) {
    *(p++) = 1 ;
    for (j=0; j<n; ++j)
      *(p++) = 0 ;
  }
  *p = 1 ;
}

read-only arrays

If a function does not modify an array, this should be clearly indicated in the prototype.

int sum(const int a[], int n) ;
double DotProduct(const double *X, const double *Y, int n) ;

constant vigilence

int * * const R ; Prevents assignment to R
int * const * R ; Prevents assignment to *R
const int * * R ; Prevents assignment to **R

constant warnings

Take a look at that example on pages 383 of the textbook (C Primer Plus by Stephen Prata).

int * p1;
const int * p2 ;
const int ** pp2 ;
p1 = p2 ;     /* GCC warning:  Assignment discard qualifiers */
p2 = p1 ;
pp2 = &p1 ;   /* GCC warning:  Assignment from incompatiable type */

The first assignment is forbidden because it would allow the *p2 to be modified as *p1. The second assignment is allowed because the original p2 is "lost". Assigning to *p1 cannot modify the original *p2 values.

The third assignment is also forbidden. So, why is assigning a int * to a const int * allowed, but assigning a int ** to a const int ** not allowed.

The reason is seen in this modified example.

int * p1;
const int ** pp2 ;
const int n = 13 ;
pp2 = &p1 ;   /* GCC warning:  Assignment from incompatiable type */
*pp2 = &n ;
*p1 = 10 ;

If the assignment were allowed, the constant n could be modified. Rather than attempt to understand these complex rules, it might be better to never to assign to anything with a const qualifier anywhere in its declaration.

lack of constancy

The following looks like a useful function, but can it ever be used to modify its const arguments.

void ArraySum(int *A, const int *B, const int *C, int n) {
  int i ;
  for (i=0; i<n; ++i)
    A[i] = B[i] + C[i] ;
}

It could, if the arrays overlap.

  int X[20] ;
  X[0] = 1 ;
  ArraySum(&X[1], X, X, 19) ;