Arrays in C Language.

July 22, 2008

The C language provides a capability that enables the user to define a set of ordered data items known as an array.

Suppose we had a set of grades that we wished to read into the computer and suppose we wished to perform some operations on these grades, we will quickly realize that we cannot perform such an operation until each and every grade has been entered since it would be quite a tedious task to declare each and every student grade as a variable especially since there may be a very large number.

In C we can define variable called grades, which represents not a single value of grade but a entire set of grades. Each element of the set can then be referenced by means of a number called as index number or subscript.

Declaration of arrays:

Like any other variable arrays must be declared before they are used. The general form of declaration is:

type variable-name[50];

The type specifies the type of the elements that will be contained in the array, such as int float or char and the size indicates the maximum number of elements that can be stored inside the array for ex:

float height[50];

Declares the height to be an array containing 50 real elements. Any subscripts 0 to 49 are valid. In C the array elements index or subscript begins with number zero. So height [0] refers to the first element of the array. (For this reason, it is easier to think of it as referring to element number zero, rather than as referring to the first element).

As individual array element can be used anywhere that a normal variable with a statement such as

G = grade [50];

The statement assigns the value stored in the 50th index of the array to the variable g.
More generally if I is declared to be an integer variable, then the statement g=grades [I];
Will take the value contained in the element number I of the grades array to assign it to g. so if I were equal to 7 when the above statement is executed, then the value of grades [7] would get assigned to g.

A value stored into an element in the array simply by specifying the array element on the left hand side of the equals sign. In the statement

grades [100]=95;

The value 95 is stored into the element number 100 of the grades array.
The ability to represent a collection of related data items by a single array enables us to develop concise and efficient programs. For example we can very easily sequence through the elements in the array by varying the value of the variable that is used as a subscript into the array. So the for loop

for(i=0;i < 100;++i);
sum = sum + grades [i];

Will sequence through the first 100 elements of the array grades (elements 0 to 99) and will add the values of each grade into sum. When the for loop is finished, the variable sum will then contain the total of first 100 values of the grades array (Assuming sum were set to zero before the loop was entered)

In addition to integer constants, integer valued expressions can also be inside the brackets to reference a particular element of the array. So if low and high were defined as integer variables, then the statement

nextvalue=sorted_data[(low+high)/2]; would assign to the variable next_value indexed by evaluating the expression (low+high)/2. If low is equal to 1 and high were equal to 9, then the value of sorted_data[5] would be assigned to the next_value and if low were equal to 1 and high were equal to 10 then the value of sorted_data[5] would also be referenced.

Just as variables arrays must also be declared before they are used. The declaration of an array involves the type of the element that will be contained in the array such as int, float, char as well as maximum number of elements that will be stored inside the array. The C system needs this latter information in order to determine how much memory space to reserve for the particular array.

The declaration int values[10]; would reserve enough space for an array called values that could hold up to 10 integers. Refer to the below given picture to conceptualize the reserved storage space.

values[0]

values[1]

values[2]

values[3]

values[4]

values[5]

values[6]

values[7]

values[8]

values[9]

The array values stored in the memory.

Initialization of arrays:

We can initialize the elements in the array in the same way as the ordinary variables when they are declared. The general form of initialization off arrays is:

type array_name[size]={list of values};

The values in the list care separated by commas, for example the statement

int number[3]={0,0,0};

Will declare the array size as a array of size 3 and will assign zero to each element if the number of values in the list is less than the number of elements, then only that many elements are initialized. The remaining elements will be set to zero automatically.

In the declaration of an array the size may be omitted, in such cases the compiler allocates enough space for all initialized elements. For example the statement

int counter[]={1,1,1,1};

Will declare the array to contain four elements with initial values 1. this approach works fine as long as we initialize every element in the array.

The initialization of arrays in c suffers two draw backs
1. There is no convenient way to initialize only selected elements.
2. There is no shortcut method to initialize large number of elements.

