You get a bonus - 1 coin for daily activity. Now you have 1 coin

Defining and calling functions

Lecture



The power of the SI language is largely determined by the ease and flexibility in defining and using functions in SI programs. Unlike other high-level programming languages, there is no division into procedures, subprograms and functions in the SI language; here the entire program is built only from functions.

A function is a collection of declarations and operators, usually designed to solve a specific task. Each function must have a name that is used to declare, define, and call it. In any program on the SI there should be a function named main (main function), it is from this function, no matter where it is located in the program, the program starts.

When calling a function, it can be passed some values ​​(actual parameters) used during the execution of the function with the help of arguments (formal parameters). The function can return some (one!) Value. This return value is the result of the function execution, which, when the program is executed, is substituted into the function call point, wherever the call is encountered. It is also allowed to use functions that have no arguments and functions that do not return any values. The effect of such functions may consist, for example, in changing the values ​​of some variables, printing some texts, etc.

There are three concepts associated with the use of functions in the SI language - function definition (description of actions performed by a function), function declaration (setting the form for accessing a function) and function call.

The function definition specifies the type of return value, the name of the function, the types and number of formal parameters, as well as variable declarations and operators, called the function body, and define the action of the function. A function class can also be specified in the function definition.

Example:

  int rus (unsigned char r)

      {if (r> = 'A' && c <= '')

            return 1;

         else

            return 0;

      }

In this example, a function is defined with the name rus, which has one parameter with the name r and of the unsigned char type. The function returns an integer value of 1 if the function parameter is a letter of the Russian alphabet, or 0 otherwise.

In the SI language there is no requirement that the definition of a function necessarily precede its call. The definitions of the functions used can follow the definition of the function main, in front of it, or in another file.

However, in order for the compiler to verify the correspondence of the types of actual parameters passed to the types of formal parameters, a function declaration (prototype) must be placed before the function is called.

The function declaration has the same form as the function definition, with the only difference that the function body is missing, and the names of the formal parameters can also be omitted. For the function defined in the last example, the prototype may be

int rus (unsigned char r); or rus (unsigned char);

In the programs in the SI language, the so-called library functions are widely used, i.e. functions pre-designed and written to libraries. The prototypes of library functions are located in special header files supplied with libraries as part of programming systems, and are included in the program using the #include directive.

If the function declaration is not specified, then by default the function prototype is built based on the analysis of the first function reference, be it a function call or definition. However, such a prototype is not always consistent with the subsequent definition or function call. It is recommended to always set the function prototype. This will allow the compiler to either issue diagnostic messages if the function is used incorrectly, or to correctly adjust the argument mismatch that is set when the program is executed.

The function parameter declaration during its definition can be performed in the so-called "old style", in which only the names of the parameters follow in brackets, followed by the brackets of the parameter type declarations. For example, the rus function from the previous example can be defined as follows:

  int rus (r)

         unsigned char r;

         {... / * function body * / ...}

In accordance with the syntax of the C language, the definition of a function has the following form:

  [memory class specifier] [type specifier] function_name

    ([list of formal parameters])

    {body functions}

The optional class-memory-specifier specifies the memory class of the function, which can be static or extern. The memory classes will be discussed in detail in the next section.

The function type specifier specifies the type of the return value and can be any type. If no type specifier is specified, then the function is assumed to return an int value.

A function cannot return an array or function, but it can return a pointer to any type, including an array and a function. The return type specified in the function definition must match the type in the declaration of this function.

A function returns a value if its execution ends with a return statement containing some expression. The specified expression is evaluated, converted, if necessary, to the type of the return value and returned to the function call point as a result. If the return statement does not contain an expression or the function is completed after the execution of its last statement (without the return statement), then the return value is undefined. For functions that do not use the return value, the void type must be used, indicating that there is no return value. If a function is defined as a function that returns some value, and there is no expression in the return statement when it exits, then the behavior of the calling function after passing control to it may be unpredictable.

