Pointers to Pointers in C Why and How

  • Slides: 20
Download presentation
Pointers to Pointers in C Why and How This presentation makes extensive use of

Pointers to Pointers in C Why and How This presentation makes extensive use of Power. Point’s animation capabilities. Many of the Andincomprehensible some suggestions for (impossibly some good software slides are cluttered) practices appear along theand waydisappear as unless the development dynamic elements scripted in the animation. If you are reading these words, you’re not in Power. Point’s slideshow mode, which is necessary to see the animation. Hit function key F 5 to enter the slideshow (animation) mode.

The Dreaded Double Asterisk in C (Not So Dreadful, Actually) n First, a review

The Dreaded Double Asterisk in C (Not So Dreadful, Actually) n First, a review of how and why pointers are used in C to create functions that produce side-effects n Then a discussion and example of why that can lead to declarations with two asterisks in them, like struct stack_item **ptr_to_the_top_ptr; 2

Writing Functions That Produce Side Effects n One of the features of C that

Writing Functions That Produce Side Effects n One of the features of C that requires a little getting used to is that changing the value stored in a local variable inside a function has absolutely no effect on anything outside the function n Even if the local variable or parameter has the same name as another variable defined outside the function (which is a baaad programming practice!) there is absolutely no relationship between them since they have different scope n But sometime we may want one function to change the value stored in a variable defined in another function (e. g. , scanf) n That’s called “creating a side effect” (I’ll explain why later) and it requires using pointer variables 3

Review of Passing Arguments to Functions in C Remember: the only way C passes

Review of Passing Arguments to Functions in C Remember: the only way C passes argument values into functions is by copying (formally known as pass-by-value) param 1 int funct 1(int param 1) When it compiles a function { definition, the compiler /* function body */ generates code that causes each } After allhas arguments have evaluated separate execution of the After the function’s execution completed (anbeen. . . andstorage theor resultant value is the is and the resultant values copied into function to request new cells … and all local to the function explicit return statement is executed when. . . this temporary integer cell gets So each. . . time functionfor call the this expression each copied into the cell of— the int main() corresponding parameter cells the value for the temporary storage of the destroyed (unless the programmer has the execution flow hits the ‘}’ at the end of the created/allocated (or “invocation” or “activation”) of argumentofinfirst theargument function parameter corresponding by static (ifparameters any) into the for { function’s before declared that it is tocell be function definition) …. . . explicitly funct 1 iscall executed is then evaluated. . . value position to the that first parameter, of second argument the statements inargument the body of storage and hence not to be destroyed) int x; justdefinition got evaluated into cellthe for second parameter, and so on start executing while (…) 72 x = funct 1(36*2 ). . . — then the statements inside the body of the function get executed } … the returned value is substituted for the expression consisting of the function call (the function’s name followed by the argument list in parentheses) … 4

Passing Pointer Values (Addresses) as Arguments to a Function n The compiler’s mechanism for

Passing Pointer Values (Addresses) as Arguments to a Function n The compiler’s mechanism for passing a pointer value into a function is exactly the same as for any other type of value: n When the function call is executed, a parameter cell to hold a pointer value is created n An argument gets evaluated to yield a value n The resultant value (an address, which we illustrate by an arrow, but it’s really just a bit pattern like any other value) gets copied into the correct parameter cell n But what the programmer can do with a copied address/pointer value (i. e. , de-reference it!) makes for some powerful new programming capabilities 5

Passing Pointer Values (Addresses) as Arguments to a Function (cont’d) Note that what’s stored

