4.2. The type of functionsAll functions have a type: they return a value of that type whenever they are used. The reason that C doesn't have ‘procedures’, which in most other languages are simply functions without a value, is that in C it is permissible (in fact well-nigh mandatory) to discard the eventual value of most expressions. If that surprises you, think of an assignment a = 1; That's a perfectly valid assignment, but don't forget that it has a value too. The value is discarded. If you want a bigger surprise, try this one: 1; That is an expression followed by a semicolon. It is a well formed statement according to the rules of the language; nothing wrong with it, it is just useless. A function used as a procedure is used in the same way—a value is always returned, but you don't use it: f(argument); is also an expression with a discarded value. It's all very well saying that the value returned by a function can be
ignored, but the fact remains that if the function really does
return a value then it's probably a programming error not to do something
with it. Conversely, if no useful value is returned then it's a good idea
to be able to spot anywhere that it is used by mistake. For both of those
reasons, functions that don't return a useful value should be declared to
be Functions can return any type supported by C (except for arrays and functions), including the pointers, structures and unions which are described in later chapters. For the types that can't be returned from functions, the restrictions can often be sidestepped by using pointers instead. All functions can be called recursively. 4.2.1. Declaring functionsUnfortunately, we are going to have to use some jargon now. This is one of the times that the use of an appropriate technical term really does reduce the amount of repetitive descriptive text that would be needed. With a bit of luck, the result is a shorter, more accurate and less confusing explanation. Here are the terms.
The terms ‘parameter’ and ‘argument’ do tend to get used as if they were interchangeable, so don't read too much into it if you see one or the other in the text below. If you use a function before you declare it, it is implicitly declared
to be ‘function returning double aax1(void); and here is how it might be used: The declaration was an interesting one. It defined
The presence of To define a function you also have to provide a body for
it, in the form of a compound statement. Since no function can itself
contain the definition of a function, functions are all separate from
each other and are only found at the outermost level of the program's
structure. Here is a possible definition for the function
double
aax1(void) {
/* code for function body */
return (1.0);
}
It is unusual for a block-structured language to prohibit you from defining functions inside other functions, but this is one of the characteristics of C. Although it isn't obvious, this helps to improve the run-time performance of C by reducing the housekeeping associated with function calls. 4.2.2. The return statementThe Here is another example function. It uses
#include <stdio.h>
int
non_space(void){
int c;
while ( (c=getchar ())=='\t' || c== '\n' || c==' ')
; /* empty statement */
return (c);
}
Look at the way that all of the work is done by the test in the
while (something); with the semicolon hidden away at the end like that. It's too easy to
miss it when you read the code, and to assume that the following
statement is under the control of the The type of expression returned must match the type of the function,
or be capable of being converted to it as if an assignment statement
were in use. For example, a function declared to return
return (1); and the integral value will be converted to 4.2.3. Arguments to functionsBefore the Standard, it was not possible to give any information about
a function's arguments except in the definition of the function itself.
The information was only used in the body of the function and was
forgotten at the end. In those bad old days, it was quite possible to
define a function that had three
#include <stdio.h>
#include <stdlib.h>
main(){
void pmax(); /* declaration */
int i,j;
for(i = -10; i <= 10; i++){
for(j = -10; j <= 10; j++){
pmax(i,j);
}
}
exit(EXIT_SUCCESS);
}
/*
* Function pmax.
* Returns: void
* Prints larger of its two arguments.
*/
void
pmax(int a1, int a2){ /* definition */
int biggest;
if(a1 > a2){
biggest = a1;
}else{
biggest = a2;
}
printf("larger of %d and %d is %d\n",
a1, a2, biggest);
}Example 4.2What can we learn from this? To start with, notice the careful
declaration that The function declaration (in Now on to the function definition, where the body is supplied. The
definition shows that the function takes two arguments, which will be
known as In the function definition you don't have to specify the type
of each argument because they will default to
/* BAD STYLE OF FUNCTION DEFINITION */
void
pmax(a1, a2){
/* and so on */
The proper way to declare and define functions is through the use of prototypes. 4.2.4. Function prototypesThe introduction of function prototypes is the biggest change of all in the Standard. A function prototype is a function declaration or definition which includes information about the number and types of the arguments that the function takes. Although you are allowed not to specify any information about a function's arguments in a declaration, it is purely because of backwards compatibility with Old C and should be avoided. A declaration without any information about the arguments is not a prototype. Here's the previous example ‘done right’.
#include <stdio.h>
#include <stdlib.h>
main(){
void pmax(int first, int second); /*declaration*/
int i,j;
for(i = -10; i <= 10; i++){
for(j = -10; j <= 10; j++){
pmax(i,j);
}
}
exit(EXIT_SUCCESS);
}
void
pmax(int a1, int a2){ /*definition*/
int biggest;
if(a1 > a2){
biggest = a1;
}
else{
biggest = a2;
}
printf("largest of %d and %d is %d\n",
a1, a2, biggest);
}Example 4.3This time, the declaration provides information about the function
arguments, so it's a prototype. The names void pmax (int xx, int yy ); and then say that All the same, you can miss out the names if you want to. This declaration is entirely equivalent to the one above. void pmax (int,int); All that is needed is the type names. For a function that has no arguments the declaration is void f_name (void); and a function that has one void f_name (int,double,...); The ellipsis (...) shows that other arguments follow. That's useful
because it allows functions like int printf (const char *format_string,...) where the type of the first argument is ‘pointer to Once the compiler knows the types of a function's arguments, having seen them in a prototype, it's able to check that the use of the function conforms to the declaration. If a function is called with arguments of the wrong type, the presence of a prototype means that the actual argument is converted to the type of the formal argument ‘as if by assignment’. Here's an example: a function is used to evaluate a square root using Newton's method of successive approximations.
#include <stdio.h>
#include <stdlib.h>
#define DELTA 0.0001
main(){
double sq_root(double); /* prototype */
int i;
for(i = 1; i < 100; i++){
printf("root of %d is %f\n", i, sq_root(i));
}
exit(EXIT_SUCCESS);
}
double
sq_root(double x){ /* definition */
double curr_appx, last_appx, diff;
last_appx = x;
diff = DELTA+1;
while(diff > DELTA){
curr_appx = 0.5*(last_appx
+ x/last_appx);
diff = curr_appx - last_appx;
if(diff < 0)
diff = -diff;
last_appx = curr_appx;
}
return(curr_appx);
}Example 4.4The prototype tells everyone that The conversion of 4.2.5. Argument ConversionsWhen a function is called, there are a number of possible conversions that will be applied to the values supplied as arguments depending on the presence or absence of a prototype. Let's get one thing clear: although you can use these rules to work out what to do if you haven't used prototypes, it is a recipe for pain and misery in the long run. It's so easy to use prototypes that there really is no excuse for not having them, so the only time you will need to use these rules is if you are being adventurous and using functions with a variable number of arguments, using the ellipsis notation in the prototype that is explained in Chapter 9. The rules mention the default argument promotions and compatible type. Where they are used, the default argument promotions are:
The introduction of prototypes (amongst other things) has increased the need for precision about ‘compatible types’, which was not much of an issue in Old C. The full list of rules for type compatibility is deferred until Chapter 8, because we suspect that most C programmers will never need to learn them. For the moment, we will simply work on the basis that if two types are the same, they are indisputably compatible. The conversions are applied according to these rules (which are intended to be guidance on how to apply the Standard, not a direct quote):
The order of evaluation of the arguments in the function call is explicitly not defined by the Standard. 4.2.6. Function definitionsFunction prototypes allow the same text to be used for both the declaration and definition of a function. To turn a declaration: double some_func(int a1, float a2, long double a3); into a definition, we provide a body for the function:
double
some_func(int a1, float a2, long double a3){
/* body of function */
return(1.0);
}
by replacing the semicolon at the end of the declaration with a compound statement. In either a definition or a declaration of a function, it serves as a prototype if the parameter types are specified; both of the examples above are prototypes. The Old C syntax for the declaration of a function's formal arguments is still supported by the Standard, although it should not be used by new programs. It looks like this, for the example above:
double
some_func(a1, a2, a3)
int a1;
float a2;
long double a3;
{
/* body of function */
return(1.0);
}
Because no type information is provided for the parameters at the point where they are named, this form of definition does not act as a prototype. It declares only the return type of the function; nothing is remembered by the compiler about the types of the arguments at the end of the definition. The Standard warns that support for this syntax may disappear in a later version. It will not be discussed further. Summary
4.2.7. Compound statements and declarationsAs we have seen, functions always have a compound statement as their body. It is possible to declare new variables inside any compound statement; if any variables of the same name already exist, then the old ones are hidden by the new ones within the new compound statement. This is the same as in every other block-structured language. C restricts the declarations to the head of the compound statement (or ‘block’); once any other kind of statement has been seen in the block, declarations are no longer permitted within that block. How can it be possible for names to be hidden? The following example shows it happening:
int a; /* visible from here onwards */
void func(void){
float a; /* a different 'a' */
{
char a; /* yet another 'a' */
}
/* the float 'a' reappears */
}
/* the int 'a' reappears */Example 4.5A name declared inside a block hides any outer versions of the same name until the end of the block where it is declared. Inner blocks can also re-declare that name—you can do this for ever. The scope of a name is the range in which it has meaning. Scope starts from the point at which the name is mentioned and continues from there onwards to the end of the block in which it is declared. If it is external (outside of any function) then it continues to the end of the file. If it is internal (inside a function), then it disappears at the end of the block containing it. The scope of any name can be suspended by redeclaring the name inside a block. Using knowledge of the scope rules, you can play silly tricks like this one:
main () {}
int i;
f () {}
f2 () {}
Now The Standard has changed things slightly with respect to a function's formal parameters. They are now considered to have been declared inside the first compound statement, even though textually they aren't: this goes for both the new and old ways of function definition. So, if a function has a formal parameter with the same name as something declared in the outermost compound statement, this causes an error which will be detected by the compiler. In Old C, accidental redefinition of a function's formal parameter was a horrible and particularly difficult mistake to track down. Here is what it would look like:
/* erroneous redeclaration of arguments */
func(a, b, c){
int a; /* AAAAgh! */
}
The pernicious bit is the new declaration of a in the body of the
function, which hides the parameter called Footnotes1. Stroustrup B. (1991). The C++ Programming Language 2nd edn. Reading, MA: Addison-Wesley |
The C BookThis book is published as a matter of historical interest. Please read the copyright and disclaimer information. GBdirect Ltd provides up-to-date training and consultancy in C, Embedded C, C++ and a wide range of other subjects based on open standards if you happen to be interested. |
|
West Yorkshire Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday South East Regional Office
GBdirect Ltd
Training: 0800 651 0338 Please call between 0900 and 1700 (UK time) on Monday to Friday Please note: |