The list of formal parameters is a sequence of declarations of formal parameters, separated by commas. Formal parameters are variables that are used inside the function body and receive a value when a function is called by copying the values ​​of the corresponding actual parameters into them. The list of formal parameters can end with a comma (,) or a comma with an ellipsis (, ...), which means that the number of function arguments is variable. However, it is assumed that the function has at least as many required arguments as the formal parameters are specified before the last comma in the parameter list. Such a function can be passed a larger number of arguments, but over additional arguments are not carried out type control.

If the function does not use parameters, the presence of parentheses is mandatory, and instead of a list of parameters, it is recommended to specify the word void.

The order and types of formal parameters should be the same in the definition of the function and in all its declarations. The types of actual parameters when calling a function must be compatible with the types of the corresponding formal parameters. The type of a formal parameter can be any basic type, structure, union, enumeration, pointer, or array. If the type of the formal parameter is not specified, then the int type is assigned to this parameter.

For a formal parameter, you can set the memory class register, while for values ​​of type int, the type specifier can be omitted.

Formal parameters identifiers are used in the function body as references to the values ​​passed. These identifiers cannot be redefined in a block that forms the function body, but can be redefined in an internal block within the function body.

When passing parameters to a function, if necessary, the usual arithmetic transformations are performed for each formal parameter and each actual parameter independently. After conversion, the formal parameter cannot be shorter than int, i.e. declaration of a formal parameter with type char is equivalent to its declaration of type int. And the parameters, which are real numbers, are of type double.

The transformed type of each formal parameter determines how the arguments are passed when the function is called on the stack. The mismatch between the types of actual arguments and formal parameters can be the cause of misinterpretation.

The body of a function is a compound statement containing statements defining the action of the function.

All variables declared in the function body without specifying a memory class have an auto memory class, i.e. they are local. When a function is called, local variables are allocated memory in the stack and are initialized. Control is transferred to the first operator of the function body and the execution of the function begins, which continues until the return operator or the last operator of the function body is encountered. The control returns to the point following the call point, and local variables become inaccessible. With a new function call for local variables, the memory is distributed again, and therefore the old values ​​of local variables are lost.

Function parameters are passed by value and can be considered as local variables for which memory is allocated when the function is called and initialized with the values ​​of actual parameters. When exiting the function, the values ​​of these variables are lost. Since the transfer of parameters occurs by value, the values ​​of the variables in the calling function, which are the actual parameters, cannot be changed in the function body. However, if you pass a pointer to a variable as a parameter, you can change the value of this variable using the unloading operation.

Example:

  / * Incorrect use of parameters * /

        void change (int x, int y)

        {int k = x;

                 x = y;

                 y = k;

        }

In this function, the values ​​of the x and y variables, which are formal parameters, are swapped, but since these variables exist only inside the change function, the values ​​of the actual parameters used in the function call will remain unchanged. In order to swap the values ​​of the actual arguments, you can use the function shown in the following example.

Example:

  / * Proper use of parameters * /

        void change (int * x, int * y)

        {int k = * x;

              * x = * y;

              * y = k;

        }

When calling such a function, the actual parameters should not be the values ​​of variables, but their addresses

change (& a, & b);

If you need to call a function before its definition in the file in question, or the function definition is in another source file, then the function call should be preceded by a declaration of this function. The function declaration (prototype) has the following format:

[memory-class specifier] [type-specifier] function-name ([formal-parameter-list]] [, function-name-list];

In contrast to the definition of a function, in the prototype a semicolon immediately follows the heading, and the function body is missing. If several different functions return values ​​of the same type and have the same lists of formal parameters, then these functions can be declared in a single prototype, specifying the name of one of the functions as the function name, and all the others should be placed in the function name list, each function accompanied by a list of formal parameters. The rules for using the remaining format elements are the same as when defining a function. The names of the formal parameters when declaring the function can be omitted, and if they are specified, then their scope extends only to the end of the declaration.

A prototype is an explicit declaration of a function that precedes the definition of a function. The type of the return value when declaring the function must match the type of the return value in the function definition.

If the function prototype is not specified, but a function call is encountered, an implicit prototype is constructed from the analysis of the function call form. The return type of the prototype being created is int, and the list of types and the number of parameters of the function is formed based on the types and the number of actual parameters used in this call.

