How To: Write Constructor and Destructor Functions in C

Write Constructor and Destructor Functions in C

Hey everyone.

Only found the site a few days ago but really loving it. Been
reading and learning new things and always grateful for that.

I used to code in c, as well as a few other languages a few years
back but fell away from it a bit due to various other commitments.
Getting back into the swing of it after a long break and having
some fun.

Anyways - I'm playing about with something right now and it dawned
on me that it's something I used more often than I realised. Sort
of took it for granted but it's quite useful so I thought I'd share
and hope someone gets something useful out of it.

Constructors and destructors in c.

I used to find that my programs became sloppy and quite messy rather
quickly.

It's easy enough to write more maintainable code, but my main() in
particular would end up cluttered. It more often than not became a
repugnant mess of initialisations, assignments, argument processing
and all that.

Then there's the middle bit where the program does what it's actually
supposed to...then the sloppy, messy cleanup at the end.

Another problem I found was that on the odd occasion my code would
exit without cleaning up...closing files, free()'ing memory. Normally
this is a result of poor logic or design, but it becomes a pain
having to make sure you tidy up everything nice at every point
in your code where your program might exit.

When I discovered how to use constructor and destructor functions in
c it made things a lot cleaner. My main() functions became more
streamlined - I began to care less about initialising because I'd
have a constructor to take care of it for me, and cleanup was pretty
easy since I had a destructor to do the work when my program exited.

Declaring the functions.

Your prototypes can declare the constructor and destructor functions
using _attribute_

Have a look https://gcc.gnu.org/onlinedocs/gcc-4.3.5/gcc/Function-Attributes.html for more info - you can use _attribute_ for a few different things.

void function_ctor (void) __attribute__ ((constructor));
void function_dtor (void) __attribute__ ((destructor));

These are the prototypes for both types, we can be sure that the
constructor will be called before main() begins, and immediately
after main returns or the program exits.

Try it:

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

void function_ctor (void) __attribute__ ((constructor));
void function_dtor (void) __attribute__ ((destructor));

int
main(void)
{
return 0;
}

void
function_ctor(void)
{
fprintf(stdout, "Constructor.\n");
}

void
function_dtor(void)
{
fprintf(stdout, "Destructor.\n");
}

Run the program, it's simple enough and quite self-explanitory,
most people familiar enough with c will know about this, newcomers
may not, but the usefulness of this feature is quite obvious to
even the freshest of coders.

Another example.

Here's a more useful example - we will have two global FILE *
streams, one for standard output and one for standard error.

Our constructor will assign them default values - namely stdout
and stderr, respectively. These are standard c FILE * streams, we always have stdin (0), stdout (1) and stderr (2).

A simple function will process command line arguments which can
be used to specify alternative output files.

Our destructor will then close non-standard FILE streams so that
any writing done to either stream will be saved.

The code is well commented and simple enough to understand, here
it is:

/**
* fattrs.c
*/

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

/**
* Some naughty globals....oh dear!
*/
FILE *_o_stream;
FILE *_e_stream;

/**
* We need a constructor that will assign our globals so that
* we can use them safely in our program...
*
* These are just the prototypes - the actual functions appear
* below main().
*/
void __init_streams (void) __attribute__ ((constructor));
void __close_streams (void) __attribute__ ((destructor));

/**
* Apparently, it ain't a good idea to do too much work directly
* in your constructor/destructor functions.
*
* So I've heard, now I ain't gonna tell no lies - I dunno why,
* maybe it's about keeping it neat, or maybe there's a proper
* good reason for this...personally, I've done loads of things
* directly within my constructor/destructor functions in the
* past without any problems.
*
* Still, for the sake of neatness, maintainability and just to
* be pedantic we'll instead use the constructor/destructor as
* wrapper functions which will call on the following functions
* to carry out the actual work...
*
* Note that these function names begin with a single _ whereas
* the constructor/destructors begin with __
*/
void _init_streams (void);
void _close_streams (void);

/**
* For processing command line args...
*/
void args (int, char **);

