The ANSI C standard defines the concept of a function prototype , which is a subset of a function declaration that indicates the types of input parameters. Prototypes were introduced to eliminate the flaws that ordinary function declarations have.
Thus, specifying a list of parameter types in parentheses of a function prototype is necessary, otherwise the expression will be recognized by the compiler as an outdated function declaration, which can lead to ambiguous situations described in this article.
A function declaration enters the return type of the function and its identifier into the specified scope . Please note that not all function declarations can be considered prototypes, but only those that have a list of types of input parameters.
Thus, the first expression of the code below is a declaration, but not a function prototype. The following expression can rightfully be considered a prototype, since it specifies the types of its parameters:
/* #1 ( "foo") */ void foo(); /* #2 ( "bar") */ void bar(int count, const char *word);
Let's go straight to 1972 (the year when the C language was released) and recall how the programmers of that time defined their functions. Let me remind you that the definition of a function associates its signature with the corresponding executable block (body). This code demonstrates the definition of the add
function in the K & R style:
void add(right, left, result) int right; int left; int *result; { *result = right + left; }
As you may have noticed, in this record, parentheses identify the function, but do not contain any types of input parameters. The same property has the "classic" function declarations, described in the previous section.
It is possible that if the new syntax of prototypes and definitions of functions introduced by the ANSI C standard is not followed, it is possible that difficult ambiguous situations can arise. Consider an example:
#include <stdio.h> #include <stdint.h> #include <inttypes.h> #include <limits.h> /* "print_number" */ void print_number(); int main(void) { /* */ print_number((double)13.359); print_number((double)9238.46436); print_number((double)18437); /* */ print_number(UINT64_MAX); print_number("First", "Second", "Third"); print_number(NULL, "Breakfast", &print_number); } void print_number(double number) { printf(" : [%f]\n", number); }
Let's analyze this program. The correct print_number
function print_number
declared without specifying a list of parameter types, as a result of which you are able to call this function with any arguments. The program compiled without errors and printed the following result:
$ gcc illegal.c -o illegal -Wall $ ./illegal : [13.359000] : [9238.464360] : [18437.000000] : [0.000000] : [0.000000] : [0.000000]
Also note that even with the -Wall
flag, the -Wall
compiler gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
did not generate any warnings (but it would be highly desirable).
It will not be difficult to print_number
this program; all you have to do is to add a double number
in parentheses to the print_number
function print_number
on the seventh line, after which any compiler following the standard will indicate errors in the main()
function:
$ gcc -Wall illegal.c -o illegal illegal.c: In function 'main': illegal.c:17:22: error: incompatible type for argument 1 of 'print_number' print_number("First", "Second", "Third"); ^~~~~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'char *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:17:9: error: too many arguments to function 'print_number' print_number("First", "Second", "Third"); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:22: error: incompatible type for argument 1 of 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~ illegal.c:7:6: note: expected 'double' but argument is of type 'void *' void print_number(double number); ^~~~~~~~~~~~ illegal.c:18:9: error: too many arguments to function 'print_number' print_number(NULL, "Breakfast", &print_number); ^~~~~~~~~~~~ illegal.c:7:6: note: declared here void print_number(double number); ^~~~~~~~~~~~
Also note that the indication of the keyword void
in parentheses prototypes and definitions of functions that do not take parameters is highly desirable (but not necessarily). Failure to comply with this advice can lead to equally sad consequences.
#include <stdio.h> /* "do_something" */ void do_something(); int main(void) { /* "do_something" */ do_something(NULL, "Papa Johns", 2842, 1484.3355); } void do_something() { puts("I am doing something interesting right now!"); }
To correct the code above, it is necessary to insert the void
keyword in the definition and declaration of the do_something()
function; otherwise, this program will compile without errors. In this example, the main()
function is also defined with the void
token in the parameters, although this is not necessary.
I was inspired to write this article by Stephen Prath’s book “The C programming language. Lectures and exercises. The sixth edition”, and specifically the section “Functions with arguments” of the fifth chapter.
Source: https://habr.com/ru/post/456682/
All Articles