/* Program to count the no of positive and negative numbers*/
#include< stdio.h >
void main( )
{
int a[50],n,count_neg=0,count_pos=0,I;
printf(“Enter the size of the array\n”);
scanf(“%d”,&n);
printf(“Enter the elements of the array\n”);
for I=0;I < n;I++)
scanf(“%d”,&a[I]);
for(I=0;I < n;I++)
{
if(a[I] < 0)
count_neg++;
else
count_pos++;
}
printf(“There are %d negative numbers in the array\n”,count_neg);
printf(“There are %d positive numbers in the array\n”,count_pos);
}

Multi dimensional Arrays:

Often there is a need to store and manipulate two dimensional data structure such as matrices & tables. Here the array has two subscripts. One subscript denotes the row & the other the column.
The declaration of two dimension arrays is as follows:

data_type array_name[row_size][column_size];
int m[10][20]

Here m is declared as a matrix having 10 rows( numbered from 0 to 9) and 20 columns(numbered 0 through 19). The first element of the matrix is m[0][0] and the last row last column is m[9][19]

Elements of multi dimension arrays:

A 2 dimensional array marks [4][3] is shown below figure. The first element is given by marks [0][0] contains 35.5 & second element is marks [0][1] and contains 40.5 and so on.

marks [0][0]
35.5

Marks [0][1]
40.5

Marks [0][2]
45.5

marks [1][0]
50.5

Marks [1][1]
55.5

Marks [1][2]
60.5

marks [2][0]

Marks [2][1]

Marks [2][2]

marks [3][0]

Marks [3][1]

Marks [3][2]

Initialization of multidimensional arrays:

Like the one dimension arrays, 2 dimension arrays may be initialized by following their declaration with a list of initial values enclosed in braces

Example:

int table[2][3]={0,0,01,1,1};

Initializes the elements of first row to zero and second row to 1. The initialization is done row by row. The above statement can be equivalently written as

int table[2][3]={{0,0,0},{1,1,1}}

By surrounding the elements of each row by braces.
C allows arrays of three or more dimensions. The compiler determines the maximum number of dimension. The general form of a multidimensional array declaration is:

date_type array_name[s1][s2][s3]…..[sn];

Where s is the size of the ith dimension. Some examples are:

int survey[3][5][12];
float table[5][4][5][3];

Survey is a 3 dimensional array declared to contain 180 integer elements. Similarly table is a four dimensional array containing 300 elements of floating point type.