Thus, the function prototype must be specified in the following cases:

1. The function returns a value of a type other than int.

2. It is required to initialize some pointer to a function before this function is defined.

The presence in the prototype of a complete list of types of parameter arguments allows you to check the conformity of the types of actual parameters when calling a function to the types of formal parameters, and, if necessary, perform the appropriate conversions.

In the prototype, you can indicate that the number of function parameters is variable, or that the function has no parameters.

If the prototype is specified with the static memory class, then the function definition must have a static memory class. If the memory class specifier is not specified, then the extern memory class is assumed.

The function call has the following format:

address-expression ([expression-list])

Since, syntactically, the function name is the address of the beginning of the function body, an address-expression (including the function name or a pointer to a function pointer) having the function address value can be used as a function call.

The expression list is a list of the actual parameters passed to the function. This list may be empty, but the presence of parentheses is required.

The actual parameter can be a value of any basic type, structure, union, enumeration, or pointer to an object of any type. The array and function cannot be used as actual parameters, but pointers to these objects can be used.

The function call is executed as follows:

1. The expressions in the list of expressions are calculated and subjected to the usual arithmetic transformations. Then, if the function prototype is known, the type of the actual argument obtained is compared with the type of the corresponding formal parameter. If they do not match, then either type conversion is performed or an error message is generated. The number of expressions in the list of expressions must match the number of formal parameters, unless the function has a variable number of parameters. In the latter case, only mandatory parameters are subject to verification. If the prototype of the function indicates that it does not require parameters, and when they are called, the error message is generated.

2. There is an assignment of the values ​​of the actual parameters to the corresponding formal parameters.

3. Control is transferred to the first function operator.

4. Executing the return statement in the function body returns the control and possibly the value to the calling function. In the absence of a return statement, control is returned after the execution of the last function body statement, and the return value is undefined.

The address expression in front of the parentheses determines the address of the function being called. This means that a function can be called via a function pointer.

Example:

int (* fun) (int x, int * y);

Here the variable fun is declared as a pointer to a function with two parameters: an int type and a pointer to an int. The function itself must return an int. Parentheses containing the pointer name fun and the pointer character * are required, otherwise the entry

int * fun (intx, int * y);

will be interpreted as a declaration of the fun function returning a pointer to an int.

The function call is possible only after initializing the value of the pointer fun and has the form:

(* fun) (i, & j);

In this expression, to get the address of the function referenced by the fun pointer, the racking * operation is used.

A pointer to the function can be passed as a parameter to the function. In this case, the addressing occurs during the call of the function to which the pointer to the function refers. You can assign a value to a function pointer in an assignment operator by using the function name without a list of parameters.

Example:

  double (* fun1) (int x, int y);

     double fun2 (int k, int l);

        fun1 = fun2;  / * initialize function pointer * /

        (* fun1) (2.7);  / * function call * /

In the above example, the pointer to the function fun1 is described as a pointer to a function with two parameters, which returns a double value, and the function fun2 is also described. Otherwise, i.e. when a function pointer is assigned to a function described differently than a pointer, an error will occur.

Consider an example of using a pointer to a function as a parameter of a function that calculates the derivative of the function cos (x).

Example:

  double proiz (double x, double dx, double (* f) (double x));

    double fun (double z);

    int main ()

    {

      double x;  / * point to calculate the derivative * /

      double dx;  / * increment * /

      double z;  / * derivative value * /

      scanf ("% f,% f", & x, & dx);  / * enter x and dx * /

      z = proiz (x, dx, fun);  / * function call * /

      printf ("% f", z);  / * print the value of the derivative * /

      return 0;

    }

    double proiz (double x, double dx, double (* f) (double z))

    {/ * derivative calculating function * /

       double xk, xk1, pr;

       xk = fun (x);

       xk1 = fun (x + dx);

       pr = (xk1 / xk-1e0) * xk / dx;

       return pr;

    }

    double fun (double z)

    {/ * function from which the derivative is calculated * /

       return (cos (z));

    }

