Search code examples
cgcccompiler-constructionlanguage-design

Does gcc actually treat prototypes as functions and do their parameters have memory allocated?


I have a strange specific question about the design of C and really about programming and language design in general.

This is the basis of it: If I called a function that I had only prototyped, not assigned, would I be making a call to the actual C function data structure? In other words would this be a genuine function in every sense, or would the prototype be treated by gcc in a somewhat representative way, likely using a different data structure? The specific point within that question is about whether memory is allocated for the parameters declared with the prototype and whether or not an empty scope is created.

Gcc won't let you do this of course but if it would write the same machine code it normally would and I did try to call to a function that had only been prototyped, would the failure be:

  1. prototypes aren't in all senses functions

  2. the parameters aren't actually declarations, so they don't represent allocated memory with proper addresses and normal behaviors

  3. since there were no curly brackets, gcc didn't, or couldn't, generate a scope for this "function" to be added to the stack, making the parameter declarations absurd since there is no scope for them to be declared in (thus they aren't - thus no addresses)

  4. there is a scope created, it's contents could otherwise go on the stack, but execution dies because there were no instructions in the function block to advance the program in memory

  5. you can technically think of, and treat prototypes exactly like functions, problem is they don't do anything!

  6. something else I have completely missed

I have no idea why this question matters to me - but I guess if anything matters than everything matters - and it's kinda driving me crazy...

Thanks all!


Solution

  • When you use gcc without the -c flag, it does two things: it compiles the source files to object and files and then it links the object files into the final executable (or library) file. So in that sense you can think of it as two tools, in fact for the linking step gcc does call the separate tool ld.

    Now what happens when gcc sees a function prototype? It stores information about the function's signature in its internal data structures, so it knows how to typecheck calls to the function and how to generate code for the function calls (depending on the types, it might have to insert code for implicit conversions and the generated code looks different when calling variadic functions for example). A prototype does not cause any actual code to be generated.

    When gcc sees an actual function definition, it also stores the same information in its internal data structures, but it also generates code for the function's body and stores the name of the function and the address of the generated code in the object's symbol table.

    Now for function calls the compiler does the same thing for a merely-prototyped function as it does for an actually implemented function. In fact, it doesn't even know whether a definition for a function exists or not because the compiler only sees one c file (or rather one compilation unit) at a time and the definition might well live in another file. So what does the compiler do? It pushes the arguments onto the system stack and/or stores them in registers, depending on the number and types of arguments and the calling convention. Then it adds a call to the function using the function's name as a symbol.

    This will work regardless of whether all the functions are defined or not. You will not get an error for undefined functions if you only do gcc -c.

    Now what does ld do? It goes through all the object files and copies their contents together into the final executable or library file. While doing so it replaces the symbolic names of functions and variables with their actual address in the executable. This is the part where you get an error if a function is not defined.

    So what would happen if it did let you call undefined functions? Well, it can't. It doesn't refuse to create an executable when you call undefined functions as a sort of sanity check, it refuses to create the executable because it can't. When there's no function definition, there's no address with which to replace the symbol. So it's just not possible to link the files.

    So I guess the answer is "a": Prototypes aren't functions in the sense that they don't exist in the resulting object file at all. A function will only exist in the object file that contains its actual definition and if such a file does not exist (or there's more than one), that's an error.