Functions and Program Structure



  • Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch.


  • Each function definition has the form :


  • return-type function-name(argument declarations)
    {
        declarations and statements
    }

  • Various parts may be absent; a minimal function is dummy() {} which does nothing and returns nothing.


  • If the return type is omitted, int is assumed.


  • The return statement is the mechanism for returning a value from the called function to its caller. Any expression can follow return: (Syntax) : return expression; The expression will be converted to the return type of the function if necessary. Parentheses are often used around the expression, but they are optional.


  • The calling function is free to ignore the returned value. Furthermore, there need to be no expression after return; in that case, no value is returned to the caller.


  • Control also returns to the caller with no value when execution ``falls off the end" of the function by reaching the closing right brace.


  • If a function fails to return a value, its ``value'' is certain to be garbage.




Q. Demonstrate use of functions:


  •    
    #include <ctype.h>
       /* atof:  convert string s to double */
       double atof(char s[])
       {
           double val, power;
           int i, sign;
           for (i = 0; isspace(s[i]); i++)  /* skip white space */
               ;
           sign = (s[i] == '-') ? -1 : 1;
           if (s[i] == '+' || s[i] == '-')
               i++;
           for (val = 0.0; isdigit(s[i]); i++)
               val = 10.0 * val + (s[i] - '0');
           if (s[i] == '.')
               i++;
           for (power = 1.0; isdigit(s[i]); i++) {
               val = 10.0 * val + (s[i] - '0');
               power *= 10;
           }
           return sign * val / power;
       }

  • The calling routine must know that atof returns a non-int value. One way to ensure this is to declare atof explicitly in the calling routine.


  • #include 
    #define MAXLINE 100
    /* rudimentary calculator */
       main()
       {
           double sum, atof(char []);
           char line[MAXLINE];
           int getline(char line[], int max);
           sum = 0;
           while (getline(line, MAXLINE) > 0)
               printf("\t%g\n", sum += atof(line));
           return 0;
       }

  • The declaration double sum, atof(char []); says that sum is a double variable, and that atof is a function that takes one char[] argument and returns a double.





Functions Returning Non-integers



  • A mismatch can happen is that if there is no function prototype, a function is implicitly declared by its first appearance in an expression, such as sum += atof(line). If a name that has not been previously declared occurs in an expression and is followed by a left parentheses, it is declared by context to be a function name, the function is assumed to return an int, and nothing is assumed about its arguments.


  • Also if a function declaration does not include arguments, as in double atof(); that too is taken to mean that nothing is to be assumed about the arguments of atof; all parameter checking is turned off.


  • If the function takes arguments, declare them; if it takes no arguments, use void.



Q. Demonstrate the use of functions


  • /* atoi:  convert string s to integer using atof */
       int atoi(char s[])
       {
           double atof(char s[]);
           return (int) atof(s);
       }
    

  • The value of atof, a double, is converted automatically to int when it appears in this return, since the function atoi returns an int.




External Variables



  • External variables are defined outside of any function, and are thus potentionally available to many functions.


  • Functions themselves are always external, because C does not allow functions to be defined inside other functions.


  • Because external variables are globally accessible, they provide an alternative to function arguments and return values for communicating data between functions. Any function may access an external variable by referring to it by name.


  • External variables are also useful because of their greater scope and lifetime. Automatic variables are internal to a function; they come into existence when the function is entered, and disappear when it is left.


  • External variables, on the other hand, are permanent, so they can retain values from one function invocation to the next.


  • If two functions must share some data, yet neither calls the other, it is often most convenient if the shared data is kept in external variables rather than being passed in and out via arguments.




Q. Write a calculator program that provides the operators +, -, * and /


  • The calculator will use reverse Polish notation (Postfix) instead of infix.


  • In reverse Polish notation, each operator follows its operands; an infix expression like (1 - 2) * (4 + 5) is entered as 1 2 - 4 5 + * Parentheses are not needed.


  •    #include 
       #include   /* for  atof() */
       #define MAXOP   100  /* max size of operand or operator */
       #define NUMBER  '0'  /* signal that a number was found */
       int getop(char []);
       void push(double);
       double pop(void);
       /* reverse Polish calculator */
       main()
       {
           int type;
           double op2;
           char s[MAXOP];
           while ((type = getop(s)) != EOF) {
               switch (type) {
               case NUMBER:
                   push(atof(s));
                   break;
               case '+':
                   push(pop() + pop());
                   break;
               case '*':
                   push(pop() * pop());
                   break;
               case '-':
                   op2 = pop();
                   push(pop() - op2);
                   break;
               case '/':
                   op2 = pop();
                   if (op2 != 0.0)
                       push(pop() / op2);
                   else
                       printf("error: zero divisor\n");
                   break;
               case '\n':
                   printf("\t%.8g\n", pop());
                   break;
               default:
                   printf("error: unknown command %s\n", s);
                   break;
               }
           }
           return 0;
       }

  • The simplified algorithm of the program is :


  •    while (next operator or operand is not end-of-file indicator)
           if (number)
               push it
           else if (operator)
               pop operands
               do operation
               push result
           else if (newline)
               pop and print top of stack
           else
               error

  • Because + and * are commutative operators, the order in which the popped operands are combined is irrelevant, but for - and / the left and right operand must be distinguished.


  • In push(pop() - pop()); the order in which the two calls of pop are evaluated is not defined. To guarantee the right order, it is necessary to pop the first value into a temporary variable.


  •    #define MAXVAL  100  /* maximum depth of val stack */
       int sp = 0;          /* next free stack position */
       double val[MAXVAL];  /* value stack */
       /* push:  push f onto value stack */
       void push(double f)
       {
           if (sp < MAXVAL)
               val[sp++] = f;
           else
               printf("error: stack full, can't push %g\n", f);
       }
       /* pop:  pop and return top value from stack */
       double pop(void)
       {
           if (sp > 0)
               return val[--sp];
           else {
               printf("error: stack empty\n");
               return 0.0;
           }
       }
    

  • Thus the stack and stack index that must be shared by push and pop are defined outside these functions.




