2
#include<stdio.h>

int *arr;

int main()
{
   arr = calloc(1, sizeof(int));

   free(arr);
   return 0;
}

As far as I understand, this warning occurs because I did not declare the function in header (In this case I should've included stdlib.h). My questions are:

  1. Why doesn't the GCC give an error? Because as far as I understand, calloc is located at stdlib.h, but I didn't include it in my program. Why does my program still know what is calloc?

  2. Should we close our eyes at the warning? Because my program works well even without include of stdlib.h.

3
  • 1
    Re “Why does my program still know what is calloc?”: A warning that there is an implicit declaration of a function does not mean the compiler knew about your function previously. If you write y = foo(x);, then, when the compiler sees foo(, it can see you are using foo as a function and that you have not declared it, so it can warn you about foo from that, not from prior knowledge of foo. In the case of calloc, the compiler may have prior knowledge of calloc. This is because the names of standard library routines are reserved by the C standard for use with file scope. Commented Nov 26, 2022 at 13:40
  • 1
    … That means you should not use them for other purposes, and that allows knowledge about them to be built into the compilers—When the name of a standard library function is used, it ought to be used only for the standard library function. Commented Nov 26, 2022 at 13:41
  • 1
    Always compile with warnings enabled, and do not accept code until it compiles without warning. To enable warnings add -Wall -Wextra -pedantic to your gcc/clang compile string (also consider adding -Wshadow to warn on shadowed variables and -Werror to treat warnings as errors). For VS (cl.exe on windows), use /W3. All other compilers will have similar options. Read and understand each warning -- then go fix it. The warnings will identify any problems, and the exact line on which they occur. You can learn a lot by listening to what your compiler is telling you. Commented Nov 26, 2022 at 13:53

4 Answers 4

2

As far as I understand, this warning occurs because I did not declare the function in header (In this case I should've included <stdlib.h>).

You are correct. The compiler complains that you are calling a function for which it has not seen a declaration. Including <stdlib.h> does provide a proper declaration for the function calloc. Note that you could also have provided a declaration yourself by adding the line: void *calloc(size_t, size_t);. Yet it is recommended to use the standard include files to get the exact declarations for library functions.

Why doesn't the GCC give an error?

Because the compiler is lenient in order to compile older programs that were written before the C Standard was even published. It used to not be an error to call functions without a declaration or a definition in scope. The compiler would just infer the prototype from the types of the arguments provided. In your case the prototype is inferred as int calloc(int, size_t), which is obviously incorrect and will cause undefined behavior. To avoid such problems, the compiler issues a warning about calloc being undeclared.

Why does my program still know what is calloc?

It does not, the prototype is inferred from the call statement and this guess falls short. As a matter of fact, the compiler should also complain about the int return value being implicitly converted to an int * when stored to arr. If int * and int have a different representation on the target system (as is the case on 64-bit systems), the value of arr will be invalid and free(arr) will definitely have undefined behavior too. The compiler produces an executable because the function calloc is found in the C library, which is linked to all C programs implicitly. The argument and return types are not checked at link time for C programs.

Should we close our eyes at the warning?

Definitely not. You should compile your program with all warnings enabled using gcc -Wall -Wextra -Werror, and gcc will consider all warnings like fatal errors and refuse to produce an executable until they are corrected. This will save you many hours of debugging time. It is a pity this behavior is not the default one.

my program works well even without include of <stdlib.h>.

Well it appears to work on your system, but it fails on mine (assuming I remove my default compiler options that prevent gcc from making an executable). The program has undefined behavior. Do not rely on chance.

Here is a proper version:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = calloc(1, sizeof(*arr));  /* use object type for consistency */

    printf("pointer value is %p\n", (void *)arr);

    free(arr);
    return 0;
}
Sign up to request clarification or add additional context in comments.

2 Comments

"the function calloc is found in the C library, which is linked to all C programs implicitly." Then why can't the type of the return value and the type of the arguments be specified in the C library?
@RodionIskhakov: the types of the function arguments and return value is not specified in the library because of historical considerations. The creators of the language did not specify that and at a time where space was expensive, it was not considered important enough. Consistency was left for the programmer to verify. C++ conversely and to implement overload mechanisms, does include the argument types in the function symbol, but not the return type because overloading on return type is not supported.
1

calloc is not in stdlib.h. Nothing is in stdlib.h. Nothing is in any .h (or nothing should).

What is in stdlib.h is just (about calloc I mean)

extern void *calloc(size_t nmemb, size_t size);

calloc is in the libc. That is where your program will find it when it will call it.

Strictly speaking, you don't need anything else (I mean, from an execution point of view) to be able to call a function.

All .c codes are compiled to do their task. libc has been compiled long ago. And at linking time (for static libraries, and for dynamic to point to them) and then at run time, all functions from all compiled code are loaded. So your main from your code and calloc will find each other at that time.

The problem is not there.

The problem is that in order to compile your main, the compiler need to know how calloc is supposed to be called.

It needs to know it to check for syntax error. Otherwise you could pass 3 arguments, or only one, to calloc, and the compiler would have no way to know that this is not correct. You could pass arguments of the wrong type, and likewise.

It needs to know also to know how many bytes it is supposed to push as an argument, and even how it is supposed to pass arguments.

See for example these two codes

one.c

#include <stdio.h>

void printInt(int a, int b){
    printf("ints %d %d\n", a, b);
}

void printFloat(float a, float b){
    printf("floats %f %f\n", a, b);
}

void printDouble(double a, double b){
    printf("doubles %f %f\n", a, b);
}

two.c

#include <stdio.h>
int main(void) {
    printInt(1,2);
    printInt(1.0, 2.0);

    printFloat(1,2);
    printFloat(1.0,2.0);

    printDouble(1,2);
    printDouble(1.0,2.0);
}

Compile them with (depending on your compiler)

gcc -std=gnu99 -o main one.c two.c # There is an implicit -lc here including libc, that contains printf

On my computer it prints

ints 1 2
ints 1034078176 -2098396512
floats 0.000000 0.000000
floats 0.000000 0.000000
doubles 0.000000 0.000000
doubles 1.000000 2.000000

See that it works as intended only for printInt called with ints, and printDouble called with doubles. It fails to cast 1.0 as an int to call printInt, or on the contrary to cast 1 as a double to call printDouble. And For printFloat it fails in all cases, because the compiler assumed wrongly the size of the arguments to be pushed.

But other than that, those 3 functions are called. It is not the the code of the function that is missing. It is the ability of the compiler, when calling them, to call them correctly.

Just add, in two.c the declarations

extern void printInt(int, int);
extern void printFloat(float, float);
extern void printDouble(double, double);

(Or create a one.h containing those, and #include "one.h" in two.c, it leads to the same result)

And now the output is as expected

ints 1 2
ints 1 2
floats 1.000000 2.000000
floats 1.000000 2.000000
doubles 1.000000 2.000000
doubles 1.000000 2.000000

And I haven't even started with types declarations.

#include are not meant to provide libraries, and the functions that are in them. That you do while linking, adding .o and -lsomelib to the liking command line (or using other way, depending on your compiler).

#include are there to provide codeless declarations that the compiler needs to know how to call those functions.

4 Comments

Note that extern void *calloc(size_t nmemb, size_t size); is risky in a standard header file because nmemb and size could have been defined as macros, functions, variables or types before the #include <stdlib.h> directive. Argument names are either omitted or specified as comments or with leading underscores to avoid this pitfall.
Also note that the output doubles 1.000000 2.000000 from the invalid code is purely coincidental as the function prototype was inferred as int printDouble(int, int); from the first call. The 1.0 and 2.0 have not been passed to the function, they just happen to be in the places where the function expects them. printDouble(1.0,1.0+1.0); may well produce different output.
Re “Nothing is in stdlib.h”: That is not true. Declarations are in stdlib.h. Macro definitions are in stdlib.h. static inline definitions may be in stdlib.h. Those are things. You may mean there are no function definitions in stdlib.h (which is not necessarily true), but somebody just learning C is not going to know what you mean.
Re “calloc is in the libc”: libc is a name for the library file providing an implementation of the standard C library and environment in certain C implementations. It is not standard and is not universal.
1
  1. The function is not located at stdlib.h, it is in the same shared library object, libc.so so your linker has no problem finding the symbol. If you tried to call a made up function, you also wouldn't get a compiler error, rather your linker would complain about being unable to resolve a symbol.

  2. You technically can, but you absolutely shouldn't. If you wouldn't you wouldn't be warned about argument errors. The Linker does not (and can not) check, if you supplied the right amount and types of arguments.

Example:

// a.c
int asdf(int b){
   return b+1;
}

// main.c
int main(void) {
   asdf();
   return 0;
}

If you compile now: gcc main.c a.c You will only get a warning, while if you had included a header you would get an error. This is BAD, since it can lead to undefined behavior that can lead to unexpected crashes, memory corruption, security issues, etc.

You should always give your compiler a fair opportunity to help you.

EDIT: Clarify, that ignoring is bad

2 Comments

your example is somewhat misleading: with separate compilation, the programmer may indeed only get a warning, but the program still has undefined behavior. This warning should not be ignored as it is a useful indication of a potentially fatal problem.
@chqrlie Absolutely. This is exactly what I was trying to say. Didn't convey it that well though
0
  1. Why doesn't the GCC give an error? Because as far as I understand, calloc is located at stdlib.h, but I didn't include it in my program. Why does my program still know what is calloc?

For compatibility with legacy code, this is not an error, so compilation cannot be aborted, and so, you get a warning instead. In old legacy C code, you can use a function without a declaration if it returns an int result (which is assumed if you don't specify a prototype for the function). Old K&R code is still valid in C98 (I cannot ensure about later standard editions, but at least I have tested K&R code and it compiles with at most some warnings that can be ignored) and so it must be compiled into working code (but still probably invalid code, as the code generated to call calloc will consider an int is being returned---while calloc indeed returns a void * pointer type, which in 64bit architectures is 64bit)

Just try this code, and see :)

main(argc, argv)
char **argv;
{
    int i;
    char *sep = "";
    for (i = 0; i < argc; i++) {
        printf("%s[%s]", sep, *argv++);
        sep = ", ";
    }
    puts("");
}

The code above compiles without any problem in a pdp11 runing unix v7 (just tested before posting) and in gcc last version. Is shows you the argv command parameter list on stdout.

$ make pru$$
Make:  Don't know how to make pru31.  Stop.
[tty2]lcu@pdp-11 $ cc -o pru$$ pru$$.c
[tty2]lcu@pdp-11 $ pru$$ a b c d e f
[pru31], [a], [b], [c], [d], [e], [f]
[tty2]lcu@pdp-11 $ 

This is pdp11/45 UNIX(tm) V7
(C) 1978 AT&T Bell Laboratories.  All rights reserved.

Restricted rights: Use, duplication, or disclosure
is subject to restrictions stated in your contract with
Western Electric Company, Inc.

login: 
  1. Should we close our eyes at the warning? Because my program works well even without include of stdlib.h.

I think you should never close your eyes at any warning. If you just #include <stdlib.h>, the compiler will compile correctly the source code without a warning. The important thing about warnings is that YOU SHOULD READ THEM, AND UNDERSTAND THEM (and the consequences of ignoring them) BEFORE CONTINUING. A waning is just an advisory message, it doesn't interrupt the compilation because this can be the intended purpose of the compiler run (simply to compile old, legacy code, that will produce a valid program, but written a long time ago)

In the case above, you had better to do the proper #include <stdio.h>, because if you don't, your code will be erroneous. It will assume the calloc() function returns an int (which is not the same size in 64bit architectures) and the value will probably will be a truncated pointer (a pointer in which some part of it has been truncated by the conversion to an int) and will not work (but this happens recently, with 64bit architectures, it was fine on 32bit ones, incorrect but working) So, you can obey or disobey, but you are on your own.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.