It is vitally important in C programming to ensure that you never use an array index which is out of range - for example, if foobar is an array declared as having 100 elements, then referring to foobar[100], or foobar[-6] would be indexing errors. For somewhat obscure and technical reasons, the C compiler cannot detect such errors, nor, in general, can they be automatically detected at run time. But the effects of such errors tend to be highly variable, very strange, and potentially very confusing. The nett result is that array indexing errors are among the most difficult to find and correct in C programming. It is very important, therefore, to get into the habit very early on of being very careful indeed about array indices.
This is straightforward enough with simple numeric indices, such as foobar[10] and so on. Although, for beginning programmers it is still very common to make errors "at the margin" - i.e. to forget that the valid indices start at zero instead of 1, or make some related mistake. So even with a simple indexing expression such as foobar[j] it is important to carefully examine how the value of j is being generated or manipulated to make sure that it cannot go out of range.
But what about more complicated index expressions, such as in:
foobar[(x + y) * z] = w + 1;
If you put something like this in a program, you need to very carefully examine what the values of x, y and z can possibly be, and try to guarantee that there are absolutely no circumstances in which the index expression might yield a value which is outside the bounds of the particular array. In practice this kind of analysis is difficult and error prone. It is better to adopt a style of defensive programming where you deliberately try to make the program itself warn you if something is going wrong. Thus, the statement above might be replaced with the following:
index = (x + y) * z; if( (index < 0) || (index >= 100) { printf("\nArray indexing bug - Controlled Crash!!!\n"); exit(); } foobar[index] = w + 1;
The operator || is called logical OR and will evaluate as
TRUE (non-zero) if either of its operands is TRUE. The
function exit() is a library function which causes the program to
immediately and unconditionally terminate.
So the effect of this code is that the index is first calculated and stored in the variable index. Then this value is compared with the valid limits. If it turns out, for whatever reason, to be invalid, a warning message is printed and the program is terminated. Of course, if the index is valid, then execution simply continues around the if statement, no message is printed, the program is not terminated, and the index is used to actually index into the array.
At first sight this defensive programming may seem like a lot of
additional effort. But to repeat: array indexing bugs do happen;
and if you have not adopted this kind of defensive programming strategy,
they are extremely difficult to track down and isolate. Thus the
small additional effort of the defensive checks is usually far
outweighed by the reduction in time (not to mention sweat and tears)
required to debug the program. Of course, some judgement will always be
required as to whether to add extra checking code - but if in doubt, it
is always safer to add it. There is also a question over what to do if
an indexing error is detected: the example above shows just about
the crudest possible reaction. There are much more sophisticated
possible approaches to reacting to unexpected events in a program (so
called "exception handling"). By all means, feel free to experiment
with formulating your own approaches to this if you think you can
improve on the very crude mechanism shown above.