Passing Pointer Values (Addresses) as Arguments to a Function (cont’d) Note that what’s stored here is in reality just a The compiler’s mechanism forpattern passing pointer value into binary bit thatawill be interpreted as ana address, not anything symbolic like ‘&x’, so function is exactly the sameit’s as for any other type of value even if there were a local variable named x here in When this statementfunct 1 (and. . that’s the value in main ’s x is a very bad programming gets executed. . . so code funct 1 has quite practice, since itchanged; makes the potentially Now when this statement gets confusing) this produced cell woulda contain the address for side-effect executed. . . int main() x the x back in the main function int funct 1(int *param 1) { 17 int x = 5; 5 { param 1 • • • &x. . . funct 1(&x) *param 1 = 17; }. . . this cell gets created and initialized }. . . so this cell now contains the. . . then argument andthis its value is an address, or pointer address (points to) the. . . into cell this cell. . . gets evaluated. . . toofthis value, pointing cell. . . This address (pointer) Whenever this function call. . . this type) named ‘x’(integer back in pointer the main value is then copied. . . gets executed. . . cell gets created … function • • • 6

Summary of Values and Side Effects n It’s called a side-effect because the original

Summary of Values and Side Effects n It’s called a side-effect because the original “purist” view of functions came from mathematics, where sin(x) is just a mathematical expression designating a value, not a programming entity that might actually do something n. A “pure” function in C just computes and returns a value but doesn't affect anything outside of itself during its computation (i. e. , it has no side effects) n Side effects in the calling function occur only if you specifically program for them using pointers — but the mechanism for passing pointer/address values as arguments to a function is the same as for passing any other kind of value 7

Summary of Programming for Side Effects n To make a function able to change

Summary of Programming for Side Effects n To make a function able to change the value stored in a variable declared in a function that calls it, the calling function must supply the address of the variable it wants changed as an argument to the function call n The called function must: 1. Have a pointer of the correct pointer type as the parameter receiving a copy of the address whose content it's supposed to change 2. Dereference that parameter to make the change back in the calling function * int funct 1(int param 1) { param 1 =. . . ; } * int main() { int x; . . . funct 1(&x). . . } 8

Summary of Values and Side Effects (cont'd) n Don’t be confused by the jargon:

Summary of Values and Side Effects (cont'd) n Don’t be confused by the jargon: sometimes a side-effect is the whole point of the function, e. g. , scanf, which we want to read some keyboard input, convert the ASCII codes into some other value (e. g. , a floating point value) according to the format descriptor, and store the resultant value not in a variable local to the scanf function but in a variable declared in the function that called it; which is, by definition, a side-effect, even though it’s the whole point of the scanf function in the first place n “Side effect” is technical jargon here, not a moral judgment n Remember: the purpose of a function can be to compute and return a value or produce side effects, or both 9

Pointers to Pointers n Suppose we want a function to change the value of

Pointers to Pointers n Suppose we want a function to change the value of a pointer variable declared in a calling function? For example, let’s examine the code for a function to push an integer onto a stack of integers implemented as a linked list The pointer The integer n Let’s assume the following structure has component been globally*component defined: (that we’ll totally ignore typedef struct stack_element in this example) { int an_integer; struct stack_element *next_in_stack; } STACK_ITEM; • Note: I’m putting the structure definition in a typedef to improve readability Visually, the we’llstack and any new n Let’s assume there’s a function that creates • That’s the only reason (but an important one) that we portray a structure in the real world, records (structures) use fortypedef’s pushing onto stacktoo but calls a separate of this type like so function to actually do the “push” • Anywhere we see STACK_ITEM, we could equally well write struct stack_element, they are completely although it would * It has to be global so that differentinterchangeable; functions can use it to declare localseem variables of stylistically improper to useare both in it’s the global same program the same (user-defined) type. Global definitions fine; variables whose usage is discouraged – in SE 300 we’ll talk about why 10

Pointers to Pointers (cont’d) n The “creating” function can create an initially empty stack

Pointers to Pointers (cont’d) n The “creating” function can create an initially empty stack just by declaring a NULL pointer to it: top 1 STACK_ITEM *top 1 = NULL; n It will also have the right kind of pointer to hold the new_item address returned by malloc STACK_ITEM *new_item; n Now whenever it wants to create a new item for the stack, it makes a call to malloc: new_item = malloc(sizeof(STACK_ITEM)); n And to push the new item, we might want to simply call Note: By not casting the result from the push(top 1, new_item); malloc, I am assuming that the program has used This is not we but#include<stdlib. h> that won’t actually work and quite a religious issue, but the reason is complicated and beyond our scope here; consult the web if you’re interested need to understand why 11

Pointers to Pointers (cont’d) n Let’s look at some simple (but incorrect) code for

Pointers to Pointers (cont’d) n Let’s look at some simple (but incorrect) code for the push: void push(STACK_ITEM *the_top, STACK_ITEM *newguy) { = the_top; When newguy->next_in_stack this declaration the_top is processed … = newguy; When this declaration } new_item is promptly set to And we’re now ready to try to is processed … point to the address by push the new STACK_ITEM int main() void returned push(STACK_ITEM *the_top, n Now let’s look at an animation similar to the previous ones we’ve malloc { STACK_ITEM *newguy) onto the stack seen } STACK_ITEM *top 1; STACK_ITEM *new_item; new_item = malloc(sizeof(STACK_ITEM)); push( top 1, new_item); top 1 new_item … an empty stack is created { newguy->next_in_stack = the_top = newguy; } the_top; Note that although new_item itself is local to main, the cell obtained from malloc is not (take CS 420 to learn more about memory management ; -) … another pointer is created 12

Pointers to Pointers (cont’d) The new is inserted in Then the. STACK_ITEM second argument

Pointers to Pointers (cont’d) The new is inserted in Then the. STACK_ITEM second argument … the cells for its The first argument is then “front” of the old top by making it is evaluated and the After the parameters have been … and the real pointer to the parameters are evaluated and the resultant point to the old top… resultant value is processing copied created and initialized, ofpush the stack has not When is Although created • top Remember: the arrow just copied now value is copied into the cell for the second starts with the first statement in the Here’s the stack we wanted to been updated to point to called …different looks from the original, that visual for function the first parameter body wind…upand with; the problem newest STACK_ITEM the_top pointisto the difference is just an artifact of the picture, since that the_top is going to new STACK_ITEM the copied arrow now originates from a different int main() disappear when to weby exit from void push(STACK_ITEM *the_top, (pointed newguy) place { the push. STACK_ITEM function… *newguy) STACK_ITEM *top 1; { • The reality is that the address value depicted by STACK_ITEM *new_item; newguy->next_in_stack = the_top; the arrow is what was copied and its bit pattern new_item = malloc(sizeof(STACK_ITEM)); the_top = newguy; push(was top 1, new_item); copied exactly (without distortion); it points } } to (the arrowhead touches) the same place as the original the_top 1 new_item newguy 13

The Problem: We Needed a Side-Effect (and Didn’t Get One) … we wanted to

The Problem: We Needed a Side-Effect (and Didn’t Get One) … we wanted to change top 1 itself, which would mean we needed push to create a side effect The problem of course is that inside push, we didn’t want to merely change our local copy of the value of the true top, top 1, which is all that this statement did … int main() { STACK_ITEM *top 1; STACK_ITEM *new_item; new_item = malloc(sizeof(STACK_ITEM)); push( top 1, new_item); } top 1 void push(STACK_ITEM *the_top, STACK_ITEM *newguy) { newguy->next_in_stack = the_top; the_top = newguy; } new_item 14

We Know How to Do That If the calling function wants the called Thetocalled

We Know How to Do That If the calling function wants the called Thetocalled Note that newguy doesn’t need be function, of course, function (push, in this case) to change a pointer to a pointer, since it’snow not doesn’t want a parameter of something one of the calling function’s the same type as the one it’s goingintothis be used change anything local variables (top 1, case), tothe supposed to change; it needs a in the calling program … calling function must pass the called pointer to that type … function the address of the local variable it wants changed int main() { STACK_ITEM *top 1; STACK_ITEM *new_item; new_item = malloc(sizeof(STACK_ITEM)); push(&top 1, new_item); } top 1 new_item void push(STACK_ITEM * *the_top, STACK_ITEM *newguy) { newguy->next_in_stack = * the_top; *the_top = newguy; } … it just holds the (address) value we so ittop 1 can de-reference want … to set to point to –its thelocal cellthe which the address of new contains STACK_ITEM of theonto cell itthe is stack really supposed being pushed to change 15

Creating The Side Effect … is stored in the cell pointed Then to by

Creating The Side Effect … is stored in the cell pointed Then to by this expression is evaluated Then this expression the_top (the_top is de-referenced) … … is stored in is evaluated … Then this expression is evaluated … Now when push is called Then… this expression is evaluated … The push function is now complete … int main() { STACK_ITEM *top 1; And we have successfully STACK_ITEM *new_item; pushed new_item = amalloc(sizeof(STACK_ITEM)); … sonew this. STACK_ITEM value … push(&top 1, new_item); onto the stack whose top } element is pointed to by the pointer top 1 newguy->next_in_stack void push(STACK_ITEM **the_top, STACK_ITEM *newguy) { newguy->next_in_stack = *the_top; *the_top = newguy; } the_top newguy new_item soresultant thisresultant pointer … … and the ……and the …address and itsvalue local cells are …address these local cells is value is destroyed as part are created copied into this cellofcell copied into this the return processing 16

A Note About the Push Logic (Nothing to Do With Pointers) • Although it

A Note About the Push Logic (Nothing to Do With Pointers) • Although it is not obvious, we got lucky this time: This push logic will work correctly for subsequent pushes as well as the first one illustrated here • The best way to see that is to step through the logic just as we did here, drawing and updating the diagrams as you go • If we’d not been lucky, we’d have to modify the push logic to handle as many special cases as were needed void push(STACK_ITEM **the_top, STACK_ITEM *newguy) { newguy->next_in_stack = *the_top; *the_top = newguy; } top 1 17

A Note on Names But the_top needs a more complicated name, perhaps something like

A Note on Names But the_top needs a more complicated name, perhaps something like “ptr_to_top_ptr”, to • The choice of informative variablelike names is particularly important in this top 1 should really be namedbetter something highlight the importance of the fact that it is sort of program “ptr_to_top_of_stack”, it isn’t pointer but a pointer to a pointer notsince a “simple” actually the top element in thethat stack; justchanged willitare be • The names in the example here not in fact very good; they were chosen points to the top element as much or more for brevity than accuracy, since I needed to minimize the number of characters per line or the font would have been unreadable int main() { STACK_ITEM *top 1; STACK_ITEM *new_item; new_item = malloc(sizeof(STACK_ITEM)); push(&top 1, new_item); } Similarly, new_item might better be named “ptr_to_new_item” … void push(STACK_ITEM **the_top, STACK_ITEM *newguy) { newguy->next_in_stack = *the_top; *the_top = newguy; } … and newguy could be “ptr_to_newguy” 18

A Good Software Development Practice “Complex Systems That Succeed Are Invariably Found to Have

A Good Software Development Practice “Complex Systems That Succeed Are Invariably Found to Have Evolved From Simpler Systems That Succeeded” n Unless you get better with pointers, recursive structures, and pointers to pointers than I am, I wouldn’t recommend setting out to write complex programs from scratch with pointers to pointers n Instead, modularize your code properly – e. g. , push and pop as separate functions – but initially code them without all the parameters they’ll use eventually and just declare your key variables, such as the top of a stack, to be global (not passed as arguments) until you get the basic logic for the data structure working n Once you know your basic logic for manipulating the data structure(s) works, then modify your function definitions to accept the necessary parameters and eliminate your use of globals (and I’d do that one key parameter at a time, myself) 19

A Good Software Development Practice (cont’d) n Trying to debug both the basic logic

A Good Software Development Practice (cont’d) n Trying to debug both the basic logic of a complex data structure with complex operations and the dereferencing of pointers to structures containing pointers gives you too much to worry about – you can never be sure if your program is malfunctioning because your understanding of the data structure or its algorithms is wrong or because of mechanical difficulties with the complex syntax of C n “Simplification of concerns” or “development in small steps” or “build a little, test* a little” is another good software development practice – the fewer the number of things that can go wrong in each step, the fewer will and the quicker you’ll be able to pinpoint what you were doing wrong 20