Undefined behavior with deprecated function declarations in ANSI C


    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 in order to eliminate the disadvantages common function declarations possess.


    Thus, specifying a list of parameter types in parentheses of a function prototype is mandatory, otherwise such an expression will be recognized by the compiler as an obsolete function declaration, which can lead to ambiguous situations described in this article.


    Outdated prototypes


    A function declaration introduces the return type of the function and its identifier into the specified scope . Note that not all function declarations can be considered prototypes, but only those that have a list of input parameter types.


    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);

    Deprecated Definitions


    Let's go straight to 1972 (the year the C language was released) and remember how the programmers of that time defined their functions. Let me remind you that the definition of a function connects its signature with the corresponding executable block (body). This code demonstrates the definition of a function addin 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 entry, parentheses identify the function, but do not contain any types of input parameters. The “classic” function declarations described in the previous section have the same property.


    Ambiguous situations


    It is possible that if the new syntax of prototypes and function definitions introduced by the ANSI C standard are not observed, difficult ambiguous situations may arise. Consider an example:


    #include 
    #include 
    #include 
    #include 
    /* Устаревшее объявление функции "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);
    }

    We will analyze this program. The correct function itself is print_numberdeclared without specifying a list of parameter types, so you can 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 flag, the -Wallcompiler gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0did not generate any warnings (but it would be highly desirable).


    Fixing this program will not be difficult, just add the double numberfunction declarations print_numberon the seventh line in parentheses , after which any compiler that follows the standard will indicate errors in the function main():


    $ 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);
          ^~~~~~~~~~~~

    Functions without parameters


    I also note that specifying the keyword voidin parentheses of prototypes and definitions of functions that do not accept parameters is extremely desirable (but not necessary). If this rule is not observed, the compiler will not be able to check the correspondence of the types of the passed arguments when the function is called with valid types from the definition.


    #include 
    /*  Устаревшее объявление функции "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!");
    }

    Correct the above code by inserting a keyword voidin the definition and declaration of the function do_something(), otherwise this program will compile without errors. In this example, the function is main()also defined with a token voidin the parameters, although this is not necessary.


    Conclusion


    The writing of this article was inspired by Stephen Prat's book "C programming language. Lectures and exercises. Sixth edition", and specifically the section "Functions with arguments" of the fifth chapter.


    Also popular now: