Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Leaving out forward declarations (prototypes)

Tags:

c

function

I'm up to lesson 14 on the famous "Learn C The Hard Way" online course.

In that lesson, it introduces the concept of forward declarations in C. There are two forward declarations in the code sample. One of them can be commented out, and the code still compiles, but the other one cannot be commented out. To me they both look equally as important.

Here's the code. It simply prints out all characters and their hex codes if they are from the alphabet, otherwise it skips them.

The two compiler outputs are at the bottom of the code. Could someone explain why one errors out and the other one doesn't?

#include <stdio.h>
#include <ctype.h>

// forward declarations
int can_print_it(char ch);       //NOT OK to skip(??)
void print_letters(char arg[]);  //OK to skip(??)

void print_arguments(int argc, char *argv[])
{
    int i = 0;

    for(i = 0; i < argc; i++) {
        print_letters(argv[i]);
    }
}

void print_letters(char arg[])
{
    int i = 0;

    for(i = 0; arg[i] != '\0'; i++) {
        char ch = arg[i];

        if(can_print_it(ch)) {
            printf("'%c' == %d ", ch, ch);
        }
    }

    printf("\n");
}

int can_print_it(char ch)
{
    return isalpha(ch) || isblank(ch);
}


int main(int argc, char *argv[])
{
    print_arguments(argc, argv);
    return 0;
}

If I comment out the first forward declaration (first one only), this happens:

cc -Wall -g    ex14.c   -o ex14
ex14.c: In function ‘print_letters’:
ex14.c:24:9: warning: implicit declaration of function ‘can_print_it’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration
ex14.c:24:12: note: previous implicit declaration of ‘can_print_it’ was here
make[1]: *** [ex14] Error 1
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'
make: *** [all] Error 2

And if I comment out the second declaration (second one only), this happens:

cc -Wall -g    ex14.c   -o ex14
ex14.c: In function ‘print_arguments’:
ex14.c:13:9: warning: implicit declaration of function ‘print_letters’ [-Wimplicit-function-declaration]
ex14.c: At top level:
ex14.c:17:6: warning: conflicting types for ‘print_letters’ [enabled by default]
ex14.c:13:9: note: previous implicit declaration of ‘print_letters’ was here
make[1]: Leaving directory `/home/andrew/c_tutorials/lesson14/ex14_original'
like image 670
Andy J Avatar asked May 28 '14 04:05

Andy J


People also ask

What is difference between function prototype and forward declaration?

A function prototype is a declaration statement that includes a function's name, return type, and parameters. It does not include the function body. What is a forward declaration? A forward declaration tells the compiler that an identifier exists before it is actually defined.

What is the difference between prototype and declaration?

A function declaration is any form of line declaring a function and ending with ; . A prototype is a function declaration where all types of the parameters are specified.

Why are forward declarations necessary?

Forward declaration is used in languages that require declaration before use; it is necessary for mutual recursion in such languages, as it is impossible to define such functions (or data structures) without a forward reference in one definition: one of the functions (respectively, data structures) must be defined ...

Is forward declaration good practice?

The Google style guide recommends against using forward declarations, and for good reasons: If someone forward declares something from namespace std, then your code exhibits undefined behavior (but will likely work).


2 Answers

Well compiler hints why this does happen. The crucial thing it here:

ex14.c:32:5: error: conflicting types for ‘can_print_it’
ex14.c:33:1: note: an argument type that has a default promotion can’t match an empty parameter name list declaration

The argument for can_print_it has a default promotion, therefore it cannot have an implicit declaration. Great read on it is here: Default argument promotions in C function calls. Basically, the argument type for can_print_it (char) is illegal to be used with implicit declarations. To make it work, you would need to use appropriate type, for char it is int. For other types you can check out the linked question and answer.

The print_letters has no such arguments its argument is of a pointer type.

Side-note: As one could see, with 3 wrong answers, people get confused. Implicit declarations are not used often and can be tricky. IMO in general, or at least practical applications, their usage is discouraged. Nevertheless, they are perfectly legal.

like image 169
luk32 Avatar answered Sep 28 '22 03:09

luk32


You give a function prototype so the compiler knows what to do when it first comes across that function in your code. Specifically, if it has no other information, the compiler will

  • assume that the return value is an int
  • promote arguments:
    • "integer types" to int (so char becomes int, for example)
    • promote float to double
    • pointers become pointers to int

The problem is that when you convert a char to an int, it is possible that the significant byte ends up offset by (for example) 3 bytes from where you thought you stored it - since a value like 0x33 might be stored as 0x00000033. Depending on the architecture of the machine, this will cause a problem.

The same is not true with pointers. A pointer "always" has the same size, and always points to the first byte of the object (this wasn't always true... some of us remember "near" and "far" pointers, and not with nostalgia). Thus, even though the compiler may think it is passing a pointer to an int, the subsequent interpretation (by the function-that-had-not-been-declared) as a pointer to a char does not cause a problem.

The fact that your second function is declared as void when the compiler assumed it would return int does not matter, since you never used its return value (which it doesn't have) in an assignment or expression. So even though it's a bit confusing for the compiler, this generates only a warning, not an error. And since the argument is a pointer, the promotion rules again don't cause a conflict.

That said - it is a good idea to always declare your function prototypes before using them; in general, you should turn on all compiler warnings, and improve your code until it compiles without warnings or errors.

like image 40
Floris Avatar answered Sep 28 '22 04:09

Floris