4

Recently I've learnt about implicit function declarations in C. The main idea is clear but I have some troubles with understanding of the linkage process in this case.

Consider the following code ( file a.c):

#include <stdio.h>

int main() {
    double someValue = f();
    printf("%f\n", someValue);
    return 0;
}

If I try to compile it:

gcc -c a.c -std=c99

I see a warning about implicit declaration of function f().

If I try to compile and link:

gcc a.c -std=c99

I have an undefined reference error. So everything is fine.

Then I add another file (file b.c):

double f(double x) {
    return x;
}

And invoke the next command:

gcc a.c b.c -std=c99

Surprisingly everything is linked successfully. Of course after ./a.out invocation I see a rubbish output.

So, my question is: How are programs with implicitly declared functions linked? And what happens in my example under the hood of compiler/linker?

I read a number of topics on SO like this, this and this one but still have problems.

Community
  • 1
  • 1
Edgar Rokjān
  • 17,245
  • 4
  • 40
  • 67

4 Answers4

5

First of all, since C99 , implicit declaration of a function is removed from the standard. compilers may support this for compilation of legacy code, but it's nothing mandatory. Quoting the standard foreword,

  • remove implicit function declaration

That said, as per C11, chapter §6.5.2.2

If the function is defined with a type that does not include a prototype, and the types of the arguments after promotion are not compatible with those of the parameters after promotion, the behavior is undefined.

So, in your case,

  • the function call itself is implicit declaration (which became non-standard since C99),

  • and due to the mismatch of the function signature [Implicit declaration of a function were assumed to have an int return type], your code invokes undefined behavior.

Just to add a bit more reference, if you try to define the function in the same compilation unit after the call, you'll get a compilation error due to the mismatch signature.

However, your function being defined in a separate compilation unit (and missing prototype declaration), compiler has no way to check the signatures. After the compilation, the linker takes the object files and due to the absence of any type-checking in the linker (and no info in object files either), happily links them. Finally, it will end up in a successful compilation and linking and UB.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261
3

Here is what is happening.

  1. Without a declaration for f(), the compiler assumes an implicit declaration like int f(void). And then happily compiles a.c.
  2. When compiling b.c, the compiler does not have any prior declaration for f(), so it intuits it from the definition of f(). Normally you would put some declaration of f() in a header file, and include it in both a.c and b.c. Because both the files will see the same declaration, the compiler can enforce conformance. It will complain about the entity that does not match the declaration. But in this case, there is no common prototype to refer to.
  3. In C, the compiler does not store any information about the prototype in the object files, and the linker does not perform any checks for conformance (it can't). All it sees is a unresolved symbol f in a.c and a symbol f defined in b.c. It happily resolves the symbols, and completes the link.
  4. Things break down at run time though, because the compiler sets up the call in a.c based on the prototype it assumed there. Which does not match what the definition in b.c looks for. f() (from b.c) will get a junk argument off the stack, and return it as double, which will be interpreted as int on return in a.c.
Ziffusion
  • 8,779
  • 4
  • 29
  • 57
  • 1
    I think that compiler assumes `int f()` instead of `int f(void)` when dealing with implicit declaration. – Edgar Rokjān Jan 04 '16 at 20:06
  • I'll check, but given that the call in `a.c` has no arguments, it would make sense to assume `int f(void)` because `int f()` means `any number of arguments`. – Ziffusion Jan 04 '16 at 20:18
  • 2
    The implicit declaration is invalid in modern C and is UB. Implicit one is not provided like `int f(void);`. It only provides a declaration like `int f();` Compiler doesn't "intuit" when compiling `b.c`and it doesn't need to have a prototype defined before. A function definition also provides its prototype. If a prototype was provided before, it's checked against. Otherwise, not. "Common prototype" is not a thing. A prototype is either available or not. It's irrelevant how that compiler gets that info (through a header file or actual definition etc). – P.P Jan 04 '16 at 21:09
  • While (3) and (4) are good, it's simply defined as *undefined behaviour* in the standard. – P.P Jan 04 '16 at 21:09
  • @I3x All this is peripheral to the question, and mostly nitpicking the way I phrased some things. But whatever. – Ziffusion Jan 04 '16 at 21:40
2

How are programmes with implicitly declared functions are linked? And what happens in my example under the hood of compiler/linker?

The implicit int rule has been outlawed by the C standard since C99. So it's not valid to have programs with implicit function declarations.

It's not valid since C99. Before that, if a visible prototype is not available then the compiler implicitly declares one with int return type.

Surprisingly everything is linked successfully. Of course after ./a.out invocation I see a rubbish output.

Because you didn't have prototype, compiler implicitly declares one with int type for f(). But the actual definition of f() returns a double. The two types are incompatible and this is undefined behaviour.

This is undefined even in C89/C90 in which the implicit int rule is valid because the implicit prototype is not compatible with the actual type f() returns. So this example is (with a.c and b.c) is undefined in all C standards.

It's not useful or valid anymore to have implicit function declarations. So the actual detail of how compiler/linker handles is only of historic interest. It goes back to the pre-standard times of K&R C which didn't have function prototypes and the functions return int by default. Function prototypes were added to C in C89/C90 standard. Bottom line, you must have prototypes (or define functions before use) for all functions in valid C programs.

P.P
  • 117,907
  • 20
  • 175
  • 238
  • This is not complete. The problem has nothing to do with implicit declaration, but is due to the fact that the linker knows nothing about types, he found a definition of a function named `f` and that is suitable for a link. To prevent such a problem, some languages use name mangling to code type into the name of the function; but this is not the case in C. Having prototypes is not sufficient, you need consistent prototypes! – Jean-Baptiste Yunès Jan 04 '16 at 20:31
  • @Jean-BaptisteYunès I have explained that types of implicit prototype and the actual is incompatible and UB is invoked as a result f that.There's no such thing as "consistent prototype". A prototype is a complete info about the type of function's return and its parameters. – P.P Jan 04 '16 at 20:38
  • @i3x but the big part of the question (I think) was why does the link complete, and the details of the linking process that explained that. – Ziffusion Jan 04 '16 at 20:39
  • @Jean-BaptisteYunès yep, the main misunderstanding was about linking problem in this case. I read answers of Sourav and l3x consequently. So they mixed in my head a little. Now Sourav's answer seems to be more complete. – Edgar Rokjān Jan 04 '16 at 20:54
  • @Ziffusion I am saying it's UB and it compiled and linked due to the ancient rule of C. Btw, I just read your answer and it's not entirely accurate. – P.P Jan 04 '16 at 20:58
  • @i3x I think you miss the point. The main causes are: inconsistent prototypes (implicit declaration border effect) and type-free linking. Both have nothing to do with "ancient rules of C". – Jean-Baptiste Yunès Jan 05 '16 at 05:39
1

After compiling, all type information is lost (except maybe in debug info, but the linker doesn't pay attention to that). The only thing that remains is "there is a symbol called "f" at address 0xdeadbeef".

The point of headers is to tell C about the type of the symbol, including, for functions, what arguments it takes and what it returns. If you mismatch the real ones with the ones you declare (either explicitly or implicitly), you get undefined behavior.

Colonel Thirty Two
  • 23,953
  • 8
  • 45
  • 85