A typical C compiler has these steps, exactly in that order:
- Preprocessing.
- Lexical, syntactic and semantic analysis of the code.
- Intermediate code generation (object code, these files with extension
.o
).
- Linkage.
- Executable code generation.
Let's suppose you start with this in main.c
:
void teste();
int main() {
teste();
}
Notice the prototype of the teste()
function there. It states that the function exists somewhere, and that it is the linker's responsibility to find it. If it does not find it, it will throw an error like this (assuming you are using GCC, but it will look something like some other compiler):
/home/blablabla/blablabla.o: In function 'main':
main.c:(.text.startup+0x7): undefined reference to 'teste'
collect2: error: ld returned 1 exit status
The error message says that the teste
function could not be found. It could be declared in a different file ( teste.c
for example):
#include <stdio.h>
void teste() {
printf("foo");
}
And then, by compiling the two sources together, the linker will find the teste()
function.
You can try to make the preprocessor work with this by making main
look like this:
void teste();
int main() {
teste();
}
#ifndef _teste
# error Voce esqueceu de implementar o teste.
#endif
If the programmer does not set _teste
anywhere, this error will appear. However, this solution is incomplete because there is no guarantee that the teste()
function and the _teste
symbol appear together. For example, verification is bypassed if no teste.c
exists:
#define _teste
// Esqueci de implementar a função aqui!
Another one that goes wrong is this:
#include <stdio.h>
void teste() {
printf("Foo");
}
// Esqueci de dar o #define _teste
In that case, the error persists even though the function has been implemented.
However, this does not work at all. The preprocessor processes the files separately, so even though the programmer places the teste()
function and the _teste
symbol on another file, the error will still be triggered.
You can try to remedy with #include
in main.c
:
#include "teste.inc"
void teste();
int main() {
teste();
}
#ifndef _teste
# error Voce esqueceu de implementar o teste.
#endif
And so, the programmer defines the function teste()
and the symbol _teste
within teste.inc
. However, this is not good programming practice because header files (files with .h
extension) should not have implementation. While in this case, you are not using the header file exactly as a header but rather abusing the header definition to import an implementation (so we use a different extension, .inc
).
The problem is that the preprocessor is just a tool for copy-and-paste text and in fact, it is a totally separate language from C. There is a language that is the one of the preprocessor and another completely different language that is the one of the compiler and both do not communicate except for the fact that the output of one is the input of the other. Therefore, it is not only possible in the preprocessor to know if the function was declared because the preprocessor does not even know what a function is and it sees its code just as a lot of text being blindly copied to the output . On the other hand, the linker (which is also separate from the compiler and the preprocessor except that it uses the compiler output as input) has nothing to do with custom error messages.
We can think of a different approach. You can already provide a skeleton for the teste
function and abandon #ifdef
. For example, if everything is done in a single source code:
#include <stdio.h>
void teste();
int main() {
teste();
}
void teste() {
# error Voce esqueceu de implementar o teste. Substitua essa mensagem pelo codigo.
}
In multiple source code, with the caveat that you are abusing the purpose of #include
, you can do this in main.c
:
#include "teste.inc"
void teste();
int main() {
teste();
}
And no teste.inc
:
#include <stdio.h>
void teste() {
# error Voce esqueceu de implementar o teste. Substitua essa mensagem pelo codigo.
}