Information about Statically Allocated Arrays
Information about Dynamically Allocated Arrays
Information about Dynamically Allocated 2D Arrays
int a1[100];  // declare a static array, a1, of 100 ints
char c1[50];  // declare a static array, c1, of 50 chars
// accessing array elements using indexing
for(i=0; i < 100; i++) {
  a1[i] = 0;   
}
// pass in the dimention of the array, n, to make this 
// function work for any size array
void init_array(int arr[], int n) {
  int i;
  for(i=0; i < n; i++) {
    arr[i] = i;   
  }
}
int main() {
  int array[100];
  init_array(array, 100);
	...
array [0]: base address array [1]: next address array [2]: next address ... ... array [99]: last addressThe address of bucket i is at a offset i from the base address of the array (the exact value of the address depends on the number of bytes in the type stored in each bucket):
i*(sizeof(buckettype)) + array_base_addrFor example, for an int array each int value is stored in 4 bytes, so the addresses of buckets might look something like this:
addr bucket -------------- 1230: array[0] (base address of array) 1234: array[1] 1238: array[2] ... ...
int matrix[50][100]; // declared a static 2D array of 50 rows, 100 colsTo access individual bucket values, indicate both the row and the column index:
int val = matrix[3][7]; // get int value in row 3, column 7You can think of a 2-D array as a matrix of int values indexed by row and column index values:
     0   1   2  3  ...   99
 0:
 1:
 2:             x
 .
 .
 .
49:                      y
x is stored at matrix[2][3]
y is stored at matrix[49][99]
Often nested loops are used to access individual bucket values:
for(i=0; i < 50; i++) {
  for(j=0; j < 100; j++) {
	   matrix[i][j] = 0;
  }
}
matrix [0][0]: base address matrix [0][1]: next address matrix [0][2]: next address ... ... matrix [0][99]: matrix [1][0]: matrix [1][1]: ... matrix [1][99]: matrix [2][0]: ... matrix [49][0]: matrix [49][1]: ... matrix [49][99]:
However, only the first dimension size can be unspecified in the parameter definition: you need to indicate that the parameter is a 2-D array, and can leave the size of the first dimension unspecified (for good generic design), but the second dimension is needed by the compiler to generate the correct address:
void init_matrix(int m[][100], int rows) {
  int i, j;
  for(i=0; i < rows; i++) {
    for(j=0; j < 100; j++) {
	     m[i][j] = i*j;
    }
  }
}
int main() {
  int matrix[50][100]; 
  init_matrix(matrix, 50);
However, you can use a pointer to point to each bucket value and then increment the pointer to point to the next one. When incremented, a pointer points to the very next storage location of that type (the address the pointer holds is incremented by the size of the type it points to): when incremented an int pointer point points to the very next int storage address (the address 4 bytes beyond the current one); and when incremented a pointer to a char points to the very next char storage address (the address 1 byte beyond the current one).
// accessing array elements using pointer arithmetic
char *cptr = NULL;
int *iptr = NULL;
// make the pointer point to the first bucket in the array
// the address of the start of an array is given two ways:
//   &(array[0])  the address of bucket 0 
//   array            also the address of bucket 0 
cptr = &(c1[0]);   // initialize cptr to point to the start of c1
iptr = a1;   // initialize aptr to point to the start of a1
for(i=0; i < 50; i++) {
  *cptr = 'a';
  *iptr = i;
  cptr++;  // cptr points to the next valid char address (the next bucket of c1)
  iptr++;  // iptr points to the next valid int address (the next bucket of a1)
}
// sets first matrix to: 
// row 0:   0,   1,   2, ...,  99 
// row 1: 100, 110, 120, ..., 199
//        ...
iptr = &(matrix[0][0]);
for(i=0; i < 50*100; i++) {
	*iptr = i;
	iptr++;  
}
Some examples of declaration and use:
// declare a pointer variable to point to allocated heap space
int    *p_array;
double *d_array;
// call malloc to allocate that appropriate number of bytes for the array
p_array = (int *)malloc(sizeof(int)*50);      // allocate 50 ints
d_array = (int *)malloc(sizeof(double)*100);  // allocate 100 doubles
// always CHECK RETURN VALUE of functions and HANDLE ERROR return values
// here is one example of handling an unrecoverable malloc error 
// (other error handling code is removed from this document for readability):
if(p_array == NULL) {
  printf("malloc of size %d failed!\n", 50);   // could also call perror here
  exit(1);   // or return an error to caller
}
// use [] notation to access array buckets 
// (THIS IS THE PREFERED WAY TO DO IT)
for(i=0; i < 50; i++) {
  p_array[i] = 0;
}
// you can use pointer arithmetic (but in general don't)
double *dptr = d_array;    // the value of d_array is equivalent to &(d_array[0])
for(i=0; i < 50; i++) {
  *dptr = 0;
  dptr++;
}
// (1) a single NxM malloc: really this is a large 1-dim array of int values
//     onto which we will map 2D accesses 
// 
// declaration and allocation:
int *2d_array;    // the type is a pointer to an int (the bucket type)
2d_array = (int *)malloc(sizeof(int)*N*M);
// in memory: 
//                   row 0       row 1     row 2 ...
// 2d_array -----> [ 0, 0, ... , 0, 0, ... 0, 0, ... ] 
// access using [] notation: 
//    cannot use [i][j] syntax because the compiler has no idea where the 
//    next row starts within this chunk of heap space, so must use single 
//    index value that is calculated using row and column index values and 
//    the column dimension
for(i=0; i < N; i++) {
  for(j=0; j < M; j++) {
	   2d_array[i*M +j] = 0;
  }
}
// access using pointer arithmetic (all N*M buckets are contiguous)
int *ptr = 2d_array;
for(i=0; i < N; i++) {
  for(j=0; j < M; j++) { 
     *ptr = 0; 
     ptr++;
  }
}
void init2D(int *arr, int rows, int cols) {
  int i,j;
  for(i=0; i < rows; i++) {
    for(j=0; j < cols; j++) {
	     arr[i*cols +j] = 0;
    }
  }
}
int main() {
  int *array;
  array = malloc(sizeof(int)*N*M);
  if(array != NULL) {
    init2D(arr, N, M);
  }
	...
// (2) N mallocs, one for each row, plus one malloc for array of row
//     arrays.  The bucket locations in individual rows are contiguous, 
//     but rows are not necessarily contiguous in heap space.
// 
int **2d_array; // an array of int arrays (a pointer to pointers to ints)
// declaration and allocation: 
// allocate an array of N pointers to ints
// malloc returns the address of this array (a pointer to (int *)'s)
2d_array = (int **)malloc(sizeof(int *)*N);
// for each row, malloc space for its buckets and add it to 
// the array of arrays
for(i=0; i < N; i++) {
  2d_array[i] = (int *)malloc(sizeof(int)*M);
}
// in memory: 
// 2d_array -----> | *-|-------> [ 0, 0, 0, ... , 0]  row 0
//                 | *-|-------> [ 0, 0, 0, ... , 0]  row 1
//                 |...|                               ...
//                 | *-|-------> [ 0, 0, 0, ... , 0] 
// access using [] notation:
//  2d_array[i] is ith bucket in 2d_array, which is the address of 
// a 1d array, on which you can use indexing to access its bucket value
for(i=0; i < N; i++) {
  for(j=0; j < M; j++) {
     2d_array[i][j] = 0;
  }
}
// CANNOT access every bucket with pointer arithmetic
// as an offset from the base address of bucket [0][0]
// (NOT all of the N*M buckets are contigous)
// but each row of buckets are contiguous, so can use
// pointer arithmetic within each row of values
int *ptr;
for(i=0; i < N; i++) {
  *ptr = 2d_array[i];  // set pointer to address of bucket 0 in row i
  for(j=0; j < M; j++) { 
     *ptr = 0; 
     ptr++;
  }
}
void init2D_Method2(int **arr, int rows, int cols) {
  int i,j;
  for(i=0; i < rows; i++) {
    for(j=0; j < cols; j++) {
	     arr[i][j] = 0;
    }
  }
}
int main() {
  int **array;
	// some code to allocate the row array and multiple col arrays
	...
  init2D_Method2(array, N, M);
	...