To calculate the derivative of any other function, you can change the function body fun or use the name of another function when calling the proiz function. In particular, to calculate the derivative of the function cos (x), you can call the function proiz in the form

z = proiz (x, dx, cos);

and to calculate the derivative of the function sin (x) in the form

z = proiz (x, dx, sin);

Any function in a program in the SI language can be called recursively, i.e. she can call herself. The compiler allows any number of recursive calls. For each call, formal parameters and variables with auto and register memory classes are allocated a new memory area, so their values ​​from previous calls are not lost, but only the values ​​of the current call are available at each time instant.

Variables declared with the static memory class do not require allocation of a new memory area with each recursive function call and their values ​​are available during the whole program execution time.

The classic example of recursion is the mathematical definition of factorial n! :

n! = 1 for n = 0;

        n * (n-1)! with n> 1.

The function calculating factorial will have the following form:

 long fakt (int n)

        {

         return ((n == 1)? 1: n * fakt (n-1));

        }

Although the CI compiler does not limit the number of recursive function calls, this number is limited by the computer's memory resource and if the number of recursive calls is too large, a stack overflow can occur.

1.5.2. Calling a function with a variable number of parameters

When calling a function with a variable number of parameters, any required number of arguments is specified in the call to this function. In the declaration and definition of such a function, the variable number of arguments is given by ellipsis at the end of the list of formal parameters or the list of types of arguments.

All arguments specified in the function call are placed on the stack. The number of formal parameters declared for a function is determined by the number of arguments that are taken from the stack and assigned to formal parameters. The programmer is responsible for the correct selection of additional arguments from the stack and determining the number of arguments that are in the stack.

Examples of functions with a variable number of parameters are functions from the library of functions of the SI language that perform input / output information (printf, scanf, etc.). These functions are discussed in detail in the third part of the book.

A programmer can develop his functions with a variable number of parameters. To provide a convenient way to access the arguments of a function with a variable number of parameters, there are three macro definitions (macros) va_start, va_arg, va_end, which are located in the stdarg.h header file. These macros indicate that a user-developed function has a number of required arguments, followed by a variable number of optional arguments. Required arguments are available through their names as when calling a regular function. To extract the optional arguments, use the va_start, va_arg, va_end macros in the following order.

The va_start macro is used to set the arg_ptr argument to the beginning of the list of optional parameters and has the form of a function with two parameters:

void va_start (arg_ptr, prav_param);

The prav_param parameter must be the last required parameter of the function being called, and the pointer arg_prt must be declared with a predefined variable in the list of variables of type va_list:

va_list arg_ptr;

The va_start macro must be used before the first use of the va_arg macro.

The macro *** and va_arg provides access to the current parameter of the function being called and also has the form of a function with two parameters

type_arg va_arg (arg_ptr, type);

This macro *** retrieves a value of type type at the address specified by the pointer arg_ptr, increases the value of the pointer arg_ptr by the length of the parameter used (length type) and thus the parameter arg_ptr will point to the next parameter of the function being called. The macro *** and va_arg are used as many times as necessary to extract all the parameters of the function being called.

The va_end macro is used after all the parameters of the function have been processed and sets the pointer to the list of optional parameters to zero (NULL).

Consider the use of these macros for processing the parameters of a function that calculates the average value of an arbitrary sequence of integers. Since the function has a variable number of parameters, we will consider the end of the list to be -1. Since the list must have at least one element, the function will have one required parameter.

Example:

  #include 

    int main ()

   {int n;

     int sred_znach (int, ...);

     n = sred_znach (2,3,4, -1);

                     / * call with four parameters * /

     printf ("n =% d", n);

     n = sred_znach (5,6,7,8,9, -1);

                    / * call with six parameters * /

     printf ("n =% d", n);

     return (0);

    }



   int sred_znach (int x, ...);

    {

      int i = 0, j = 0, sum = 0;

      va_list uk_arg;

      va_start (uk_arg, x); / * set the uk_arg pointer to * /

                           / * first optional parameter * /

      if (x! = - 1) sum = x; / * check for empty list * /

      else return (0);

      j ++;

      while ((i = va_arg (uk_arg, int))! = - 1)

                                   / * sample of the next * /

      {/ * parameter and check * /

        sum + = i; / * at the end of the list * /

        j ++;

       }

      va_end (uk_arg); / * close the list of parameters * /

      return (sum / j);

    }