/* example program to add two matrices & store the results in the 3rd matrix */
#include< stdio.h >
#include< conio.h >
void main()
{
int a[10][10],b[10][10],c[10][10],i,j,m,n,p,q;
clrscr();
printf(“enter the order of the matrix\n”);
scanf(“%d%d”,&p,&q);
if(m==p && n==q)
{
printf(“matrix can be added\n”);
printf(“enter the elements of the matrix a”);
for(i=0;i < m;i++)
for(j=0;j < n;j++)
scanf(“%d”,&a[i][j]);
printf(“enter the elements of the matrix b”);
for(i=0;i < p;i++)
for(j=0;j < q;j++)
scanf(“%d”,&b[i][j]);
printf(“the sum of the matrix a and b is”);
for(i=0;i < m;i++)
for(j=0;j < n;j++)
c[i][j]=a[i][j]+b[i][j];
for(i=0;i < m;i++)
{
for(j=0;j < n;j++)
printf(“%d\t”,&a[i][j]);
printf(“\n”);
}
}

All about pointers in C

July 22, 2008

Using pointers is a bit like riding a bicycle. Just when you think that you’ll never understand them—suddenly you do! Once learned the trick is hard to forget. There’s no real magic to pointers, and a lot of readers will already be familiar with their use. The only peculiarity of C is how heavily it relies on the use of pointers, compared with other languages, and the relatively permissive view of what you can do with them.

Declaring pointers

Of course, just like other variables, you have to declare pointers before you can use them. Pointer declarations look much like other declarations: but don’t be misled. When pointers are declared, the keyword at the beginning (c int, char and so on) declares the type of variable that the pointer will point to. The pointer itself is not of that type, it is of type pointer to that type. A given pointer only points to one particular type, not to all possible types. Here’s the declaration of an array and a pointer:

int ar[5], *ip;

We now have an array and a pointer (see Figure 5.3):

Diagram showing an array with four elements (labelled 'ar[0]' to            'ar[4]') and a pointer called 'ip' which does not currently            have any connection to the array.
Figure 5.3. An array and a pointer

The * in front of ip in the declaration shows that it is a pointer, not an ordinary variable. It is of type pointer to int, and can only be used to refer to variables of type int. It’s still uninitialized, so to do anything useful with it, it has to be made to point to something. You can’t just stick some integer value into it, because integer values have the type int, not pointer to int, which is what we want. (In any case, what would it mean if this fragment were valid:

ip = 6;

What would ip be pointing to? In fact it could be construed to have a number of meanings, but the simple fact is that, in C, that sort of thing is just wrong.)

Here is the right way to initialize a pointer:

int ar[5], *ip;
ip = &ar[3];

In that example, the pointer is made to point to the member of the array ar whose index is 3, i.e. the fourth member. This is important. You can assign values to pointers just like ordinary variables; the difference is simply in what the value means. The values of the variables that we have now are shown in Figure 5.4 (?? means uninitialized).

Diagram showing an array with four elements (labelled 'ar[0]' to            'ar[4]') each of which has an undefined value, and a pointer            called 'ip' which contains the address of 'ar[3]'.
Figure 5.4. Array and initialized pointer

You can see that the variable ip has the value of the expression &ar[3]. The arrow indicates that, when used as a pointer, ip points to the variable ar[3].

What is this new unary &? It is usually described as the ‘address-of’ operator, since on many systems the pointer will hold the store address of the thing that it points to. If you understand what addresses are, then you will probably have more trouble than those who don’t: thinking about pointers as if they were addresses generally leads to grief. What seems a perfectly reasonable address manipulation on processor X can almost always be shown to be impossible on manufacturer Y’s washing machine controller which uses 17-bit addressing when it’s on the spin cycle, and reverses the order of odd and even bits when it’s out of bleach. (Admittedly, it’s unlikely that anyone could get C to work an an architecture like that. But you should see some of the ones it does work on; they aren’t much better.)

We will continue to use the term ‘address of’ though, because to invent a different one would be even worse.

Applying the & operator to an operand returns a pointer to the operand:

int i;
float f;
      /* '&i' would be of type pointer to int */
      /* '&f' would be of type pointer to float */

In each case the pointer would point to the object named in the expression.

A pointer is only useful if there’s some way of getting at the thing that it points to; C uses the unary * operator for this job. If p is of type ‘pointer to something’, then *p refers to the thing that is being pointed to. For example, to access the variable x via the pointer p, this would work:

#include <stdio.h>
#include <stdlib.h>
main(){
      int x, *p;

      p = &x;         /* initialise pointer */
      *p = 0;         /* set x to zero */
      printf("x is %d\n", x);
      printf("*p is %d\n", *p);

      *p += 1;        /* increment what p points to */
      printf("x is %d\n", x);

      (*p)++;         /* increment what p points to */
      printf("x is %d\n", x);

      exit(EXIT_SUCCESS);
}

Example 5.1

You might be interested to note that, since & takes the address of an object, returning a pointer to it, and since * means ‘the thing pointed to by the pointer’, the & and * in the combination *& effectively cancel each other out. (But be careful. Some things, constants for example, don’t have addresses and the & operator cannot be applied to them; &1.5 is not a pointer to anything, it’s an error.) It’s also interesting to see that C is one of the few languages that allows an expression on the left-hand side of an assignment operator. Look back at the example: the expression *p occurs twice in that position, and then the amazing (*p)++; statement. That last one is a great puzzle to most beginners—even if you’ve managed to wrap your mind around the concept that *p = 0 writes zero into the thing pointed to by p, and that *p += 1 adds one to where p points, it still seems a bit much to apply the ++ operator to *p.

The precedence of (*p)++ deserves some thought. It will be given more later, but for the moment let’s work out what happens. The brackets ensure that the * applies to p, so what we have is ‘post-increment the thing pointed to by p’. Looking at Table 2.9, it turns out that ++ and * have equal precedence, but they associate right to left; in other words, without the brackets, the implied operation would have been *(p++), whatever that would mean. Later on you’ll be more used to it—for the moment, we’ll be careful with brackets to show the way that those expressions work.

So, provided that a pointer holds the address of something, the notation *pointer is equivalent to giving the name of the something directly. What benefit do we get from all this? Well, straight away it gets round the call-by-value restriction of functions. Imagine a function that has to return, say, two integers representing a month and a day within that month. The function has some (unspecified) way of determining these values; the hard thing to do is to return two separate values. Here’s a skeleton of the way that it can be done:

#include <stdio.h>
#include <stdlib.h>
void
date(int *, int *);     /* declare the function */

main(){
      int month, day;
      date (&day, &month);
      printf("day is %d, month is %d\n", day, month);
      exit(EXIT_SUCCESS);
}

void
date(int *day_p, int *month_p){
      int day_ret, month_ret;
      /*
       * At this point, calculate the day and month
       * values in day_ret and month_ret respectively.
       */
      *day_p = day_ret;
      *month_p = month_ret;
}

Example 5.2

Notice carefully the advance declaration of date showing that it takes two arguments of type ‘pointer to int’. It returns void, because the values are passed back via the pointers, not the usual return value. The main function passes pointers as arguments to date, which first uses the internal variables day_ret and month_ret for its calculations, then takes those values and assigns them to the places pointed to by its arguments.

When date is called, the situation looks like Figure 5.5.

Diagram showing the variables 'day' and 'month' which have undefined            values, and the pointers 'day_p' and 'month_p' which contain their            addresses.
Figure 5.5. Just as date is called

The arguments have been passed to date, but in main, day and month are uninitialized. When date reaches the return statement, the situation is as shown in Figure 5.6 (assuming that the values for day and month are 12 and 5 respectively).

Diagram showing the same variables as Figure 5.5, except that            the 'day' and 'month' now have the values '12' and '5'            respectively.
Figure 5.6. Just as date is about to return

One of the great benefits introduced by the new Standard is that it allows the types of the arguments to date to be declared in advance. A great favourite (and disastrous) mistake in C is to forget that a function expects pointers as its arguments, and to pass something else instead. Imagine what would have happened if the call of date above had read

date(day, month);

and no previous declaration of date had been visible. The compiler would not have known that date expects pointers as arguments, so it would pass the int values of day and month as the arguments. On a large number of computers, pointers and integers can be passed in the same way, so the function would execute, then pass back its return values by putting them into wherever day and month would point if their contents were pointers. This is very unlikely to give any sensible results, and in general causes unexpected corruption of data elsewhere in the computer’s store. It can be extremely hard to track down!

Fortunately, by declaring date in advance, the compiler has enough information to warn that a mistake has almost certainly been made.

Perhaps surprisingly, it isn’t all that common to see pointers used to give this call-by-reference functionality. In the majority of cases, call-by-value and a single return value are adequate. What is much more common is to use pointers to ‘walk’ along arrays.

Arrays and pointers

Array elements are just like other variables: they have addresses.

int ar[20], *ip;

ip = &ar[5];
*ip = 0;        /* equivalent to ar[5] = 0; */

The address of ar[5] is put into ip, then the place pointed to has zero assigned to it. By itself, this isn’t particularly exciting. What is interesting is the way that pointer arithmetic works. Although it’s simple, it’s one of the cornerstones of C.

Adding an integral value to a pointer results in another pointer of the same type. Adding n gives a pointer which points n elements further along an array than the original pointer did. (Since n can be negative, subtraction is obviously possible too.) In the example above, a statement of the form

*(ip+1) = 0;

would set ar[6] to zero, and so on. Again, this is not obviously any improvement on ‘ordinary’ ways of accessing an array, but the following is.

int ar[20], *ip;

for(ip = &ar[0]; ip &lt; &ar[20]; ip++)
      *ip = 0;

That example is a classic fragment of C. A pointer is set to point to the start of an array, then, while it still points inside the array, array elements are accessed one by one, the pointer incrementing between each one. The Standard endorses existing practice by guaranteeing that it’s permissible to use the address of ar[20] even though no such element exists. This allows you to use it for checks in loops like the one above. The guarantee only extends to one element beyond the end of an array and no further.

Why is the example better than indexing? Well, most arrays are accessed sequentially. Very few programming examples actually make use of the ‘random access’ feature of arrays. If you do just want sequential access, using a pointer can give a worthwhile improvement in speed. In terms of the underlying address arithmetic, on most architectures it takes one multiplication and one addition to access a one-dimensional array through a subscript. Pointers require no arithmetic at all—they nearly always hold the store address of the object that they refer to. In the example above, the only arithmetic that has to be done is in the for loop, where one comparison and one addition are done each time round the loop. The equivalent, using indexes, would be this:

int ar[20], i;
for(i = 0; i < 20; i++)
      ar[i] = 0;

The same amount of arithmetic occurs in the loop statement, but an extra address calculation has to be performed for every array access.

Efficiency is not normally an important issue, but here it can be. Loops often get traversed a substantial number of times, and every microsecond saved in a big loop can matter. It isn’t always easy for even a smart compiler to recognize that this is the sort of code that could be ‘pointerized’ behind the scenes, and to convert from indexing (what the programmer wrote) to actually use a pointer in the generated code.

If you have found things easy so far, read on. If not, it’s a good idea to skip to Section 5.3.3. What follows, while interesting, isn’t essential. It has been known to frighten even experienced C programmers.

To be honest, C doesn’t really ‘understand’ array indexing, except in declarations. As far as the compiler is concerned, an expression like x[n] is translated into *(x+n) and use made of the fact that an array name is converted into a pointer to the array’s first element whenever the name occurs in an expression. That’s why, amongst other things, array elements count from zero: if x is an array name, then in an expression, x is equivalent to &x[0], i.e. a pointer to the first element of the array. So, since *(&x[0]) uses the pointer to get to x[0], *(&x[0] + 5) is the same as *(x + 5) which is the same as x[5]. A curiosity springs out of all this. If x[5] is translated into *(x + 5), and the expression x + 5 gives the same result as 5 + x (it does), then 5[x] should give the identical result to x[5]! If you don’t believe that, here is a program that compiles and runs successfully:

#include <stdio.h>
#include <stdlib.h>
#define ARSZ 20
main(){
      int ar[ARSZ], i;
      for(i = 0; i < ARSZ; i++){
              ar[i] = i;
              i[ar]++;
              printf("ar[%d] now = %d\n", i, ar[i]);
      }

      printf("15[ar] = %d\n", 15[ar]);
      exit(EXIT_SUCCESS);
}

Example 5.3

Summary

  • Arrays always index from zero—end of story.
  • There are no multidimensional arrays; you use arrays of arrays instead.
  • Pointers point to things; pointers to different types are themselves different types. They have nothing in common with each other or any other types in C; there are no automatic conversions between pointers and other types.
  • Pointers can be used to simulate ‘call by reference’ to functions, but it takes a little work to do it.
  • Incrementing or adding something to a pointer can be used to step along arrays.
  • To facilitate array access by incrementing pointers, the Standard guarantees that in an n element array, although element n does not exist, use of its address is not an error—the valid range of addresses for an array declared as int ar[N] is &ar[0] through to &ar[N]. You must not try to access this last pseudo-element.

Qualified types

If you are confident that you have got a good grasp of the basic declaration and use of pointers we can continue. If not, it’s important to go back over the previous material and make sure that there is nothing in it that you still find obscure; although what comes next looks more complicated than it really is, there’s no need to make it worse by starting unprepared.

The Standard introduces two things called type qualifiers, neither of which were in Old C. They can be applied to any declared type to modify its behaviour—hence the term ‘qualifier’—and although one of them can be ignored for the moment (the one named volatile), the other, const, cannot.

If a declaration is prefixed with the keyword const, then the thing that is declared is announced to the world as being constant. You must not attempt to modify (change the value of) const objects, or you get undefined behaviour. Unless you have used some very dirty tricks, the compiler will know that the thing you are trying to modify is constant, so it can warn you.

There are two benefits in being able to declare things to be const.

  1. It documents the fact that the thing is unmodifiable and the compiler helps to check. This is especially reassuring in the case of functions which take pointers as arguments. If the declaration of a function shows that the arguments are pointers to constant objects, then you know that the function is not allowed to change them through the pointers.
  2. If the compiler knows that things are constant, it can often do increased amounts of optimization or generate better code.

Of course, constants are not much use unless you can assign an initial value to them. We won’t go into the rules about initialization here (they are in Chapter 6), but for the moment just note that any declaration can also assign the value of a constant expression to the thing being declared. Here are some example declarations involving const:

const int x = 1;        /* x is constant */
const float f = 3.5;    /* f is constant */
const char y[10];       /* y is an array of 10 const ints */
                        /* don't think about initializing it yet! */

What is more interesting is that pointers can have this qualifier applied in two ways: either to the thing that it points to (pointer to const), or to the pointer itself (constant pointer). Here are examples of that:

int i;                  /* i is an ordinary int */
const int ci = 1;       /* ci is a constant int */
int *pi;                /* pi is a pointer to an int */
const int *pci;         /* pc is a pointer to a constant int */
      /* and now the more complicated stuff */

/* cpi is a constant pointer to an int */
int *const cpi = &i;

/* cpci is a constant pointer to an constant int */
const int *const cpci = &ci;

The first declaration (of i) is unsurprising. Next, the declaration of ci shows that it is a constant integer, and therefore may not be modified. If we didn’t initialize it, it would be pretty well useless.

It isn’t hard to understand what a pointer to an integer and a pointer to a constant integer do—but note that they are different types of pointer now and can’t be freely intermixed. You can change the values of both pi and pci (so that they point to other things); you can change the value of the thing that pi points to (it’s not a constant integer), but you are only allowed to inspect the value of the thing that pci points to because that is a constant.

The last two declarations are the most complicated. If the pointers themselves are constant, then you are not allowed to make them point somewhere else—so they need to be initialized, just like ci. Independent of the const or other status of the pointer itself, naturally the thing that it points to can also be const or non-const, with the appropriate constraints on what you can do with it.

A final piece of clarification: what constitutes a qualified type? In the example, ci was clearly of a qualified type, but pci was not, since the pointer was not qualified, only the thing that it points to. The only things that had qualified type in that list were: ci, cpi, and cpci.

Although the declarations do take some mental gymnastics to understand, it just takes a little time to get used to seeing them, after which you will find that they seem quite natural. The complications come later when we have to explain whether or not you are allowed to (say) compare an ordinary pointer with a constant pointer, and if so, what does it mean? Most of those rules are ‘obvious’ but they do have to be stated.

Type qualifiers are given a further airing in Chapter 8.

Pointer arithmetic

Although a more rigorous description of pointer arithmetic is given later, we’ll start with an approximate version that will do for the moment.

Not only can you add an integral value to a pointer, but you can also compare or subtract two pointers of the same type. They must both point into the same array, or the result is undefined. The difference between two pointers is defined to be the number of array elements separating them; the type of this difference is implementation defined and will be one of short, int, or long. This next example shows how the difference can be calculated and used, but before you read it, you need to know an important point.

In an expression the name of an array is converted to a pointer to the first element of the array. The only places where that is not true are when an array name is used in conjunction with sizeof, when a string is used to initialize an array or when the array name is the subject of the address-of operator (unary &). We haven’t seen any of those cases yet, they will be discussed later. Here’s the example.

#include <stdio.h>
#include <stdlib.h>
#define ARSZ 10

main(){
      float fa[ARSZ], *fp1, *fp2;

      fp1 = fp2 = fa; /* address of first element */
      while(fp2 != &fa[ARSZ]){
              printf("Difference: %d\n", (int)(fp2-fp1));
              fp2++;
      }
      exit(EXIT_SUCCESS);
}

Example 5.4

The pointer fp2 is stepped along the array, and the difference between its current and original values is printed. To make sure that printf isn’t handed the wrong type of argument, the difference between the two pointers is forced to be of type int by using the cast (int). That allows for machines where the difference between two pointers is specified to be long.

Unfortunately, if the difference does happen to be long and the array is enormous, the last example may give the wrong answers. This is a safe version, using a cast to force a long value to be passed:

#include <stdio.h>
#define ARSZ 10

main(){
      float fa[ARSZ], *fp1, *fp2;

      fp1 = fp2 = fa; /* address of first element */
      while(fp2 != &fa[ARSZ]){
              printf("Difference: %ld\n", (long)(fp2-fp1));
              fp2++;
      }
      return(0);
}

Example 5.5

void, null and dubious pointers

C is careful to keep track of the type of each pointer and will not in general allow you to use pointers of different types in the same expression. A pointer to char is a different type of pointer from a pointer to int (say) and you cannot assign one to the other, compare them, substitute one for the other as an argument to a function …. in fact they may even be stored differently in memory and even be of different lengths.

Pointers of different types are not the same. There are no implicit conversions from one to the other (unlike the arithmetic types).

There are a few occasions when you do want to be able to sidestep some of those restrictions, so what can you do?

The solution is to use the special type, introduced for this purpose, of ‘pointer to void’. This is one of the Standard’s invented features: before, it was tacitly assumed that ‘pointer to char’ was adequate for the task. This has been a reasonably successful assumption, but was a rather untidy thing to do; the new solution is both safer and less misleading. There isn’t any other use for a pointer of that type—void * can’t actually point to anything—so it improves readability. A pointer of type void * can have the value of any other pointer assigned to and can, conversely, be assigned to any other pointer. This must be used with great care, because you can end up in some heinous situations. We’ll see it being used safely later with the malloc library function.

You may also on occasion want a pointer that is guaranteed not to point to any object—the so-called null pointer. It’s common practice in C to write routines that return pointers. If, for some reason, they can’t return a valid pointer (perhaps in case of an error), then they will indicate failure by returning a null pointer instead. An example could be a table lookup routine, which returns a pointer to the object searched for if it is in the table, or a null pointer if it is not.

How do you write a null pointer? There are two ways of doing it and both of them are equivalent: either an integral constant with the value of 0 or that value converted to type void * by using a cast. Both versions are called the null pointer constant. If you assign a null pointer constant to any other pointer, or compare it for equality with any other pointer, then it is first converted the type of that other pointer (neatly solving any problems about type compatibility) and will not appear to have a value that is equal to a pointer to any object in the program.

The only values that can be assigned to pointers apart from 0 are the values of other pointers of the same type. However, one of the things that makes C a useful replacement for assembly language is that it allows you to do the sort of things that most other languages prevent. Try this:

int *ip;
ip = (int *)6;
*ip = 0xFF;

What does that do? The pointer has been initialized to the value of 6 (notice the cast to turn an integer 6 into a pointer). This is a highly machine-specific operation, and the bit pattern that ends up in the pointer is quite possibly nothing like the machine representation of 6. After the initialization, hexadecimal FF is written into wherever the pointer is pointing. The int at location 6 has had 0xFF written into it—subject to whatever ‘location 6’ means on this particular machine.

It may or may not make sense to do that sort of thing; C gives you the power to express it, it’s up to you to get it right. As always, it’s possible to do things like this by accident, too, and to be very surprised by the results.

Conditions in C

July 22, 2008

Conditionals are used within the if and while constructs:

    if (conditional_1)
    {
	...block of statements executed if conditional_1 is true...
    }
    else if (conditional_2)
    {
	...block of statements executed if conditional_2 is true...
    }
    else
    {
	...block of statements executed otherwise...
    }

and any variant that derives from it, either by omitting branches or by including nested conditionals.

Conditionals are logical operations involving comparison of quantities (of the same type) using the conditional operators:

	<		smaller than
 	<=		smaller than or equal to
	==		equal to
	!=		not equal to
	>=		greater than or equal to
	>		greater than

and the boolean operators

	&& 		and
	||		or
	!		not

Another conditional use is in the switch construct:

    switch (expression)
    {
	case const_expression_1:
	{
	    ...block of statements...
            break;
	}
	case const_expression_2:
	{
	    ...block of statements...
            break;
	}
	default:
	{
	    ...block of statements..
	}
    }

The appropriate block of statements is executed according to the value of the expression, compared with the constant expressions in the case statement. The break statements insure that the statements in the cases following the chosen one will not be executed. If you would want to execute these statements, then you would leave out the break statements. This construct is particularly useful in handling input variables.

Loops in C language?

July 22, 2008

If you want to repeat the same blocks of code over and over you have two choices. Copy and paste each block or you can use a loop. In C there are three different types of loops: for, while, and do…while. Each of them has their own specific uses and syntax, and below I’ll explain all three.

For Loops

for( variable declaration/initialization; condition; variable update) {
      //Code to be repeated
}

So as you can see there are three parts needed to use a for loop. The first part is the variable declaration/initialization. Here you can either declare a new variable and assign it a value or use an existing variable. In C you can use any integer based data type (int, char, etc…), in other languages there are other types of for loops (for each…) where you can use other data types. Second, the condition tells the program to exit the loop when the conditional expression evaluates to false. Finally the variable update does just that, updates the variables. Generally you would put something like x++, x = x + 10 but if you really wanted to you could call another function to update the variable.

For loops are generally used when you know the exact number of iterations. Here’s an example of a simple for loop.

#include <stdio.h>

int main() {
     int x;
     for (x = 0; x < 10; x++) {
          printf("x = %d", x);
     }

     return (1);
}

The results of this program would be: “x = 0x = 1x = 2x = 3x = 4x = 5x = 6x = 7x = 8x = 9″ Because the program loops from 0 and exits when x = 10 (meaning it only prints up to 9). As you can see for loops are fairly easy.

While Loops

while ( condition ) {
      //Code to be repeated
}

The condition can be any boolean expression and the loop will continue while the condition evaluates to true. Here are a couple of legal conditions: (x == 1), (x != 7), or even (x ==5 || v == 7) which says execute the loop while x is equal to 5 or v is equal to 7. A while loop is basically a simple for loop with no initialization or update section. Heres an example of a while loop.

#include <stdio.h>

int main() {
     int x = 0;
     while (x > 10) {
          printf("x = %d", x);
          x++;
     }

     return (1);
}

The program above would output the same as the for loop program but with an extra line of code. Now theres one thing you need to be careful about here. Since the while loop doesn’t initialize the condition variable it must be done outside of the loop. I’ve forgotten to do this before and the results can be varied. Sometimes it’ll be fine but sometimes you’ll get results that are not as expected.

Do..While Loops

do {
      //Code to be repeated
} while (condition);

Do..While loops are essentially the same as While loops except that you know they’ll be executed at least once. The condition again can be any boolean expression and will loop while the condition is true. A while loop says “Loop while the condition is true, and execute this block of code”, a do..while loop says “Execute this block of code, and then continue to loop while the condition is true”. Here’s an example of a do..while loop.

#include <stdio.h>

int main() {
     int x = 0;
     do {
          printf("x = %d", x);
          x++;
     } while (x << 10);

     return (1);
}

The results are the same as before. One thing that people often forget about do..while loops is the semi-colon after the while (condition);

Break and Continue

These two keywords have great importance to looping. The Break command will exit the most immediately surrounding loop regardless of what the conditions of the loop are. If you are executing a loop and hit a continue statement, the loop will stop its current iteration, update itself (in the case of for loops) and begin to execute again from the top. Here’s an example of both statements.

#include <stdio.h>

int main() {
     int x;
     for (x = 0; x < 10; x++) {
          if(x == 1 || x == 4 ) {
               continue;
          }
          else if(x == 8) {
               break;
          }
          else {
               printf("x = %d", x);
          }
     }

     return (1);
}

The results of this are x = 0x = 2x = 3x = 5x = 6x = 7. As you can see both 1 and 4 are exclude and the list ends at 7. Break and Continue statements are very useful when used properly.