With GCC family of C compilers, we can mark some functions to execute before and after main(). So some startup code can be executed before main() starts, and some cleanup code can be executed after main() ends. For example, in the following program, myStartupFun() is called before main() and myCleanupFun() is called after main().
#include<stdio.h>
/* Apply the constructor attribute to myStartupFun() so that it
is executed before main() */
void myStartupFun (void) __attribute__ ((constructor));
/* Apply the destructor attribute to myCleanupFun() so that it
is executed after main() */
void myCleanupFun (void) __attribute__ ((destructor));
/* implementation of myStartupFun */
void myStartupFun (void)
{
printf ("startup code before main()\n");
}
/* implementation of myCleanupFun */
void myCleanupFun (void)
{
printf ("cleanup code after main()\n");
}
int main (void)
{
printf ("hello\n");
return 0;
}
Output:
startup code before main()
hello
cleanup code after main()
Prerequisite: Static variables in C
In C, functions are global by default. The “static” keyword before a function name makes it
static. For example, below function fun() is static.
static int fun(void)
{
printf("I am a static function ");
}
Unlike global functions in C, access to static functions is restricted to the file where they are declared. Therefore, when we want to restrict access to functions, we make them static. Another reason for making functions static can be reuse of the same function name in other files.
For example, if we store following program in one file file1.c
/* Inside file1.c */
static void fun1(void)
{
puts("fun1 called");
}
And store following program in another file file2.c
/* Inside file2.c */
int main(void)
{
fun1();
getchar();
return 0;
}
Now, if we compile the above code with command “gcc file2.c file1.c”, we get the error “undefined reference to `fun1’” . This is because fun1() is declared static in file1.c and cannot be used in file2.c.
In C++, what is the difference between exit(0) and return 0?
When exit(0) is used to exit from program, destructors for locally scoped non-static objects are not called. But destructors are called if return 0 is used.
Program 1 – – uses exit(0) to exit
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
class Test {
public:
Test() {
printf("Inside Test's Constructor\n");
}
~Test(){
printf("Inside Test's Destructor");
getchar();
}
};
int main() {
Test t1;
// using exit(0) to exit from main
exit(0);
}
Output:
Inside Test’s Constructor
Program 2 – uses return 0 to exit
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
class Test {
public:
Test() {
printf("Inside Test's Constructor\n");
}
~Test(){
printf("Inside Test's Destructor");
}
};
int main() {
Test t1;
// using return 0 to exit from main
return 0;
}
Output:
Inside Test’s Constructor
Inside Test’s Destructor
Calling destructors is sometimes important, for example, if destructor has code to release resources like closing files.
Note that static objects will be cleaned up even if we call exit(). For example, see following program.
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
class Test {
public:
Test() {
printf("Inside Test's Constructor\n");
}
~Test(){
printf("Inside Test's Destructor");
getchar();
}
};
int main() {
static Test t1; // Note that t1 is static
exit(0);
}
Output:
Inside Test’s Constructor
Inside Test’s Destructor
1. exit()
void exit ( int status );
exit() terminates the process normally.
status: Status value returned to the parent process. Generally, a status value of 0 or EXIT_SUCCESS indicates success, and any other value or the constant EXIT_FAILURE is used to indicate an error. exit() performs following operations.
* Flushes unwritten buffered data.
* Closes all open files.
* Removes temporary files.
* Returns an integer exit status to the operating system.
The C standard atexit() function can be used to customize exit() to perform additional actions at program termination.
Example use of exit.
/* exit example */
#include <stdio.h>
#include <stdlib.h>
int main ()
{
FILE * pFile;
pFile = fopen ("myfile.txt", "r");
if (pFile == NULL)
{
printf ("Error opening file");
exit (1);
}
else
{
/* file operations here */
}
return 0;
}
When exit() is called, any open file descriptors belonging to the process are closed and any children of the process are inherited by process 1, init, and the process parent is sent a SIGCHLD signal.
The mystery behind exit() is that it takes only integer args in the range 0 – 255 . Out of range exit values can result in unexpected exit codes. An exit value greater than 255 returns an exit code modulo 256.
For example, exit 9999 gives an exit code of 15 i.e. (9999 % 256 = 15).
Below is the C implementation to illustrate the above fact:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if ( pid == 0 )
{
exit(9999); //passing value more than 255
}
int status;
waitpid(pid, &status, 0);
if ( WIFEXITED(status) )
{
int exit_status = WEXITSTATUS(status);
printf("Exit code: %d\n", exit_status);
}
return 0;
}
Output:
Exit code: 15
Note that the above code may not work with online compiler as fork() is disabled.
Explanation: It is effect of 8-bit integer overflow. After 255 (all 8 bits set) comes 0.
So the output is “exit code modulo 256”. The output above is actually the modulo of the value 9999 and 256 i.e. 15.
2. abort()
void abort ( void );
Unlike exit() function, abort() may not close files that are open. It may also not delete temporary files and may not flush stream buffer. Also, it does not call functions registered with atexit().
This function actually terminates the process by raising a SIGABRT signal, and your program can include a handler to intercept this signal (see this).
So programs like below might not write “Geeks for Geeks” to “tempfile.txt”
#include<stdio.h>
#include<stdlib.h>
int main()
{
FILE *fp = fopen("C:\\myfile.txt", "w");
if(fp == NULL)
{
printf("\n could not open file ");
getchar();
exit(1);
}
fprintf(fp, "%s", "Geeks for Geeks");
/* ....... */
/* ....... */
/* Something went wrong so terminate here */
abort();
getchar();
return 0;
}
If we want to make sure that data is written to files and/or buffers are flushed then we should either use exit() or include a signal handler for SIGABRT.
3. assert()
void assert( int expression );
If expression evaluates to 0 (false), then the expression, sourcecode filename, and line number are sent to the standard error, and then abort() function is called. If the identifier NDEBUG (“no debug”) is defined with #define NDEBUG then the macro assert does nothing.
Common error outputting is in the form:
Assertion failed: expression, file filename, line line-number
#include<assert.h>
void open_record(char *record_name)
{
assert(record_name != NULL);
/* Rest of code */
}
int main(void)
{
open_record(NULL);
}
Predict the output of following C program.
#include <stdio.h>
fun(int x)
{
return x*x;
}
int main(void)
{
printf("%d", fun(10));
return 0;
}
Output: 100
The important thing to note is, there is no return type for fun(), the program still compiles and runs fine in most of the C compilers. In C, if we do not specify a return type, compiler assumes an implicit return type as int. However, C99 standard doesn’t allow return type to be omitted even if return type is int. This was allowed in older C standard C89.
In C++, the above program is not valid except few old C++ compilers like Turbo C++. Every function should specify the return type in C++.
119 docs|30 tests
|
1. What are the different types of functions in C-2 GATE? |
2. How do library functions differ from user-defined functions in C-2 GATE? |
3. What is the purpose of recursive functions in C-2 GATE? |
4. Can a function in C-2 GATE return multiple values? |
5. How are function prototypes used in C-2 GATE? |
|
Explore Courses for Computer Science Engineering (CSE) exam
|