Protecting Array Contents

I l @ ve RuBoard

Protecting Array Contents

When you write a function that processes a fundamental type, such as int , you have a choice of passing the int by value or of passing a pointer-to- int . The usual rule is to pass quantities by value unless the program needs to alter the value, in which case you pass a pointer. Arrays don't give you that choice; you must pass a pointer. The reason is efficiency. If a function passed an array by value, it would have allocated enough space to hold a copy of the original array, then copied all the data from the original array to the new array. It is much quicker to pass the address of the array and have the function work with the original data.

This technique can raise problems. The reason C ordinarily passes data by value is to preserve the integrity of the data. If a function works with a copy of the original data, it won't accidentally modify the original data. But, because array-processing functions do work with the original data, they can modify the array. Sometimes that's desirable. For example, here's a function that adds the same value to each member of an array:

 void add_to(double ar[], int n, double val) {     int i;     for( i = 0; i < n; i++)         ar[i] += val; } 

Therefore, the function call

 add_to(prices, 100, 2.50); 

causes each element in the prices array to be replaced by a value larger by 2.5; this function modifies the contents of the array. It can do so because, by working with pointers, the function uses the original data.

Other functions, however, do not have the intent of modifying data. The following function, for example, is intended to find the sum of the array's contents; it shouldn't change the array. However, because ar is really a pointer, a programming error could lead to the original data being corrupted. Here, for instance, the expression ar[i]++ results in each element having 1 added to its value:

 long sum(int ar[], int n) {     int i;     long total = 0;     for( i = 0; i < n; i++)         total += ar[i]++;   /* error increments each element */     return total; } 

Using const with Formal Parameters

With K&R C, the only way to avoid this sort of error was being vigilant. With ANSI C, there is an alternative. If a function's intent is that it not change the contents of the array, use the keyword const when declaring the formal parameter in the prototype and in the function definition. For example, the prototype and definition for sum() should look like this:

 long sum(const int ar[], int n);  /* prototype  */ long sum(const int ar[], int n)   /* definition */ {     int i;     long total = 0;     for( i = 0; i < n; i++)         total += ar[i];     return total; } 

This tells the compiler that the function should treat the array pointed to by ar as though the array contained constant data. Then, if you accidentally use an expression like ar[i]++ , the compiler can catch it and generate an error message, telling you that the function attempts to alter constant data.

It's important to understand that using const this way does not require that the original array be constant; it just says that the function has to treat the array as though it were constant. Using const this way provides the protection for arrays that passing by value provides for fundamental types; it prevents a function from modifying data in the calling function. In general, if you write a function intended to modify an array, don't use const when declaring the array parameter. If you write a function not intended to modify an array, do use const when declaring the array parameter.

In the program shown in Listing 10.11, one function displays an array and one function multiplies each element of an array by a given value. Because the first function should not alter the array, it uses const . Because the second function has the intent of modifying the array, it doesn't use const .

Listing 10.11 The arf.c program.
 /* arf.c -- array functions */ #include <stdio.h> #define SIZE 5 void show_array(const double ar[], int n); void mult_array(double ar[], int n, double mult); int main(void) {   double dip[SIZE] = {20.0, 17.66, 8.2, 15.3, 22.22};   printf("The original dip array:\n");   show_array(dip, SIZE);   mult_array(dip, SIZE, 2.5);   printf("The dip array after calling mult_array():\n");   show_array(dip, SIZE);   return 0; } void show_array(const double ar[], int n) {   int i;   for (i = 0; i < n; i++)        printf("%8.3f ", ar[i]);   putchar('%\n'); } /* multiplies each array member by the same multiplier */ void mult_array(double ar[], int n, double mult) {   int i; Z   for (i = 0; i < n; i++)        ar[i] *= mult; } 

Here is the output:

 The original dip array:   20.000   17.660    8.200   15.300   22.220 The dip array after calling mult_array():   50.000   44.150   20.500   38.250   55.550 

Note that both functions are type void . The mult_array() function does provide new values to the dip array, but not by using the return mechanism.

More About const

Earlier, you saw that you can use const to create symbolic constants:

 const double PI = 3.14159; 

That was something you could do with the #define directive also, but const lets you create constant arrays, constant pointers, and pointers to constants.

Suppose you use an array to store values that should not be altered . You can use the const keyword to protect that array:

 #define MONTHS 12 const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31}; 

If your code subsequently tries to alter the array, you'll get a compile-time error message:

 days[9] = 44;    /* compile error */ 

Pointers to constants can't be used to change values. Consider the following code:

 double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double * pd = rates;    /* pd points to beginning of the array */ 

The second line of code declares that pd points to a const double . That means you can't use pd to change pointed-to values:

 *pd = 29.89;      /* not allowed */ pd[2] = 222.22;   /* not allowed */ rates[0] = 99.99; /* allowed because rates is not const */ 

Whether you use pointer notation or array notation, you are not allowed to use pd to change the value of pointed-to data. Note, however, that because rates was not declared as a constant, you can still use rates to change values. Also note that you can make pd point somewhere else:

 pd++;       /* make pd point to rates[1] -- allowed */ 

A pointer-to-constant is normally used as a function parameter to indicate that the function won't use the pointer to change data. For example, the show_array() function from Listing 10.11 could have been prototyped this way:

 void show_array(const double *ar, int n); 

There are some rules you should know. First, it's valid to assign the address of either constant data or non-constant data to a pointer-to-constant:

 double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double locked[4] = {0.08, 0.075, 0.0725, 0.07}; const double * pd = rates;    /* valid */ pd = locked;                  /* valid */ pd = &rates[3];               /* valid */ 

Only the addresses of non-constant data can be assigned to regular pointers, however:

 double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double locked[4] = {0.08, 0.075, 0.0725, 0.07}; double * pd = rates;          /* valid     */ pd = locked;                  /* not valid */ pd = &rates[3];               /* valid     */ 

This is a reasonable rule. Otherwise, you could use the pointer to change data that was supposed to be constant.

A practical consequence of these rules is that a function such as show_array() can accept the names of regular arrays and of constant arrays as actual arguments because either can be assigned to a pointer-to-constant:

 show_array(rates, 5);    /* valid */ show_array(locked, 4);   /* valid */ 

A function like mult_array() , however, can't accept the name of a constant array as an argument:

 mult_array(rates, 5, 1.2);    /* valid       */ mult_array(locked, 4, 1.2);   /* not allowed */ 

Therefore, using const in a function parameter definition not only protects data, it also allows the function to work with arrays that have been declared const .

There are more possible uses of const . For example, you can declare and initialize a pointer so that it can't be made to point elsewhere. The trick is the placement of the keyword const :

 double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; double * const pd = rates;    /* pd points to beginning of the array */ pd = &rates[2];               /* not allowed                         */ *pd = 92.99;                  /* ok -- changes rates[0]              */ 

Such a pointer can still be used to change values, but it can point only to the location originally assigned to it.

Finally, you can use const twice to create a pointer that can neither change where it's pointing nor change the value to which it points:

 double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5}; const double * const pd = rates; pd = &rates[2];               /* not allowed */ *pd = 92.99;                  /* not allowed */ 
I l @ ve RuBoard


C++ Primer Plus
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2000
Pages: 314
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net