Homunculus Reticulli
Homunculus Reticulli

Reputation: 68436

Refactoring legacy C code - using extern declarations to help split up modules (potential linking and run time issues)

I am in the process of refactoring some old legacy code, written in C. The code is very tightly coupled and I am struggling to refactor it into clear logical, loosely coupled modules.

In my early iterations, I have managed to ascertain logical modules - however the tight coupling is causing me problems, as many of the functions have intimate knowledge of other parts of the system.

The way I intend to fix this is to use extern declarations. The pseudocode below hopefully, explains the situation:

Assume I have two logically separate modules Foo and FooBar. Each module is to be built into a separate library (the FooBar module has a dependency on the Foo module).

/*######################################*/
/*             Foo Module               */
/*######################################*/
/*  Foo.h */
#ifndef FOO_MODULE_H
#define FOO_MODULE_H

void foo(int id);
int do_something();
...

#endif /* FOO_MODULE_H */


/* Foo.c */
#include "Foo.h"
extern int foobar();  /* defined in FooBar module (a separate library) */


void foo(int id){
    int var;

    switch (id){
        case 1:
            var = do_something();
            break;

        case 2:
            /* the module that gets here, has the required functions defined in it */
            var = foobar();
    }

    return var;
}



/*###############################*/
/*        FOOBar module          */
/*###############################*/

/* FooBar.h */

#ifndef FOOBAR_MODULE_H
#define FOOBAR_MODULE_H

#include "Foo.h"

int foobar();
void do_something_else();
...

#endif /* FOOBAR_MODULE_H */


/* source file */
int foobar(){
    return 42;
}

void do_something_else(){
   int ret = foo(2);
   printf("Function returned: %d", ret);
}

Is this a valid way to refactor the existing code into logically separate modules whilst allowing executables that link to libfoo.so and libfoobar.so to continue to work correctly?

My underlying assumption is that modules that link to libfoo.so only will not be able to resolve foobar() - but that should be fine since they do not need that function, so they will never have to call it. On the other hand, for modules that link to libfoobar.so, when they call foo(), `foobar() would have been resolved (the function definition is in the module).

Will the scheme I describe above work as I expect, or is there some gotcha that I am missing?

Upvotes: 4

Views: 412

Answers (1)

Gregor Ophey
Gregor Ophey

Reputation: 837

I tried your to compile your files into shared libs and then use them (I use cygwin).

Here's for Foo:

cm@Gregor-PC ~/src
$ gcc -I. -c --shared -o libFoo.so Foo.c

With the bin util nm you can check the symbols (grep for 'foo' to limit the output)

cm@Gregor-PC ~/src
$ nm libFoo.so | grep foo
0000000a T _foo
         U _foobar

Where it gives an offset and says 'T' for terminated that tells you the symbol is defined in the lib.

Now the FooBar lib has to linked against Foo in order to have the foo symbol

cm@Gregor-PC ~/src
$ gcc -I. -L. --shared -o libFooBar.so FooBar.c libFoo.so

cm@Gregor-PC ~/src
$ nm libFooBar.so | grep foo
61f0111e T _foo
61f010e0 T _foobar

With this I can compile against FooBar only and get foo as a known symbol:

cm@Gregor-PC ~/src
$ gcc -I. -o tst1 tst1.c libFooBar.so
cm@Gregor-PC ~/src
$ ./tst1
Function returned: 42

So it seems to work OK.

You can improve on your approach to modularize your C code by having header files that contain only public data types, exported function prototypes (declared as extern) and maybe even public global variables or constants. Such a header file declares the interface to the module and would have to be included where the module is used.

This is explained with much more detail in the wonderful book 'Functional C' (Hartel, Muller, 1997, Addison Wesley, link ) in the chapter on modules.

The benefit is that your dependencies are clearer to see (as includes in the source files) and you don't have to have that unsightly extern declaration in the Foo source.

For your example:

/*  Foo.h */
extern int foo(int id); /* exported to FooBar */

/* FooBar.h */

extern int foobar(); /* export to Foo */
extern void do_something_else(); /* export to tst1 */


/* Foo.c */
#include <Foo.h>
#include <FooBar.h>


int do_something() {
  return 11;
}

int foo(int id){
    int var;

    switch (id){
        case 1:
            var = do_something();
            break;

        case 2:
            /* the module that gets here, has the required functions defined in it */
            var = foobar();
    }

    return var;
}

/* FooBar.c */

#include <stdio.h>
#include <Foo.h>
#include <FooBar.h>

int foobar(){
    return 42;
}

void do_something_else(){
   int ret = foo(2);
   printf("Function returned: %d", ret);
}

Upvotes: 2

Related Questions