Scope Rules



  • The scope of a name is the part of the program within which the name can be used


  • For an automatic variable declared at the beginning of a function, the scope is the function in which the name is declared.


  • Local variables of the same name in different functions are unrelated.


  • The scope of an external variable or a function lasts from the point at which it is declared to the end of the file being compiled.


  • If an external variable is to be referred to before it is defined, or if it is defined in a different source file from the one where it is being used, then an extern declaration is mandatory.


  • A declaration announces the properties of a variable (primarily its type); a definition also causes storage to be set aside. If the lines

     int sp;
            double val[MAXVAL];
    appear outside of any function, they define the external variables sp and val, cause storage to be set aside, and also serve as the declarations for the rest of that source file. On the other hand, the lines
    extern int sp;
         extern double val[];
    declare for the rest of the source file that sp is an int and that val is a double array, but they do not create the variables or reserve storage for them.


  • There must be only one definition of an external variable among all the files that make up the source program; other files may contain extern declarations to access it.


  • Array sizes must be specified with the definition, but are optional with an extern declaration. Initialization of an external variable goes only with the definition.




Q. Demonstrate extern keyword


  • in file1: 
          extern int sp;
          extern double val[];
          void push(double f) { ... }
          double pop(void) { ... }
    in file2: 
          int sp = 0;
          double val[MAXVAL];
    

  • Because the extern declarations in file1 lie ahead of and outside the function definitions, they apply to all functions; one set of declarations suffices for all of file1.




Header Files



  • Consider dividing the calculator program given above into several source files.


  • The main function would go in one file, which we will call main.c; push, pop, and their variables go into a second file, stack.c; getop goes into a third, getop.c. Finally, getch and ungetch go into a fourth file, getch.c; we separate them from the others because they would come from a separately-compiled library in a realistic program.


  • There is one more thing to worry about - the definitions and declarations shared among files. As much as possible, we want to centralize this, so that there is only one copy to get and keep right as the program evolves.


  • There is a tradeoff between the desire that each file have access only to the information it needs for its job and the practical reality that it is harder to maintain more header files.


  • Header Files



Static Variables



  • The static declaration, applied to an external variable or function, limits the scope of that object to the rest of the source file being compiled.


  • Static storage is specified by prefixing the normal declaration with the word static.


  • If the two routines and the two variables are compiled in one file, as in :


  •    static char buf[BUFSIZE];  /* buffer for ungetch */
       static int bufp = 0;       /* next free position in buf */
       int getch(void) { ... }
       void ungetch(int c) { ... }
    

  • then no other routine will be able to access buf and bufp, and those names will not conflict with the same names in other files of the same program.


  • The external static declaration is most often used for variables, but it can be applied to functions as well. Normally, function names are global, visible to any part of the entire program. If a function is declared static, however, its name is invisible outside of the file in which it is declared.


  • The static declaration can also be applied to internal variables. Internal static variables are local to a particular function just as automatic variables are, but unlike automatics, they remain in existence rather than coming and going each time the function is activated. This means that internal static variables provide private, permanent storage within a single function.




Register Variables



  • A register declaration advises the compiler that the variable in question will be heavily used.


  • The idea is that register variables are to be placed in machine registers, which may result in smaller and faster programs. But compilers are free to ignore the advice.


  • The register declaration looks like

       register int  x;
       register char c;


  • The register declaration can only be applied to automatic variables and to the formal parameters of a function.


  • void sample(register unsigned m, register long n)
       {
           register int i;
           ...
       }




Block Structure



  • C is not a block-structured language because functions may not be defined within other functions.


  • Declarations of variables (including initialization) may follow the left brace that introduces any compound statement, not just the one that begins a function.


  • Variables declared in this way hide any identically named variables in outer blocks, and remain in existence until the matching right brace.


  • if (n > 0) {
           int i;  /* declare a new i */
           for (i = 0; i < n; i++)
               ...
       }
    

  • In above program the scope of the variable i is the ``true" branch of the if; this i is unrelated to any i outside the block. An automatic variable declared and initialized in a block is initialized each time the block is entered.


  • Automatic variables, including formal parameters, also hide external variables and functions of the same name.


  •    int x;
       int y;
       f(double x)
       {
           double y;
       }

  • then within the function "f", occurrences of "x" refer to the parameter, which is a double; outside "f", they refer to the external int. The same is true of the variable "y".