Is the header file declaration significant?

Is the header file declaration significant? This code:

main() { int i=100; printf("%d\n",i); } 

it seems to work, the output I get is 100. Even without using the stdio.h header file. How is this possible?

+4
source share
5 answers

How is this possible? In short: three good luck.

This is possible because some compilers will make assumptions about undeclared functions. In particular, the parameters are considered int , and the return type is also int . Since int often the same size as char* (depending on architecture), you can get away with passing int and strings, since the correct dimensional parameter will be pushed onto the stack.

In your example, since printf not declared, it is assumed that it takes two int parameters, and you passed char* and int , which are "compatible" in terms of the call. Therefore, the compiler shrugged and created code that was supposed to be right. (That really should have warned you of an undeclared feature.)

So, the first luck was that the compiler assumption is compatible with the real function.

Then, at the linker stage, since printf is part of the C standard library, the compiler / linker will automatically include this in the link stage. Since the printf character really was in C stdlib, the linker resolved the character, and everything was fine. Binding was the second luck, since a function independent of the standard library also needed its library.

Finally, at runtime, we see your third luck. The compiler made a blind guess, the symbol was apparently bound by default. But - at runtime, you could easily transfer data in such a way as to break your application. Fortunately, the parameters matched, and in the end it was all over. This, of course, will not always be so, and I believe that the above would probably fail on a 64-bit system.

So - to answer the original question, it is really important to include the header files, because if it works, then only through blind luck!

+9
source

You do not need to include a header file. Its purpose is to provide the compiler with all the information about stdio , but this is by no means necessary if your compiler is smart (or lazy).

You must enable it because it is a good habit to join - if you do not, then the compiler has no real way to find out if you are breaking the rules, for example:

 int main (void) { puts (7); // should be a string. return 0; } 

which compiles without problems, but correctly resets the kernel at startup. Change it to:

 #include <stdio.h> int main (void) { puts (7); return 0; } 

will cause the compiler to warn you about something like:

 qq.c:3: warning: passing argument 1 of 'puts' makes pointer from integer without a cast 

A good compiler can warn you about this, for example, gcc knows what printf should look like, even without a header:

 qq.c:7: warning: incompatible implicit declaration of built-in function 'printf' 
+14
source

As paxidiablo said, it is not needed, but this is only true for functions and variables, but if your header file provides some types or macros (#define) that you use, you must include the header file to use them, because they are necessary before binding occurs, i.e. during preprocessing or compilation

0
source

This is possible because when the C compiler sees an undeclared function call (printf () in your case), it assumes that it has

 int printf(...) 

signature and tries to call it casting all the arguments to int type. Since "int" and "void *" types often have the same size it works most of the time. But it is not wise to rely on such behavior.

0
source

C supports three types of function argument forms:

  • Known fixed arguments: this is when you declare a function with arguments: foo(int x, double y) .
  • Unknown fixed arguments: this is when you declare it as empty parentheses: foo() (not to be confused with foo(void) : this is the first form without arguments) or not to declare it at all.
  • Variable arguments: this is when you declare it with an ellipsis: foo(int x, ...) .

When you see the standard function operation, then the function definition (which is in form 1 or 3) is compatible with form 2 (using the same calling rule). Many old std. library functions in the same way (as they were canceled) because they exist in early versions of C, where there were no function declarations, and all of them were in form 2. Another function may be inadvertently compatible with form 2 if they have arguments as declared in the argument promotion rules for this form. But some may not be like that.

But the programmer of form 2 must pass arguments of the same types everywhere, because the compiler cannot check the arguments with the prototype and must determine the calling arguments osing the actual arguments passed.

For example, on the MC68000, the first two integer arguments for the fixed arg functions (for both forms 1 and 2) will be passed to the registers D0 and D1 , the first two pointers to A0 and A1 , all the rest passed through the stack. So, for example, the function fwrite(const void * ptr, size_t size, size_t count, FILE * stream); will receive arguments like: ptr in A0 , size in D0 , count in D1 and stream in A1 (and will return the result in D0 ). When you include stdio.h , it will be so no matter what you pass.

When you do not enable stdio.h , something else happens. When you call fwrite with fwrite(data, sizeof(*data), 5, myfile) , the compiler looks at the arguments and sees that the function is called fwrite(*, int, int, *) . So what is he doing? It passes the first pointer to A0 , first int to D0 , second int to D1 and second pointer to A1 , so we need this.

But when you try to call it fwrite(data, sizeof(*data), 5.0, myfile) , with count has a double type, the compiler will try to pass count through the stack, since it is not an integer. But the function is required in D1 . Damn: " D1 contains garbage, not count , so the further behavior is unpredictable. But than you use the prototype defined in stdio.h , everything will be fine: the compiler automatically converts this argument to int and passes it as needed. This is not abstract an example, since double in arument might just be the result of a calculation using floating point numbers, and you can just skip this supposed int result.

Another example is the function of variable arguments (form 3), for example printf(char *fmt, ...) . This convention call requires the last named argument ( fmt here) to be passed through its type stack. So then you call printf("%d", 10) , it will put a pointer to "%d" and number 10 on the stack and call the function as needed.

But when you do not include stdio.h , the compiler will not know that printf is a vararg function and suppose that printf("%d", 10) calls a function with fixed arguments of type pointer and int. Thus, the MC68000 will put a pointer to A0 and int in D0 instead of the stack, and the result is again unpredictable.

Maybe good luck with the fact that the arguments were previously on the stack and sometimes read there, and you get the correct result ... this time ... but another time will fail. Another luck is that the compiler takes care if the undeclared function can be vararg (and somehow makes the call compatible with both forms). Or all arguments in all forms are simply passed through the stack on your computer, so fixed, unknown, and vararg forms are simply called identically.

So: do not do this, even if you are lucky and it works. An unknown form of fixed argument exists only for compatibility with old code and is strongly discouraged.

Also note: C++ will not allow this at all, since it requires a function to be declared using well-known arguments.

0
source

Source: https://habr.com/ru/post/1332079/


All Articles