Parameters in C Functions

Technical Summary

Functions operate in C essentially like functions and procedures do in Pascal. However, just as the address nature of pointers is more explicit in C than in Pascal, the address nature of call-by-reference parameters is more explicit in C than in Pascal.

For example, consider the Pascal procedure

procedure MinMax(X,Y:integer; var Min,Max:integer);

begin
   if X < Y then
      begin
      Min:=X;
      Max:=Y
      end
   else
      begin
      Min:=Y;
      Max:=X
      end
end;

After execution of the code

var A,B,SmallerOne,BiggerOne : integer;
...
...
A:=50;  B:=12;
MinMax(A,B,SmallerOne,BiggerOne);

SmallerOne will be 12 and BiggerOne will be 50.

Recall that: X, Y, Min and Max are called formal parameters; A, B, SmallerOne and BiggerOne are called actual parameters; X and Y are termed call-by-value parameters, while Min and Max are called call-by-reference parameters. Call-by-value is indicated by absence of the var construct, while call-by-reference is indicated by its presence.

You were probably taught to use call-by-reference in any setting in which the parameter value gets changed. In the example above, Min and Max get changed, so we use call-by-reference, while A and B don't get changed, so we use call-by-value. Though Pascal does not make this explicit, what is really happening is that call-by-reference means that the address of the actual parameter is passed to the function or procedure, while under call-by-value, the value of the parameter is passed.

If you think about this, it makes good sense. Say I have a procedure R which has one formal parameter, and my code calls the procedure with the actual parameter L. Suppose L originally had the value 290, and that R is supposed to change L to 54. How in the world would R be able to make that change under call-by-value? It couldn't! If I just tell R that L is currently 290 but I don't tell R where L is in memory, there is no way L can place 54 in that memory location. Under call-by-reference, I tell R that L is in location (say) 7006, and then there is no problem; R simply places 54 into that memory location.

Again, this is what Pascal does, but it is all hidden from the programmer; the compiler will decide whether to send a value or an address to the procedure. By contrast, in the C language, the programmer must do this himself/herself.

footnote: Technically, we would say that C does not provide the call-by-reference option; all parameters are call-by-value. When doing C's analog of Pascal's call-by-reference, we are still doing call-by-value, with the ``value'' in this case being an address.

Here is the above example, implemented in C:

void MinMax(X,Y,MinAdrs,MaxAdrs)
   int X,Y,*MinAdrs,*MaxAdrs;

{  if (X < Y)  {
      *MinAdrs = X;
      *MaxAdrs = Y;
   }
   else  {
      *MinAdrs = Y;
      *MaxAdrs = X;
   }
}

As in Pascal, the formal parameters are declared within parentheses right after the function name. But unlike Pascal, the types of the parameters are then given outside the parentheses.

footnote: Though in ANSI C, the types are stated similarly to Pascal, i.e. the heading of MinMax here would be

void MinMax(int X, int Y, int *MinAdrs, int *MaxAdrs)

By the way, it is crucial to place the braces at the proper places. For example, if we write the first few lines above as

void MinMax(X,Y,MinAdrs,MaxAdrs)

