Execution of a C program starts with ‘main’ function. Let us
define a function in the later sessions. But for now, assume that a function is
a set of statements/instructions.
Can we write a C program without a function? The answer is obviously
NO! Checkout the following program.
#include<stdio.h>
printf("Before main..\n");
int main()
{
printf("In main..\n");
return 0;
}
printf("After main..\n");
Now, let us go through the four stages of compilation in C
language and understand what happens in every stage of compilation. Checkout
the program coded below. We use gedit editor in Ubuntu for editing/manipulating/viewing
the contents of a file. The ‘vi’ editor can also be used. You can get the usage
of vi editor here.
After preprocessing, you can observe that no error is
produced, though two printf statements are mentioned outside any function. Also,
for the few lines of code we have, the preprocessed file test.i produces a code
close to 1000 lines, which consists of all the code present in the stdio.h
header file. Let us move on to translation.
Here, this produces an error stating that…
Error: expected declaration specifiers or ‘…’ before string
constant
This is because we have put two printf statements outside
function (particularly main function here). Hence, it can be concluded that syntax
errors are checked by the translator. The only way to rectify these errors is to
place them inside a function. Let’s do it.
#include<stdio.h>
void f1(void)
{
printf("In f1\n");
}
int main()
{
printf("In main..\n");
return 0;
}
void f2(void)
{
printf("After main..\n");
}
Now let us preprocess (test.i), translate (test.s) and assemble
(test.o) the source code to generate the object file. We know the contents of
the preprocessed file test.i – it consists of pure C code after removal of
comments and respective file inclusion. After translation, the test.s file
consists of the respective assembly code. The object file of the translated code test.o
can be generated by the assembler. Finally, the object file is converted into
executable file ‘test’ using linker.
The object file (.o) consists of two sections:
Text section – consists of function body
Data section – consists of global and static variables.
After successfully generating the executable file, it can be
executed as shown below.
./test
This produces the output as shown below.
It is evident that we have two more functions – f1 and f2 written in the code. What happened to these two functions? Are they present in the executable file? To figure out this, let us do some reverse engineering. Let us disassemble the executable file ‘test’ to another file ‘prog’. It can be done using the following command.
It is evident that we have two more functions – f1 and f2 written in the code. What happened to these two functions? Are they present in the executable file? To figure out this, let us do some reverse engineering. Let us disassemble the executable file ‘test’ to another file ‘prog’. It can be done using the following command.
objdump -D test.o >prog
We can check the contents of the ‘prog’ file in text
editors. We can clearly observe that the definition of both the functions f1
and f2 are present in the executable file. But, why are the printf statements
not executed though they are present in the executable file? The simple answer
is that the function f1 and f2 are not called.
But, we have been telling that ‘main’ is also function. Who
called the ‘main’ function? The answer for this is the _start function. This
_start function calls main and the rest of the functions are called based upon
requirement. You can check the code for _start function in the ‘prog’ file
calling the ‘main’ function, where _start function is called by the OS/kernel
when the gcc compiler initiates compiling a given program.
Now, let us call the functions f1 and f2 from main as shown
below.
#include<stdio.h>
void f1(void)
{
printf("In f1\n");
}
int main()
{
printf("In main..first time..\n");
f1();
printf("In main..second time..\n");
f2();
printf("In main..third time..\n");
return 0;
}
void f2(void)
{
printf("After main..\n");
}
Now, when we preprocess this code, no errors are produced.
But produces a warning stating that...
warning: conflicting types for ‘f2’ [enabled by default]
Why hasn’t this happened with the function f2? The answer is – because the function definition for f1 appears before the function call f1() but, the definition for f2 appears after the function call f2(). Hence, the gcc compiler can locate the address of f1, from where it should start executing but cannot locate the address of f2.
Why hasn’t this happened with the function f2? The answer is – because the function definition for f1 appears before the function call f1() but, the definition for f2 appears after the function call f2(). Hence, the gcc compiler can locate the address of f1, from where it should start executing but cannot locate the address of f2.
*Note that gcc is flexible in ignoring such function declarations when the function returns/accepts void/int data types. But strictly speaking according to ANSI C standards, it is an error.
As this is not an error (w.r.t gcc), we can proceed to further stages of
compilation. After translation, we proceed for assembling; and then we go for
linking. The effect of the warning shown during translation is shown here – a linking
error producing some addresses. Hence, the function declaration or function
prototype for f2 is mandatory at the top.
The conclusion is that if a function definition appears
after the function call the function should be declared at the top of the
program.
Let us declare f2, and compile the program, which produces
that output as shown below.
#include<stdio.h>
void f2(void);//declaration of f2
void f1(void)
{
printf("In f1\n");
}
int main()
{
printf("In main..first time..\n");
f1();
printf("In main..second time..\n");
f2();
printf("In main..third time..\n");
return 0;
}
void f2(void)
{
printf("After main..\n");
}
Bypassing the four stages of compilation, we can simply
generate the executable file using the following command.
gcc test.c
This command produces the default executable file a.out,
which can be executed as follows:
./a.out
Now, let me answer the question – Can we write a C program
without main function? The answer is YES! But, there is a catch.
While executing a C program without main function, the
switch -nostartfiles is to be used as shown below. In this case, the _start
function calls the first defined function in the program and continues
execution in top-to-bottom fashion.
gcc test.c -nostartfiles
*Never forget that main is a user-defined function. Because,
the programmer defines the functionality of the main function.
0 comments:
Post a Comment