tags:

views:

239

answers:

6

I was reading wikipedia on C/C++ Prototype statements and I'm confused:

Wikipedia say: "By including the function prototype, you inform the compiler that the function "fac" takes one integer argument and you enable the compiler to catch these kinds of errors."

and uses the below as an example:

#include <stdio.h>

 /* 
  * If this prototype is provided, the compiler will catch the error 
  * in main(). If it is omitted, then the error will go unnoticed.
  */
 int fac(int n);              /* Prototype */

 int main(void) {             /* Calling function */
     printf("%d\n", fac());   /* ERROR: fac is missing an argument! */
     return 0;
 }

 int fac(int n) {             /* Called function  */
     if (n == 0) 
         return 1;
     else 
         return n * fac(n - 1);
}

But the function definition of the called function already includes all the information that the prototype tells the compiler, so why can't the compiler deduce this information from the called function's definition since they contain identical statements/information letter for letter?

What am I missing? Seems like extra work for no obvious gain.

Edit: Thanks guys. I assumed the compilers were multi-pass I guess. I'm spoiled to current languages like Python. It makes sense since it's so old to need some kludges to do things accurately in a single pass. It seems more obvious to me now. Apparently it requires fairly intimate knowledge of how the compiler links and compiles.

+2  A: 

The C compiler processes source files from top to bottom. Functions that appear after their use are not considered when resolving argument types. So, in your example, if main() was at the bottom of the file then you would not need a prototype for fac() (since the definition of fac() would already have been seen by the compiler when compiling main()).

Greg Hewgill
+16  A: 

Two reasons:

  1. The compiler reads the file top-to-bottom. If fac is used in main which is above fac, and no prototype exists, the compiler doesn't know how to check that that call is being done correctly, since it hasn't yet reached the definition of fac.

  2. It's possible to split up a C or C++ program into multiple files. fac may be defined in a completely different file from the file that the compiler is currently processing, and so it needs to know that that function exists somewhere, and how it is supposed to be called.

Note that the comments in the example you posted only apply to C. In C++, that example will always produce an error, even if the prototype is omitted (although it will produce a different error depending on whether the prototype exists or not). In C++, all functions are required to be defined or prototyped before being used.

In C, you can omit the prototype and the compiler will allow you to call the function with any number of arguments (including zero), and will assume a return type of int. But just because it doesn't yell at you during compilation doesn't mean that the program will work right if you don't call the function the right way. That's why it's useful to prototype in C: so the compiler can double-check on your behalf.

The philosophy behind C and C++ that motivates this kind of feature is that these are relatively low-level languages. They don't do a lot of hand-holding and they don't do much if any run-time checking. If your program does something incorrect, it will crash or behave bizarrely. Therefore, the languages incorporate features like this that enable the compiler to identify certain types of errors at compile-time, so that you can more easily find and fix them.

Tyler McHenry
In case #2 wouldn't includes or something of that type tell it which file to find the real function in? In python you'd just import module and then reference the function as module.function, does c not have namespaces that can be referenced like this?
pythonnewbie
How does it resolve #1? Nothing in the prototype tells the compiler that fac calls main and/or that main calls fac?
pythonnewbie
In case #2, what do you think the includes contain? They contain prototypes! But if you're writing your own `fac` function, *you* need to provide the prototype, whether it's in a seperate included file or in the same source file. In case #1, (I assume you're referring to Mystagogue's answer here), the problem is not that there's anything wrong with a recursive dependency, it's that if you do have a recursive dependency, there's no way to order the the two functions such that the compiler reads the definition of both before processing a call to either.
Tyler McHenry
The declaration of a function often resides in a header file, and it's implementation on a source file. The source file is compiled to yield an object file. Other source files or programs that want to use the function just include the header and then link the object file. This way, the function has to be compiled only once, but can be used many times in many source-files and programs.
Space_C0wb0y
Essentially, all of your questions require an understanding of the C/C++ compiler/linker, and how C/C++ compiles and then links together multiple `.cpp` or `.c` files in a project. `#include` only includes header files, which normally only contain prototypes. The actual function may be written in another .cpp file or .c file. In order for the compiler to realize "okay, this is a real function and not a syntax error", it needs a prototype in the header file to tell it.
Charles Salvia
@pythonnewbie: Both (and many more) issues are easily resolved now, but remember that C dates back to the 70s. Sure, the language could have required a multi-pass compiler that has access to all used `.cpp` files (as opposed to the headers with the prototypes only and the already-compiled object files with the implementation) so it can deduce function signatures and everything. But that was pretty damn expensive back then, so they didn't.
delnan
@Delnan, your comment turned my lightbulb on the most. Too bad it's only a comment. Thanks for the insight. Didn't know it was trying to compile in a single-pass. How...archaic :P
pythonnewbie
@pythonnewbie: Some might say "elegant" ;)
caf
Actually, the whole design is multi-pass, in a sense: the preprocessor pass and the linker pass are separate. Remember, back in the day you would have issues fitting everything in 64 KB. Compiling source files one at a time kept the amount of RAM needed low. The only component that saw all parts was the linker, and C is designed to work with dumb (=memory-efficient) linkers.
MSalters
+1  A: 