1.5.3. Passing parameters to main function

The main function, which starts the execution of the CI program, can be defined with parameters that are transmitted from the external environment, for example, from the command line. The external environment has its own rules for the presentation of data, and more precisely, all data is represented as strings of characters. To transfer these lines to the main function, two parameters are used, the first parameter is used to transfer the number of transmitted lines, the second one to transfer the lines themselves. The common (but not mandatory) names of these parameters are argc and argv. The argc parameter is of type int, its value is formed from the analysis of the command line and is equal to the number of words on the command line, including the name of the program being called (a word is any text that does not contain a space character). The argv parameter is an array of pointers to strings, each of which contains one word from the command line.If the word must contain a space character, then when it is written on the command line, it must be enclosed in quotes.

The main function can also have a third parameter, which is called argp, and which serves to transfer the parameters of the operating system (environment) in which the C-program is running to the main function.

The header of the main function is:

int main (int argc, char * argv [], char * argp [])

If, for example, the CI program command line has the form:

A: \> cprog working 'C program' 1

then the arguments argc, argv, argp are represented in memory as shown in the diagram in Fig.1.

 argc [4]

     argv [] -> [] -> [A: \ cprog.exe \ 0]

                     [] -> [working \ 0]

                     [] -> [C program \ 0]

                     [] -> [1 \ 0]

                     [NULL]

     argp [] -> [] -> [path = A: \; C: \\ 0]

                     [] -> [lib = D: \ LIB \ 0]

                     [] -> [include = D: \ INCLUDE \ 0]

                     [] -> [conspec = C: \ COMMAND.COM \]

                     [NULL]

      Fig.1. Command line layout

The operating system supports passing values ​​for the argc, argv, argp parameters, and the user is responsible for passing and using the actual arguments of the main function.

The following example presents a program for printing the actual arguments passed to the main function from the operating system and the parameters of the operating system.

 Example:

     int main (int argc, char * argv [], char * argp [])

     {int i = 0;

        printf ("\ n Program Name% s", argv [0]);

        for (i = 1; i> = argc; i ++)

        printf ("\ n argument% d equals% s", argv [i]);

        printf ("\ n Operating system options:");

        while (* argp)

          {printf ("\ n% s", * argp);

             argp ++;

           }

        return (0);

      }

Access to the parameters of the operating system can also be obtained using the library function geteuv, its prototype has the following form:

char * geteuv (const char * varname);

The argument of this function sets the name of the environment parameter, the pointer to the value of which will be returned by the function geteuv. If the specified parameter is not defined in the environment at the moment, then the return value is NULL.

Using the pointer obtained by the geteuv function, you can only read the value of the operating system parameter, but you cannot change it. To change the value of a system parameter, use the puteuv function.

The compiler of the SI language builds the SI program in such a way that at the beginning of the program’s work some initialization is performed, including, among other things, the processing of the arguments passed to the main function and passing to it the values ​​of the environment parameters. These actions are performed by the _setargv and _seteuv library functions, which are always placed by the compiler in front of the main function.

If the CI program does not use the transmission of arguments and values ​​of the operating system parameters, then it is advisable to prohibit the use of the library functions _setargv and _seteuv by placing into the CI program before the main function functions with the same names, but not performing any actions (stubs). The beginning of the program in this case will be:

 _setargv ()

    {return; / * empty function * /

     }

   -seteuv ()

    {return; / * empty function * /

     }

    int main ()

    {/ * main function without arguments * /

     ...

     ...

    renurn (0);

     }

In the above program, when calling the _setargv and _seteuv library functions, the functions placed in the program by the user and not performing any actions will be used. This will significantly reduce the size of the resulting exe-file.

продолжение следует...

Продолжение:


Часть 1 Defining and calling functions


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Algorithmization and programming. Structural programming. C language

Terms: Algorithmization and programming. Structural programming. C language