0% found this document useful (0 votes)
119 views36 pages

1 Splt266 SPLTC Programming A Modern Approach 2nd Ed

pointers of c

Uploaded by

Anand Surabhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
0% found this document useful (0 votes)
119 views36 pages

1 Splt266 SPLTC Programming A Modern Approach 2nd Ed

pointers of c

Uploaded by

Anand Surabhi
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF or read online on Scribd
You are on page 1/ 36
11 114 Pointers The 11th commandment was “Thou Shalt Compute” or “Thou Shalt Not Compute"—I forget which. Pointers are one of C’s most important—and most often misunderstood—features. Because of their importance, we'll devote three chapters to pointers. In this chap- ter, we'll concentrate on the basics; Chapters 12 and 17 cover more advanced uses of pointers. We'll start with a discussion of memory addresses and their relationship to pointer variables (Section 11.1). Section 11.2 then introduces the address and indi- rection operators. Section 11.3 covers pointer assignment. Section 11.4 explains how to pass pointers to functions, while Section 11.5 discusses returning pointers from functions. Pointer Variables The first step in understanding pointers is visualizing what they represent at the machine level. In most modern computers, main memory is divided into bytes, with each byte capable of storing eight bits of information: rae Each byte has a unique address to distinguish it from the other bytes in memory. If there are n bytes in memory, we can think of addresses as numbers that range from 0 to n— 1 (see the figure at the top of the next page). An executable program consists of both code (machine instructions corre- sponding to statements in the original C program) and data (variables in the orig nal program). Each variable in the program occupies one or more bytes of memory; 241 242 Chapter 11 Pointers Address Contents 0 | or0x0021 1 |o1az0101 2 |oaii0022 3 |o1100002 4 |oi101110 n-1 | 01000011 the address of the first byte is said to be the address of the variable. In the following figure, the variable i occupies the bytes at addresses 2000 and 2001, so i's address. is 2000: 2000 2001 Here’s where pointers come in, Although addresses are represented by num- bers, their range of values may differ from that of integers, so we can’t necessarily store them in ordinary integer variables. We can, however, store them in special pointer variables. When we store the address of a variable i in the pointer variable P, We say that p “points to” i. In other words, a pointer is nothing more than an address, and a pointer variable is just a variable that can store an address. Instead of showing addresses as numbers in our examples, I'll use a simpler notation. To indicate that a pointer variable p stores the address of a variable i, I'll show the contents of p as an arrow directed toward i: Declaring Pointer Variables A pointer variable is declared in much the same way as an ordinary variable. The only difference is that the name of a pointer variable must be preceded by an aster- isk: int *p; abetact objects > 18.1 pointers o pointors > 17.6 11.2 values 4.2 11.2 The Address and Indirection Operators 243. This declaration states that p is a pointer variable capable of pointing to objects of type int. I'm using the term object instead of variable since—as we'll see in Chapter 17—p might point to an area of memory that doesn’t belong to a variable. (Be aware that “object” will have a different meaning when we discuss program design in Chapter 19.) Pointer variables can appear in declarations along with other variables: int i, j, al10], b[20], *p, *az In this example, i and 4 are ordinary integer variables, a and b are arrays of inte- gers, and p and g are pointers to integer objects. C requires that every pointer variable point only to objects of a particular type (the referenced type): /* points only to integers +} /* points only to doubles ‘“f /* points only to characters */ ‘There are no restrictions on what the referenced type may be. In fact, a pointer variable can even point to another pointer. The Address and Indirection Operators C provides a pair of operators designed specifically for use with pointers. To find the address of a variable, we use the & (address) operator. If x is a variable, then x is the address of x in memory. To gain access to the object that a pointer points to, we use the * (indirection) operator. If p is a pointer, then *p represents the object to which p currently points. The Address Operator Declaring a pointer variable sets aside space for a pointer but doesn’t make it point to an object: int *p; /* points nowhere in particular */ It’s crucial to initialize p before we use it. One way to initialize a pointer variable is to assign it the address of some variable—or, more generally, Ivalue—using the & operator: int 4, *p; By assigning the address of 4 to the variable p, this statement makes p point to i: re aa} 244 Chapter 11 Pointers It’s also possible to initialize a pointer variable at the time we declare it: int int *p = ei; We can even combine the declaration of i. with the declaration of p, provided that i is declared first: Once a pointer variable points to an object, we can use the * (indirection) operator to access what's stored in the object. If p points to i, for example, we can print the value of i as follows: printf ("%d\n", *p); print will display the value of i, not the address of 4. The mathematically inclined reader may wish to think of * as the inverse of & Applying & to a variable produces a pointer to the variable; applying * to the pointer takes us back to the original variable: in 4/ As long as p points to i, *p is analias for i. Not only does *p have the same value as i, but changing the value of *p also changes the value of 4. (#p is an Walue, so assignment to it is legal.) The following example illustrates the equiva- lence of *p and i; diagrams show the values of p and i at various points in the computation, j= *ei; /* same as j p= ai; printf("$d\n", i); /# prints 1 */ print£("¢d\n", *p); /* prints 1 */ *p = 2; ) printi("$d\n", i); /* prints 2 */ printf("sd\n", *p); /* prints 2 */ 11.3 11.3. Pointer Assignment 245 Never apply the indirection operator to an uninitialized pointer variable. If a pointer variable p hasn’t been initialized, attempting to use the value of p in any way causes undefined behavior. In the following example, the call of print £ may print garbage, cause the program to crash, or have some other effect: int *p; printf ("td", *p); /*** WRONG */ Assigning a value to *p is particularly dangerous. If p happens to contain a valid memory address, the following assignment will attempt to modify the data stored at that address: int *p; tped: / * WRONG ***/ If the location modified by this assignment belongs to the program, it may behave erratically; if it belongs to the operating system, the program will most likely crash. Your compiler may issue a warning that p is uninitialized, so pay close attention to any warning messages you get. Pointer Assignment C allows the use of the assignment operator to copy pointers, provided that they have the same type. Suppose that i, j, p, and have been declared as follows: int i, j, *p, *a7 ‘The statement p = 61; is an example of pointer assignment; the address of i is copied into p. Here’s another example of pointer assignment: q-P ‘This statement copies the contents of p (the address of i) into q, in effect making point to the same place as p: Both p and q now point to i, so we can change i by assigning a new value to either *p or * 246 Chapter 11 Pointers *D Any number of pointer variables may point to the same object. Be careful not to confuse q=p with "a= *P; The first statement is a pointer assignment; the second isn’t, as the following example shows: p= &i; gs &)5 ima; *q = *p; The assignment *q = *p copies the value that p points to (the value of 4) into the object that ¢ points to (the variable 3), 11.4 11.4 Pointersas Arguments 247 Pointers as Arguments So far, we've managed to avoid a rather important question: What are pointers good for? There’s no single answer to that question, since pointers have several distinct uses in C. In this section, we'll see how a pointer to a variable can be use- ful as a function argument. We'll discover other uses for pointers in Section 11.5 and in Chapters 12 and 17. We saw in Section 9.3 that a variable supplied as an argument in a function call is protected against change, because C passes arguments by value. This prop- erty of C can be a nuisance if we want the function to be able to modify the vari- able. In Section 9.3, we tried—and failed—to write a decompose function that could modify two of its arguments. Pointers offer a solution to this problem: instead of passing a variable x as the argument to a function, we'll supply &x, a pointer to x. We'll declare the corre- sponding parameter p to be a pointer. When the function is called, p will have the value &x, hence *p (the object that p points to) will be an alias for x. Each appear- ance of *p in the body of the function will be an indirect reference to x, allowing the function both to read x and to modify it. To see this technique in action, let’s modify the decompose function by declaring the parameters int_paxt and frac_part to be pointers. The defini- tion of decompose will now look like this: void decompose(double x, long *int_part, double *frac_part) { *int_part = (long) x; *frac_part = x - *int_part; i ‘The prototype for decompose could be either void decompose (double x, long *int_part, double *frac_part); or void decompose (double, long *, double *); We'll call decompose in the following way: decompose (3.14159, &i, 6d); Because of the & operator in front of i and d, the arguments to decompose are pointers to 4 and 4, not the values of 1 and d. When decompose is called, the value 3.14159 is copied into x, a pointer to i is stored in int_part, and a pointer to dis stored in frac_part: 248 Chapter 11 Pointers mie) 2 saasmitlsteri ease |< ‘The first assignment in the body of decompose converts the value of x to type long and stores it in the object pointed to by int_part. Since int_part points to 4, the assignment puts the value 3 in i: x| 3.14159 int_part [HI ama] frac_part =r (lets J: ‘The second assignment fetches the value that int_pat points to (the value of i), which is 3. This value is converted to type double and subtracted from x, giv- ing .14159, which is then stored in the object that Erac_part points to: [zane } ae Se When decompose returns, i and d will have the values 3 and .14159, just as we originally wanted. Using pointers as arguments to functions is actually nothing new; we've been doing it in calls of scané since Chapter 2. Consider the following example: int i; scanf("d", gi); ‘We must put the & operator in front of 4 so that scang is given a pointer to 4; that pointer tells scan£ where to put the value that it reads. Without the &, scant ‘would be supplied with the value of 4. Although scans arguments must be pointers, it’s not always true that every argument needs the & operator. In the following example, scanf is passed a pointer variable: PROGRAM 11.4 Pointers as Arguments 249 int i, *p; p= &1) sean ("&d", p); Since p contains the address of i, scanf will read an integer and store it in 4. Using the & operator in the call would be wrong: scanf("$d", &p); | /#** WRONG ***/ scanf would read an integer and store it in p instead o' Failing to pass a pointer to a function when one is expected can have disastrous results. Suppose that we call decompose without the & operator in front of i and a: decompose (3.14159, 1, a); decompose is expecting pointers as its second and third arguments, but it’s been given the values of 4 and d instead. decompose has no way to tell the difference, so it will use the values of i and das though they were pointers. When decom- pose stores values in *int_part and *frac_part, it will attempt to change unknown memory locations instead of modifying 4 and 4. If we've provided a prototype for decompose (as we should always do, of course), the compiler will let us know that we're attempting to pass arguments of the wrong type. In the case of scanf, however, failing to pass pointers often goes undetected by the compiler, making scané an especially error-prone function, Finding the Largest and Smallest Elements in an Array To illustrate how pointers are passed to functions, let's look at a function named max_min that finds the largest and smallest elements in an array. When we call max_min, we'll pass it pointers to two variables; max_min will then store its answers in these variables. max_min has the following prototype: void max _min(int af], int n, int *max, int *min); A call of max_min might have the following appearance: max_min(b, N, &big, &small); is an array of integers; N is the number of elements in b. big and small are ordinary integer variables. When max_min finds the largest element in b, it stores the value in big by assigning it to *max. (Since max points to big, an assign- ment to *max will modify the value of big.) max_min stores the smallest ele- ment of b in sma11 by assigning it to *min. To test max_min, we'll write a program that reads 10 numbers into an array, passes the array to max_min, and prints the results: 250 Chapter 11 maxmin.c Pointers Enter 10 numbers: 34 82 49 102 7 94 23 Largest: 102 Smallest: 7 Here’s the complete program: 11.50 31 /* Finds the largest and smallest elements in an array */ #include #define N 10 void max_min(int af], int n, int *max, int main (void) i int bIN], i, big, small; printf ("Enter %d numbers: ", N); for (i = 0; i < Ny i++) scanf("td", &b[i}); max_min(b, N, &big, &small); printf ("Largest: d\n", big); printf ("Smallest: %d\n", small); return 0; } void max_min(int a{], int n, int *max, { int i; *max = *min = a[0]; for (i = 1; i < nj its) { if (ali] > *max) *max = afil; else if (a[i] < *min) +min = alil; Using const to Protect Arguments int *min); int *min) When we call a function and pass it a pointer to a variable, we normally assume that the function will modify the variable (otherwise, why would the function require a pointer?), For example, if we see a statement like £ (8) ; 11.5 11.5 Pointers as Return Values = 251 in a program, we'd probably expect £ to change the value of x. It’s possible, though, that £ merely needs to examine the value of x, not change it, The reason for the pointer might be efficiency: passing the value of a variable can waste time and space if the variable requires a large amount of storage. (Section 12.3 covers this point in more detail.) We can use the word const to document that a function won't change an object whose address is passed to the function. const goes in the parameter’s declaration, just before the specification of its type: void £(const int *p) [8 WRONG **4/ of const indicates that p is a pointer to a “constant integer.” Attempting {fy *p is an error that the compiler will detect. Pointers as Return Values We can not only pass pointers to functions but also write functions that return pointers. Such functions are relatively common; we'll encounter several in Chapter 13 The following function, when given pointers to two integers, returns a pointer to whichever integer is larger: int *max(int *a, int *b) { if (*a > *b) return a; else return b; } When we call max, we'll pass pointers to two int vi pointer variable: int *p, i, dy P = max(8i, &3); During the call of max, *a is an alias for i, while *b is an alias for j. If i has a larger value than j, max returns the address of i; otherwise, it returns the address of j. After the call, p points to either 4 or 3. Although the max function returns one of the pointers passed to it as an argu- ment, that’s not the only possibility. A function could also return a pointer to an external variable or to a local variable that’s been declared static. 252 Chapter 11 AN Pointers Never return a pointer to an automatic local variable: int *f (void) int i; return &i; } The variable i doesn’t exist once f returns, so the pointer to it will be invalid. Some compilers issue a warning such as “function returns address of local vari- able” in this situati Pointers can point to array elements, not just ordinary variables. If a is an array, then &a [i] is a pointer to element i of a. When a function has an array argument, it’s sometimes useful for the function to return a pointer to one of the elements in the array. For example, the following function returns a pointer to the middle element of the array a, assuming that a has n elements: int *find_middle(int af], int n) { return &a(n/2]; } Chapter 12 explores the relationship between pointers and arrays in considerable detail. Q&A Isa pointer always the same as an address? [p. 242] Usually, but not always. Consider a computer whose main memory is divided into words rather than bytes. A word might contain 36 bits, 60 bits, or some other number of bits. If we assume 36-bit words, memory will have the following appearance: Adress Contents 2 | eox030011902010011001010012002010011 5 201001310101001110101001310101 2 | oo1za0012002220032002210012 2 | 001200001002100003001100001001200001 4 | 001101120002101210003102110002201110 n-1 | oox009011001000011002000011001000012 Q&A 253 When memory is divided into words, each word has an address. An integer usually occupies one word, so a pointer to an integer can just be an address. How- ever, a word can store more than one character, For example, a 36-bit word might store six 6-bit characters: For this reason, a pointer to a character may need to be stored in a different form than other pointers. A pointer to a character might consist of an address (the word in which the character is stored) plus a small integer (the position of the character within the word). On some computers, pointers may be “offsets” rather than complete addresses. For example, CPUs in the Intel x86 family (used in many personal com- puters) can execute programs in several modes. The oldest of these, which dates back to the 8086 processor of 1978, is called real mode. In this mode, addresses are sometimes represented by a single 16-bit number (an offset) and sometimes by two 16-bit numbers (a segment:offset pair). An offset isn’t a true memory address; the CPU must combine it with a segment value stored in a special register. To sup- port real mode, older C compilers often provide two kinds of pointers: near point- ers (16-bit offsets) and far pointers (32-bit segment:ofiset pairs). These compilers usually reserve the words near and far as nonstandard keywords that can be used to declare pointer variables Ifa pointer can point to data in a program, is it possible to have a pointer to program code? Yes. We'll cover pointers to functions in Section 17.7. It seems to me that there’s an inconsistency between the declaration int *p = &i; and the statement Why isn’t p preceded by a * symbol in the statement, as it is in the declara- tion? [p. 244] The source of the confusion is the fact that the * symbol can have different mean- ings in C, depending on the context in which it’s used. In the declaration int *p = &i; the * symbol is not the indirection operator. Instead, it helps specify the type of p, informing the compiler that p is a pointer to an int. When it appears in a statement, 254 Chapter 11 Pointers however, the * symbol performs indirection (when used as a unary operator). The statement ‘p= Gi; /#** WRONG ***/ would be wrong, because it assigns the address of 4 to the object that p points to, | not to p itself. Is there some way to print the address of a variable? [p. 244] Any pointer, including the address of a variable, can be displayed by calling the printé function and using 8p as the conversion specification, See Section 22.3 for details. The following declaration is confusing: void f(const int *p); Does this say that £ can't modify p? [p. 251] No. It says that £ can’t change the integer that p points 10; it doesn’t prevent £ from changing p itself. void £(const int *p) { int ji *p = 0; /*#* WRONG +++/ p= &j; /* legal */ Since arguments are passed by value, assigning p a new value—by making it point somewhere else—won’t have any effect outside the function, When declaring a parameter of a pointer type, is it legal to put the word const in front of the parameter’s name, as in the following example? void £(int * const p); Yes, although the effect isn’t the same as if const precedes p's type. We saw in Section 11.4 that putting const: before p's type protects the object that p points to. Putting const after p's type protects p itself: void £(int * const p) { int di *p= 0; /* legal */ P= Gj; /*** WRONG ***/ This feature isn’t used very often. Since p is merely a copy of another pointer (the argument when the function is called), there's rarely any reason to protect it. ‘An even greater rarity is the need to protect both p and the object it points to, which can be done by putting const: both before and after p’s type: Section 11.2 Section 11.3 Section 11.4 @ 2 3 Exercises 255 void £(const int * const p) { Ant 4; *p = 0; /*#* WRONG *#*/ P= &); /*** WRONG *#*/ } Exercises If 4 is a variable and p points to i, which of the following expressions are aliases for i? (a) *P (©) *&p (e) *i (g) *&i (b) &p (@) &*p (D ai (h) ati If isan int variable and p and q are pointers to int, which of the following assignments are legal? (@) p= i; @ p = say (g) p= *a; () p= &i; () p= eq; (h) *p = () &P = ai ®p-qa @ *p = ta, ‘The following function supposedly computes the sum and average of the numbers in the array a, which has length n. ave and sum point to variables that the function should mod- ify. Unfortunately, the function contains several errors; find and correct them. void avg_sum(double af], int n, double *avg, double *sum) { int i; sum = 0.0; for (i = 0; 4 < nj ist) sum += ali]; avg = sum / n; } Write the following function: void swap(int *p, int *q); When passed the addresses of two variables, swap s ables: swap(si, &}); | /* exchanges values of i and j */ ould exchange the values of the vari- Write the following function: void split_time(long total_sec, int *hr, int *min, int *sec); total_sec is a time represented as the number of seconds since midnight, hr, min, and ‘Se¢ are pointers to variables in which the function will store the equivalent time in hours (0-23), minutes (0-59), and seconds (0-59), respectively. Write the following function: void find_two_largest (int al], int n, int *largest, int *second_largest) ; 256 Chapter 11 Section 11.5 Pointers When passed an array a of length n, the function will search a for its largest and second largest elements, storing them in the variables pointed to by largest and second_largest, respectively. ‘Write the following function: void split_date(int day of_year, int year, int *month, int *day); day_of_year isan integer between | and 366, specifying a particular day within the year designated by year. month and day point to variables in which the function will store the equivalent month (1-12) and day within that month (1-31). ‘Write the following function: int “find_largest (int a{], int n); When passed an array a of length n, the function will return a pointer to the array’s largest element. Programming Projects Modify Programming Project 7 from Chapter 2 so that it includes the following function: void pay amount (int dollars, int *twenties, int *tens, int *fives, int tones); ‘The function determines the smallest number of $20, $10, $5, and $1 bills necessary to pay the amount represented by the dollars parameter. The twent ies parameter points toa Variable in which the function will store the number of $20 bills required. The tens, fives, and ones parameters are similar. Modify Programming Project 8 from Chapter 5 so that it includes the following function: void find closest flight (int desired time, int *departure time, int *arrival_time) ; This function will find the flight whose departure time is closest to desired_time (expressed in minutes since midnight). It will store the departure and arrival times of this flight (also expressed in minutes since midnight) in the variables pointed to by departure_time and arrival_time, respectively. ‘Modify Programming Project 3 from Chapter 6 so that it includes the following function: void reduce (int numerator, int denominator, int *reduced_numerator, int *reduced denominator) ; numerator and denominator are the numerator and denominator of a fraction. reduced_numerator and reduced_denominator are pointers to variables in which the function will store the numerator and denominator of the fraction once it has been reduced to lowest terms. Modify the poker. c program of Section 10.5 by moving all external variables into main and modilying functions so that they communicate by passing arguments. The analyze_hand function needs to change the straight, flush, four, three, and pairs variables, so it will have to be passed pointers to those variables. 12 12.1 Pointers and Arrays Optimization hinders evolution. Chapter 11 introduced pointers and showed how they're used as function argu- ‘ments and as values returned by functions. This chapter covers another appli for pointers. When pointers point to array elements, C allows us to perform arith- ‘metic—addition and subtraction—on the pointers, which leads to an alternative way of processing arrays in which pointers take the place of array subscripts. The relationship between pointers and arrays in C is a close one, as we'll soon see. We'll exploit this relationship in subsequent chapters, including Chapter 13 (Strings) and Chapter 17 (Advanced Uses of Pointers). Understanding the connec- tion between pointers and arrays is critical for mastering C: it will give you insight ‘© how C was designed and help you understand existing programs. Be aware, however, that one of the primary reasons for using pointers to process arrays—effi- ciency—is no longer as important as it once was, thanks to improved compilers. Section 12.1 discusses pointer arithmetic and shows how pointers can be com- pared using the relational and equality operators. Section 12.2 then demonstrates how we can use pointer arithmetic for processing array elements. Section 12.3 reveals a key fact about arrays—an array name can serve as a pointer to the array’s first element—and uses it to show how array arguments really work. Section 12.4 shows how the topics of the first three sections apply to multidimensional arrays. Section 12.5 wraps up the chapter by exploring the relationship between pointers and variable-length arrays, a C99 feature. Pointer Arithmetic We saw in Section 11.5 that pointers can point to array elements, For example, suppose that a and p have been declared as follows: 257 258 Chapter 12 Pointers and Arrays int a(10], *p; ‘We can make p point to a[0] by writing p= &a[0]; Graphically, here’s what we've just done: We can now access a [0] through p; for example, we can store the value 5 a [0] by writing *p = 5; Here’s our picture now: Making a pointer p point to an element of an array a isn’t particularly excit- ing. However, by performing pointer arithmetic (or address arithmetic) on p, we can access the other elements of a. C supports three (and only three) forms of pointer arithmetic: Adding an integer to a pointer Subtracting an integer from a pointer Subtracting one pointer from another Let’s take a close look at each of these operations. Our examples assume that the following declarations are in effect: int al10l, *p, *q, i; Adding an Integer to a Pointer Adding an integer j to a pointer p yields a pointer to the element j places after the one that p points to, More precisely, if p points to the array element a [4] , then P + J points toa [i+3] (provided, of course, that a [i+] exists). The following example illustrates pointer addition; diagrams show the values of p and q at various points in the computation. 12.1. Pointer Arithmetic 259 sal2]; oa] 3 aime els I | TIT Subtracting an Integer from a Pointer fp points to the array element a [J , then p ~ j points to a [i-3]. For example: Pp = ga(8]; “Q) Subtracting One Pointer from Another When one pointer is subtracted from another, the result is the distance (measured in array elements) between the pointers, Thus, if p points to a [i] and q points to a (3), then p - qiis equal to i - 3, For example: 260 Chapter 12 @ compound ineals 9.9 1232 Pointers and Arrays Pp &a(5); q = sal); P-q /*iis4 */ q-pr /* 4 is -4 #/ Performing arithmetic on a pointer that doesn’t point to an array element causes undefined behavior. Furthermore, the effect of subtracting one pointer from another is undefined unless both point to elements of the same array. Comparing Pointers We can compare pointers using the relational operators (<, <=, >, >=) and the equality operators (== and !=). Using the relational operators to compare two pointers is meaningful only when both point to clements of the same array, The outcome of the comparison depends on the relative positions of the two elements in the array. For example, after the assignments &a[S]; éalll; P a the value of p <= q is 0 and the value of p >= qis 1 Pointers to Compound Literals It’s legal for a pointer to point to an element within an array created by a com- Pound literal. A compound literal, you may recall, is a C99 feature that can be used to create an array with no name. Consider the following example: int *p = (int [1){3, 0, 3, 4, 1}; P points to the first element of a five-element array containing the integers 3, 0, 3, 4, and 1. Using a compound literal saves us the trouble of first declaring an array variable and then making p point to the first element of that array: int-all = {3, 0, 3, 4, 1}; int *p = éa[0]; Using Pointers for Array Processing Pointer arithmetic allows us to visit the elements of an array by repeatedly incre- menting a pointer variable. The following program fragment, which sums the ele- ments of an array a, illustrates the technique. In this example, the pointer variable 12.2 Using Pointers for Array Processing 261 Pp initially points to {0}. Each time through the loop, p is incremented; result, it points to a [1], then a[2], and so forth. The loop terminates when p steps past the last element of a. define N 10 int a(N], sum, *p; for (p = &a[0]; p < &a(N); p++) sum += *p; The following figures show the contents of a, sum, and p at the end of the first three loop iterations (before p has been incremented). At the end of the first iteration: ‘At the end of the second iteration: At the end of the third iteration: The condition p < &a[N] in the for statement deserves special mention, Strange as it may seem, it’s legal to apply the address operator to a [N], even though this element doesn't exist (a is indexed from 0 to N — 1), Using a [¥] in this fashion is perfectly safe, since the loop doesn’t attempt to examine its value, The body of the loop will be executed with p equal to €a(0], sali], &a [N-1] , but when p is equal to &a [WN], the loop terminates. ‘We could just as easily have written the loop without pointers, of course, using subscripting instead. The argument most often cited in support of pointer arithmetic is that it can save execution time. However, that depends on the implementation— some C compilers actually produce better code for loops that rely on subscripting. 262 Chapter 12 Pointers and Arrays Combining the * and ++ Operators C programmers ofien combine the * (indirection) and ++ operators in statements that process array elements. Consider the simple case of storing a value into an array element and then advancing to the next element. Using array subscripting, ‘we might write alive] = If p is pointing to an array element, the corresponding statement would be spt = 4; Because the postfix version of ++ takes precedence over *, the compiler sees this as * (pte) = The value of p++ is p. (Since we're using the postfix version of ++, p won't be incremented until after the expression has been evaluated.) Thus, the value of * (p++) will be *p—the object to which p is pointing. Of course, *p++ isn’t the only legal combination of * and ++. We could write (*p) ++, for example, which returns the value of the object that p points to, and then increments that object (p itself is unchanged). If you find this confusing, the following table may help: Expression Meaning ‘p++ or*(p++) Value of expression is *p before increment; increment p later Cp) ++ Value of expression is * before increment; increment *p later ++4por*(++p) Increment p first; value of expression is *p after increment ++*por++(*p) Increment *p first; value of expression is *p after increment All four combinations appear in programs, although some are far more common than others. The one we'll see most frequently is *p++, which is handy in loops. Instead of writing for (p = &al0}; p< &alN]; pt+) sum += *p; to sum the elements of the array a, we could write p = &al0]; while (p < sa(N]) sum += *p+4; ‘The * and ~~ operators mix in the same way as * and ++. For an application that combines * and --, let's return to the stack example of Section 10.2. The orig- inal version of the stack relied on an integer variable named top to keep track of the “top-of-stack” position in the contents array. Let’s replace top by a pointer variable that points initially to element 0 of the content s array: int *top ptr = kcontents [0]; 12.3 12.3 Using an Array Name asa Pointer 263 Here are the new push and pop functions (updating the other stack functions left as an exercise): void push(int i) { if (is_full()) stack_overflow(); else *top_ptr++ = i; int pop (void) if (is_empty()) stack _underflow() ; else return *--top_ptr; } Note that I've written *--top_ptr, not *top_ptr--, since I want pop to dec- rement top_ptr before fetching the value to which it points. Using an Array Name as a Pointer Pointer arithmetic is one way in which arrays and pointers are related, but it’s not the only connection between the two. Here’s another key relationship: The name of an array can be used as a pointer to the first element in the array. This relationship simplifies pointer arithmetic and makes both arrays and pointers more versatile. For example, suppose that a is declared as follows: int af10l; Using a as a pointer to the first element in the array, we can modify a [0]: ta=7; /* stores 7 in af0] */ We can modify a [1] through the pointer a + 1: *(at1) = 12; /* stores 12 in afi] */ In general, a + i is the same as &a [i] (both represent a pointer to element i of a) and *(a+i) is equivalent to a[i] (both represent element i itself). In other words, array subscripting can be viewed as a form of pointer arithmeti ‘The fact that an array name can serve as a pointer makes it easier to write loops that step through an array. Consider the following loop from Section 12.2: for (p = &al0]; p < salNl; p++) sum += *p; 264 Chapter 12 Pointers and Arrays idiom AN PROGRAM reverse3.c To simplify the loop, we can replace &@ [0] by a and &a [N] by a +N: for (p= a; pcat sum += *p; + pte) Although an array name can be used as a pointer, it’s not possible to assign it a new value. Attempting to make it point elsewhere is an error: while (*a I= 0) arte (/*** WRONG ***/ This is no great loss; we can always copy a into a pointer variable, then change the pointer variable: p=aj while (*p != 0) pity Reversing a Series of Numbers (Revisited) The reverse .c program of Section 8.1 reads 10 numbers, then writes the num- bers in reverse order. As the program reads the numbers, it stores them in an array. Once all the numbers are read, the program steps through the array backwards as it prints the numbers. The original program used subscripting to access elements of the array. Here's ‘a new version in which I’ve replaced subscripting with pointer arithmetic. /* Reverses a series of numbers (pointer version) */ #include #define N 10 int main(void) int aINl, *p; printf ("Enter %d numbers: ", N); for (p = a; p< a +N; p++) scant ("%d", p); printf ("In reverse order for (p= a+N- 1; p printf (" %d", *p); print£("\n"); ar p--) return 0; In the original program, an integer variable i kept track of the current position within the array. The new version replaces i with p, a pointer variable. The num- 12.3 Using an Array Name as a Pointer 265 bers are still stored in an array; we're simply using a different technique to keep track of where we are in the array. Note that the second argument to scané is p, not &p. Since p points to an array clement, it’s a satisfactory argument for scanf; &p, on the other hand, would be a pointer to a pointer to an array element. Array Arguments (Revisited) When passed to a function, an array name is always treated as a pointer. Consider the following function, which returns the largest element in an array of integers: int find_largest (int af], int n) { max = al0]; for (i = 1; i < nj i++) if (ali] > max) max = alil; return max; } Suppose that we call £ind_largest as follows: largest = find_largest (b, N); This call causes a pointer to the first element of b to be assigned to a; the array itself isn’t copied. ‘The fact that an array argument is treated as a pointer has some important con- sequences: ‘= When an ordinary variable is passed to a function, its value is copied; any changes to the corresponding parameter don’t affect the variable, In contrast, an array used as an argument isn’t protected against change, since no copy is made of the array itself. For example, the following function (which we first saw in Section 9.3) modifies an array by storing zero into each of its elements: void store_zeros(int a{], int n) b int i; for (i = 0; i If const is present, the compiler will check that no assignment to an element of a appears in the body of find_largest. = The time required to pass an array to a function doesn’t depend on the size of the array. There’s no penalty for passing. a large array, since no copy of the array is made. = An array parameter can be declared as a pointer if desired. For example, find_largest could be defined as follows: int find_largest (int *a, int n) { } Declaring a to be a pointer is equivalent to declaring it to be an array; the compiler treats the declarations as though they were identical Although declaring a parameter to be an array is the same as declaring it to be a pointer, the same isn’t true for a variable. The declaration, int a[10]; causes the compiler to set aside space for 10 integers. In contrast, the declaration int *a; causes the compiler to allocate space for a pointer variable. In the latter case, a is not an array; attempting to use it as an array can have disastrous results. For exam- ple, the assignment ta = 0; /*** WRONG ***/ will store 0 where a is pointing. Since we don’t know where a is pointing, the effect on the program is undefined. = A function with an array parameter can be passed an array “slice” —a sequence of consecutive elements. Suppose that we want £ind_largest to locate the largest element in some portion of an array b, say elements b [5], .... [14] When we call £ind_largest, we'll pass it the address of b {5} and the number 10, indicating that we want find largest to examine 10 array elements, starting at b [5]: largest = find_largest (eb(5], 10); Using a Pointer as an Array Name If we can use an array name as a pointer, will C allow us to subscript a pointer as though it were an array name? By now, you'd probably expect the answer to be yes, and you'd be right. Here’s an example: 12.4 12.4 Pointers and Multidimensional Arrays 267 define N 10 int a(N], i, sum for (i = 0; i < N; i++) sum += pli]; ‘psa; ‘The compiler treats p [i] as * (p+i), which is a perfectly legal use of pointer arithmetic, Although the ability to subscript a pointer may seem to be little more than a curiosity, we'll see in Section 17.3 that it’s actually quite useful. Pointers and Multidimensional Arrays Just as pointers can point to elements of one-dimensional arrays, they can also point to elements of multidimensional arrays. In this section, we'll explore com- mon techniques for using pointers to process the elements of multidimensional arrays, For simplicity, I'll stick to two-dimensional arrays, but everything we'll do applies equally to higher-dimensional arrays. Processing the Elements of a Multidimensional Array We saw in Section 8.2 that C stores two-dimensional arrays in row-major order; in other words, the elements of row 0 come first, followed by the elements of row 1, and so forth, An array with r rows would have the following appearance: row 0 row row r-1 We can take advantage of this layout when working with pointers. If we make a pointer p point to the first element in a two-dimensional array (the element in row 0, column 0), we can visit every element in the array by incrementing p repeatedly. As an example, let’s look at the problem of initializing all elements of a two- dimensional array to zero. Suppose that the array has been declared as follows: int a[NUM_ROWS] [NUM_COLS] ; The obvious technique would be to use nested £or loops: int row, col; for (row = 0; row < NUM ROWS; row++) for (col = 0; col < NUM_COLS; col++) alrow] [eol] = 0; 268 Chapter 12 Pointers and Arrays But if we view a as a one-dimensional array of integers (which is how it’s stored), we can replace the pair of loops by a single loop: int *p; for (p = &a[0] [0]; p <= &a[NUM_ROWS-1] [NUM_COLS-1]; p++) *p = 0; The loop begins with p pointing to a [0] [0]. Successive increments of p make it point to a{0] [2], a0] [2], a{0] [3], and so on. When p reaches [0] [NUM_COLS-1] (the last element in row 0), incrementing it again makes P point to a[1] [0], the first element in row 1. The process continues until p goes past a [NUM_ROWS-1] [NUM_COLS~1], the last element in the array. Although treating a two-dimensional array as one-dimensional may seem like cheating, it works with most C compilers. Whether it’s a good idea to do so is another matter. Techniques like this one definitely hurt program readability, but— at least with some older compilers—produce a compensating increase in effi- ciency. With many modern compilers, though, there's often little or no speed advantage, Processing the Rows of a Multidimensional Array What about processing the elements in just one row of a two-dimensional array? Again, we have the option of using a pointer variable p. To visit the elements of row 4, we'd initialize p to point to element 0 in row i in the array a: p = sali] (01; Or we could simply write p = alil; since, for any two-dimensional array a, the expression a [i] is a pointer to the first element in row i. To see why this works, recall the magic formula that relates array subscripting to pointer arithmetic: for any array a, the expression a [4] is equivalent to *(a + i). Thus, &a(i] [0] is the same as &(*(a[i] + 0)), which is equivalent to &a [i], which is the same as a [i], since the & and + operators cancel. We'll use this simplification in the following loop, which clears row i of the array a; int a(NUM_ROWS] [NUM_COLS], *p, i; for (p = alil; p < ali] + NUM_COLS; p++) *p = 0; Since a [i] is a pointer to row i of the array a, we can pass a[i] toa function that’s expecting a one-dimensional array as its argument. In other words, a function that’s designed to work with one-dimensional arrays will also work with a row belonging to a two-dimensional array. As a result, functions such as 12.4 Pointers and Multidimensional Arrays 269 find largest and store_zeros are more versatile than you might expect. Consider £ind_largest, which we originally designed to find the largest ele- ‘ment of a one-dimensional array. We can just as easily use find_largest to determine the largest element in row i of the two-dimensional array largest = find_largest(a[i], NUM_COLS); Processing the Columns of a Multidimensional Array Processing the elements in a column of a two-dimensional array isn’t as easy, because arrays are stored by row, not by column. Here’s a loop that clears column i of the array a: int a[NUM_ROWS] [NUM_CoLS], (+p) [NUM_COLS], i; for (p = &a(0]; p < &a[NUM_ROWS]; p++) (*p) Lil = 0; I've declared p to be a pointer to an array of length NUM_COLS whose elements are integers. The parentheses around *p in (*p) [NUM_COLS] are required; without them, the compiler would treat p as an array of pointers instead of a pointer to an array. The expression p++ advances p to the beginning of the next row. In the expression (*p) [i], *p represents an entire row of a, so (*p) [i] selects the element in column i of that row. The parentheses in (*p) [i] are essential, because the compiler would interpret *p [i] as *(p[i]). Using the Name of a Multidimensional Array as a Pointer Just as the name of a one-dimensional array can be used as a pointer, so can the name of any array, regardless of how many dimensions it has, Some care is required, though. Consider the following array: int a[NUM_ROMS) (NUM_COLS] ; a is not a pointer to a [0] [0]; instead, it’s a pointer to a [0]. This makes more sense if we look at it from the standpoint of C, which regards a not as a two- dimensional array but as a one-dimensional array whose elements are one- dimensional arrays, When used as a pointer, a has type int (*) [NUM_COLS] (pointer to an integer array of length NUM_COLS). Knowing that a points to a [0] is useful for simplifying loops that process the elements of a two-dimensional array. For example, instead of writing for (p = Ga[0]; p < &a[NUM ROWS]; p++) (*p) [4] = 0; to clear column 4 of the array a, we can write for (p (*p) (4) ai p < a + NUMROWS; pes) 0; 270 Chapter 12 Pointers and Arrays 12.5 varabie-length arays >2.3 Another situation in which this knowledge comes in handy is when we want to “trick” a function into thinking that a multidimensional array is really one- dimensional. For example, consider how we might use £ind_largest to find the largest element in a. As the first argument to find_largest, let's ty passing a (the address of the array); as the second, we'll pass NUM_ROWS * NUM_COLS (the total number of elements in a): largest = find largest (a, NUM_ROWS * NUM_COLS); /* WRONG */ Unfortunately, the compiler will object to this statement, because the type of a is int (*) [vuM_coLs] but find_largest is expecting an argument of type int *. The correct call is largest = find_largest (a[0], NUM_ROWS * NUM_COLS); [0] points to element 0 in row 0, and it has type int * (after conversion by the compiler), so the latter call will work correctly. Pointers and Variable-Length Arrays (C99) Pointers are allowed to point to elements of variable-length arrays (VLAS), a fea- ture of C99. An ordinary pointer variable would be used to point to an element of a one-dimensional VLA: void £(int n) { int a(n], *p. psa; When the VLA has more than one dimension, the type of the pointer de- pends on the length of each dimension except for the first. Let’s look at the two- dimensional case: void £(int m, int n) int atm) (n], (*p) (a); pea } Since the type of p depends on n, which isn’t constant, p is said to have a variably modified type. Note that the validity of an assignment such as p = a can’t always be determined by the compiler. For example, the following code will compile but is correct only if mand n are equal: int a(m] (n], (*p) (m]; pra; *Q: Q&A 271 Ifm#n, any subsequent use of p will cause undefined behavior. Variably modified types are subject to certain restrictions, just as variable- length arrays are. The most important restriction is that the declaration of a vari- ably modified type must be inside the body of a function or in a function proto- type. Pointer arithmetic works with VLAs just as it does for ordinary arrays Returning to the example of Section 12.4 that clears a single column of a two- dimensional array a, let's declare a as a VLA this time: int aml (nl; A pointer capable of pointing to a row of a would be declared as follows: int (*p) (nl; ‘The loop that clears column i is almost identical to the one we used in Section 12.4: for (p = a; p < a + mj ptt) (*p) 4] = 07 Q&A I don’t understand pointer arithmetic. If a pointer is an address, does that mean that an expression like p + j adds j to the address stored in p? [p. 258] No, Integers used in pointer arithmetic are scaled depending on the type of the pointer, If p is of type int *, for example, then p + 3 typically adds 4 x 3 top, assuming that int values are stored using 4 bytes. But if p has type double *, then p + j will probably add 8 x 5 to p, since double values are usually 8 bytes Jong. ‘When writing a loop to process an array, is it better to use array subscripting or pointer arithmetic? [p. 261] There’s no easy answer to this question, since it depends on the machine you're using and the compiler itself. In the early days of C on the PDP-11, pointer arith- metic yielded a faster program. On today’s machines, using today's compilers, array subscripting is often just as good, and sometimes even better. The bottom line: Learn both ways and then use whichever is more natural for the kind of pro- gram you're writing, Tread somewhere that 4 [a] is the same as a [4]. Is this true? Yes, it is, oddly enough. The compiler treats 4 [a] as * (i + a) , which is the same as *(a + i). (Pointer addition, like ordinary addition, is commutative.) But * (a+ i) is equivalent to afi]. Q.E.D. But please don’t use i [a] in programs unless you're planning to enter the next Obfuscated C contest. 272 Chapter 12 Pointers and Arrays a ms Why is *a the same as a [] in a parameter declaration? [p. 266] Both indicate that the argument is expected to be a pointer. The same operations on are possible in both cases (pointer arithmetic and array subscripting, in particu- lar). And, in both cases, a itself can be assigned a new value within the function. (Although C allows us to use the name of an array variable only as a “constant pointer,” there's no such restriction on the name of an array parameter.) Isit better style to declare an array parameter as *a or a []? That’s a tough one. From one standpoint, a [] is the obvious choice, since *a is ambiguous (does the function want an array of objects or a pointer to a single object). On the other hand, many programmers argue that declaring the parameter as *a is more accurate, since it reminds us that only a pointer is passed, not a copy of the array. Others switch between *a and a [1], depending on whether the func- tion uses pointer arithmetic or subscripting to access the elements of the array. (That's the approach I'll use.) In practice, *a is more common than a [], so you'd better get used to it. For what it’s worth, Dennis Ritchie now refers to the a {] notation as “a living fossil” that “serves as much to confuse the learner as to alert the reader.” C. Would it be accu- ‘We've seen that arrays and pointers are cl rate to say that they’re interchangeable? No. It’s true that array parameters are interchangeable with pointer parameters, but array variables aren't the same as pointer variables. Technically, the name of an array isn’t a pointer; rather, the C compiler converts it to a pointer when necessary. To see this difference more clearly, consider what happens when we apply the sizeof operator to an array a. The value of sizeof (a) is the total number of bytes in the array—the size of each element multiplied by the number of elements. But if p is a pointer variable, sizeof (p) is the number of bytes required to store a pointer value. ly relate You said that treating a two-dimensional array as one-dimensional works with “most” C compilers. Doesn't it work with all compilers? [p. 268] No, Some modern “bounds-checking” compilers track not only the type of @ pointer, but—when it points to an array—also the length of the array. For example, suppose that p is assigned a pointer to a [0] [0]. Technically, p points to the first element of [0], a one-dimensional array. If we increment p repeatedly in an effort to visit all the elements of a, we'll go ont of bounds once p goes past the last element of a [0]. A compiler that performs bounds-checking may insert code to check that p is used only to access elements in the array pointed to by a [0]; an attempt to increment p past the end of this array would be detected as an error. If a is a two-dimensional array, why can we pass a [0] —but not a itself—to find_largest? Don’t both a and a [0] point to the same place (the begin- ning of the array)? [p. 270] They do, as a matter of fact—both point to element a [0] [0]. The problem is that Section 12.1 Section 12.2 Section 12.3 or 3 o Exercises 273 a has the wrong type. When used as an argument, it’s a pointer to an array, but find_largest is expecting a pointer to an integer. However, a[0] has type int *, so it’s an acceptable argument for find_largest. This concern about types is actually good: if C weren't so picky, we could make all kinds of horrible pointer mistakes without the compiler noticing. Exercises ‘Suppose that the following declarations are in effect int af] = (5, 15, 34, 54, 14, 2, 52, 72}7 int *p = &a[1], *q = &a[5]; (a) What is the value of * (p+3) ? (b) What is the value of * (q-3)? (c) What is the value of q - p? (d) Is the condition p < gq true or false? (e) Is the condition *p < *q true or false? ‘Suppose that high, 1ow, and middle are all pointer variables of the same type, and that ‘ow and high point to elements of an array. Why is the following statement illegal, and how could it be fixed? middle = (low + high) / 2; ‘What will be the contents of the a array after the following statements are executed? #define N 10 int aiN] = (1, 2, 3, 4, 5, 6, 7, 8 9, 20}; int *p = gal0], *q = ea(N-1], temp; while (p

You might also like