{  int X,Y,*MinAdrs,*MaxAdrs;

   if (X < Y)  {

this would be wrong. If we are lucky, the compiler will say so; if not, the compiler will treat X, Y, etc. as local variables, not parameters, and our program won't work correctly. Here is the code calling it:

int A,B,SmallerOne,BiggerOne;
...
...
MinMax(A,B,&SmallerOne,&BiggerOne);

First, a brief remark on the reserved word void: This means that the function MinMax has no type, e.g. is not of type int or any other type; it does not return a value. In other words, MinMax is analogous to a Pascal procedure, not a Pascal function. Writing void here serves to remind us of that fact, though its use is optional.

Now, let's look at the parameters. For example, I have changed the name of the third parameter, from Min in the Pascal case to MinAdrs here. Of course, I can use whatever names I wish, but I have chosen the name MinAdrs to remind myself that this parameter is an address. This can be seen from the `*' in the parameter declaration line,

int X,Y,*MinAdrs,*MaxAdrs;

as well as from the `&' in the call:

MinMax(A,B,&SmallerOne,&BiggerOne);

So, as promised earlier, I really am explicitly passing an address for this variable.

Since it is an address, I have to use `*' to dereference, e.g. in the line

*MinAdrs = X;

which means, ``copy X to the object pointed to by MinAdrs.'' Since MinAdrs contained the address of SmallerOne, the result will be exactly the same as if I had done

SmallerOne = X;

Example

Script File

To illustrate these principles, we show below a function MakeChange which, as its name implies, makes change. The calling module supplies a price and the amount tendered, and the function then reports the number of quarters, dimes, nickels and pennies needed for the change.

  1  Script started on Thu Apr 30 12:42:03 1992
  2  heather% cat MakeChange.c
  3  
  4  /* data for test program */
  5  int Price,AmountTendered,Quarters,Dimes,Nickels,Cents;
  6  
  7  
  8  char *DenomNamePtr[4] = {"quarter","dime","nickel","cent"};
  9  
 10  
 11  void MakeChange(Prc,AmtTndrd,QA,DA,NA,CA)
 12     int Prc,AmtTndrd,*QA,*DA,*NA,*CA;
 13  
 14  
 15  {  Prc = AmtTndrd - Prc;
 16     *QA = Prc / 25;
 17     Prc %= 25;
 18     *DA = Prc / 10;
 19     Prc %= 10;
 20     *NA = Prc / 5;
 21     Prc %= 5;
 22     *CA = Prc;
 23  }
 24  
 25  
 26  void PrntDenom(DnmNmPtr,NCoins)
 27     char *DnmNmPtr;  int NCoins;
 28  
 29  {  if (NCoins == 0) return;
 30     printf("%d %s",NCoins,DnmNmPtr);
 31     if (NCoins > 1) printf("s");
 32     printf("  ");
 33  }
 34  
 35  
 36  main()
 37  
 38  {  while(1)  {
 39        printf("enter price, amount tendered\n");
 40        scanf("%d%d",&Price,&AmountTendered);
 41        MakeChange(Price,AmountTendered,&Quarters,&Dimes,&Nickels,&Cents);
 42        if (Price == AmountTendered)
 43           printf("thanks for using exact change\n");
 44        else  {
 45           printf("your change is:  ");
 46           PrntDenom(DenomNamePtr[0],Quarters);
 47           PrntDenom(DenomNamePtr[1],Dimes);
 48           PrntDenom(DenomNamePtr[2],Nickels);
 49           PrntDenom(DenomNamePtr[3],Cents);
 50           printf("\n");
 51        }
 52     }
 53  }
 54  
 55  
 56  heather% gdb a.out
 57  GDB is free software and you are welcome to distribute copies of it
 58   under certain conditions; type "info copying" to see the conditions.
 59  There is absolutely no warranty for GDB; type "info warranty" for details.
 60  GDB 4.1, Copyright 1991 Free Software Foundation, Inc...
 61  (gdb) b 39
 62  Breakpoint 1 at 0x2414: file MakeChange.c, line 39.
 63  (gdb) r
 64  Starting program: /1/home/matloff/Courses/40/Progs/a.out 
 65  enter price, amount tendered
 66  73 100
 67  
 68  Breakpoint 1, main () at MakeChange.c:39
 69  39            MakeChange(Price,AmountTendered,&Quarters,&Dimes,&Nickels,&Cents);
 70  (gdb) p Price
 71  $1 = 73
 72  (gdb) s
 73  MakeChange (Prc=73, AmtTndrd=100, QA=0x4164, DA=0x4160, NA=0x4158, CA=0x415c)
 74      at MakeChange.c:13
 75  13      {  Prc = AmtTndrd - Prc;
 76  (gdb) p Prc
 77  $2 = 73
 78  (gdb) n
 79  14         *QA = Prc / 25;
 80  (gdb) p Prc
 81  $3 = 27
 82  (gdb) p Price
 83  $4 = 73
 84  (gdb) n
 85  15         Prc %= 25;
 86  (gdb) p QA
 87  $5 = (int *) 0x4164
 88  (gdb) p *0x4164
 89  $6 = 1
 90  (gdb) p Quarters
 91  $7 = 1
 92  (gdb) c
 93  Continuing.
 94  your change is:  1 quarter  2 cents  
 95  enter price, amount tendered
 96  ^C
 97  Program received signal 2, Interrupt
 98  0xf7737194 in read ()
 99  (gdb) qy
100  heather% e
101  heather% 
102  script done on Thu Apr 30 12:46:47 1992

Analysis

Overview: The function is in Lines 11-23. Then starting on Line 38, we have a test program to try out the function.

Line 8: Same ingredients as we have seen before, but in more complicated packages. First, let's look at

char *DenomNamePtr[4]

We are declaring a variable named DenomName; what is its type? Well, the `[4]' tells us that it is an array, of four elements. What is the type of each element? Well, the `char *' tells us that the type of each element is pointer-to-character. In other words, DenomName is an array of four pointer-to-character variables.

Now, what about the rest of this same line?

= {"quarter","dime","nickel","cent"};

Recall that we can initialize variables at the same time we declare them. E.g.

int Z = 4;

declares a variable Z of integer type, and also initializes Z to 4.

Similarly, our declaration on Line 8 here initializes DenomNamePtr[0] to point to the string ``quarter'', DenomNamePtr[1] to point to ``dime'', and so on.

Lines 11-23: When the `/' operator is applied to an integer dividend and an integer divisor, `/' produces an integer quotient, in the same way as div does in Pascal. `%' is identical to Pascal's mod.

Lines 56: Here I am again using gdb for NON-debugging purposes, i.e. just to illustrate in more detail how the pointers work.

Lines 69ff: Here we are about to call the function. We have already read in Price as 73 (Line 66, confirmed on Lines 70-71), and AmountTendered as 100. On Line 72, I go ahead; note that I am using the `s' command, not `n', because I do wish to see what happens line-by-line as I execute the MakeChange function.

Line 73: gdb provides me yet another convenience, telling me all the parameter values, e.g. Prc = 73. Of course, this is no surprise, since we already knew that Prc's actual parameter here, Price, has the value 73.

Lines 76-77: Just checking once more; Prc is 73.

Lines 80ff: At this point we have just finished executing

Prc = AmtTndrd - Prc;

from Line 75 (remember, at Line 75 we were ready to execute this statement, but had not yet done so; our `n' command on Line 78 made us execute this statement). Note (Lines 80-81) that Prc has now changed from 73 to 27, as expected (27 = 100 - 73). However, Price has not changed; it is still 73 (Lines 82-83). Price was the actual parameter corresponding to the formal parameter Prc, but since it is call-by-value, the change in Prc did not cause a change in Price.

Lines 84ff: On Line 84, our typing `n' makes the statement

*QA = Prc / 25;

from Line 79 execute. Now on Lines 86-91 we see the result: The expression Prc/25 is 27/25, which is 1. Where will this 1 be put? Well, the statement told us to put the 1 in the object pointed to by QA, i.e. the object at memory location 4164. But where did this 4164 come from? Well, it came from the actual parameter corresponding to the formal parameter QA, which we can see from Line 69 was &, i.e. the address of the variable Quarters. Putting all this information together, we see that the statement

*QA = Prc / 25;

will place 1 into Quarters! This is confirmed, on Lines 90-91.

Of course, this is exactly what we wanted--we did want Quarters to change. In Pascal we would have made this call-by-reference; C uses only call-by-value, but that is no problem, since the ``value'' which we pass to the function is the address of the variable which we want to be changed, in this case the address of Quarters (which is why I gave the formal parameter the name QA).



Norm Matloff
Wed Nov 8 17:33:20 PST 1995