int
main(
int argc,
char **argv
)
{
/**
* The args() function will process the command line arguments...
*/
args(argc, argv);

/**
* Since the constructor is called before main() executes, we
* can be sure that the global FILE streams have been initialised
* to point at standard output streams (stdout and stderr).
*/
fprintf(_o_stream, "This is the output stream.\n");
fprintf(_e_stream, "This is the error stream.\n");
}

void
__init_streams(void)
{
_init_streams();
}

void
__close_streams(void)
{
_close_streams();
}

void
_init_streams(void)
{
/**
* We'll just assign standard FILE streams so that we can
* write to stdout and stderr....
*/
_o_stream = stdout;
_e_stream = stderr;
}

void
_close_streams(void)
{
/**
* We don't need to bother closing non-standard FILE streams,
* although the args() function may have opened specific
* files for output, in which case we need to close them.
*/
if (_o_stream != stdout)
fclose(_o_stream);
if (_e_stream != stderr)
fclose(_e_stream);
}

/**
* This is all pretty self-explanitory.
*/
void
args(
int argc,
char **argv
)
{
int a;

for (a = 1; a < argc; a++) {

/**
* The -o option is used to specify an output file,
* -e to specify an error file.
*/
if (strcmp(argva, "-o") == 0) {
if (++a >= argc) {
fprintf(estream, "Usage: %s -o <file>\n", argv0);
exit(EXIT_FAILURE);
}

_o_stream = fopen(argva, "w");

if (_o_stream == NULL) {
fprintf(_e_stream, "Error opening output file %s!\n", argva);
exit(EXIT_FAILURE);
}

continue;
}

else if (strcmp(argva, "-e") == 0) {
if (++a >= argc) {
fprintf(_e_stream, "Usage: %s -e <file>\n", argv0);
exit(EXIT_FAILURE);
}

_e_stream = fopen(argva, "w");

if (_e_stream == NULL) {
/**
* We don't have an error stream to write to!
*
* We'll have to default to stderr...
*/
fprintf(stderr, "Error opening error file %s!\n", argva);
exit(EXIT_FAILURE);
}

continue;
}

else {
fprintf(_e_stream, "%s: Unknown option!\n", argva);
exit(EXIT_FAILURE);
}

} // End of for()
} // End of args()

/* End of file. *./

Compile it:

# gcc fattrs.c -o fattrs

By default, the FILE streams are assigned stdout and stderr, we
can run the program and we'll get the following:

# ./fattrs
This is the output stream.
This is the error stream.

We can redirect stdout and stderr to separate files, like this

# ./fattrs 1> out 2> err
# cat out
This the output stream.
# car err
This is the error stream.

We redirect the output of 1 (stdout) to the file named 'out' and
the output of 2 (stderr) to the file named 'err'.

If we want to change the output streams to write directly to
these files instead - first delete out and err:

# rm out err

Run attrs again, this time we'll use the -o and -e options to
specify paths to the output files:

# ./fattrs -o out -e err

You can cat the 'out' and 'err' files to verify that they were
indeed written.

Lastly.

I'd recommend you write a simple program that has 3 constructors
and 3 destructors.

Call them ctor_1(), ctor_2(), ctor_3(), dtor_1(), dtor_2() and
dtor_3().

Have each of them print a string, for example have ctor_1() dump
"This is constructor 1." to stdout, dtor_1() print "This is
destructor 1." to stdout, and so on.

Note the order they are executed in - they are automatically
prioritised based on the order of declaration.

We can use something like this to set the priority level ourselves:

((constructor (101)))
((constructor (102)))

And so on, play about with it and have fun.

Happy coding!

Just updated your iPhone? You'll find new emoji, enhanced security, podcast transcripts, Apple Cash virtual numbers, and other useful features. There are even new additions hidden within Safari. Find out what's new and changed on your iPhone with the iOS 17.4 update.

1 Comment

I apologise if the code examples appear sloppy...editor played silly buggers with the code when I pasted it in.

I get that formatting is important...but _ and *? Really?

Share Your Thoughts