The most important reason for the prototype, is to resolve circular dependencies. If "main" can call "fac," and "fac" calls "main," then you will need a prototype to resolve that.

Brent Arias
+6  A: 

Prototypes let you separate interface from implementation.

In your example, all of the code lives in one file, and you could just as easily have moved the fac() definition up where the prototype is currently and removed the prototype.

Real-world programs are composed of multiple .cpp files (aka compilation units), frequently compiled and linked into libraries before being linked into final executable form. For large scale projects of that nature, prototypes are collected into .h files (aka header files), where the header is included in other compilation units at compile time to alert the compiler to the existence and calling conventions of functionality in the library. In these cases, the function definition is not available to the compiler, so the prototypes (aka declarations) serve as a sort of contract defining the library's capabilities and requirements.

Drew Hall
A: 

Besides all the good answers already given, think about that: if you were a compiler, and your job was to translate source code in machine language, and you (being the dutiful compiler that you are) could only read a source code line by line -- how would you read the code you pasted if there was no prototype? How would you know that the function call is valid and not a syntax error? (yes, you could make a note and check at the end if everything matched, but that's another story).

Another way to look at it (this time as a human being): suppose you do not have the function defined as a prototype, nor is its source code available. You know, however, that in the library that your mate gave you there is the machine code that, when ran, returns a certain expected behaviour. How nice. Now, how would you compiler know that such function call is valid if there is no prototype telling it "hey dude trust me, there is a function named such and such that takes parameters and returns something"?

I know it's a very, very, very simplicistic way of thinking about it. Adding intentionality to pieces of software is probably a bad sign, isn't it?

lorenzog
+1  A: 

C and C++ are two different languages, and in this particular case there is a huge difference between the two. From the contents of the question I assume that you are talking about C.

#include <stdio.h>
int main() {
   print( 5, "hi" );  // [1]
}
int print( int count, const char* txt ) {
   int i;
   for ( i = 0; i < count; ++i ) 
      printf( "%s\n", txt );
}

That is a proper C program that does what you can expect: prints 5 lines saying "hi" in each one of them. The C language finds the call at [1] it assumes that print is a function that returns int and takes an unknown number of arguments (unknown to the compiler, known to the programmer), the compiler assumes that the call is correct and continues compiling. Since the function definition and the call match, the program is well formed.

The problem is that when the compiler parses the line at [1] it cannot perform any type of type checking, as it does not know what the function is. If when we write this line we mistake the order of the arguments and we type print( "hi", 5 ); the compiler will still accept the line, as it has no prior knowledge of print. Since the call is incorrect even if the code compiles it will fail later on.

By declaring the function beforehand, you provide the compiler with the needed information to check at the place of call. If the declaration is present, and the same mistake is made the compiler will detect the error and inform you of your mistake.

In C++, on the other hand, the compiler will not assume that the call is correct and will actually require you to provide a declaration of the function before the call.

David Rodríguez - dribeas