4

我正在重构一些用 C 编写的旧遗留代码。这些代码耦合非常紧密,我正在努力将其重构为清晰的逻辑、松散耦合的模块。

在我早期的迭代中,我设法确定了逻辑模块 - 但是紧密耦合给我带来了问题,因为许多功能都对系统的其他部分有深入的了解。

我打算解决这个问题的方法是使用 extern 声明。希望下面的伪代码解释了这种情况:

假设我有两个逻辑上独立的模块 Foo 和 FooBar。每个模块都将被构建到一个单独的库中(FooBar 模块依赖于 Foo 模块)。

/*######################################*/
/*             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);
}

这是将现有代码重构为逻辑上独立的模块同时允许链接到 libfoo.so 和 libfoobar.so 的可执行文件继续正常工作的有效方法吗?

我的基本假设是,仅链接到 libfoo.so 的模块将无法解析foobar()- 但这应该没问题,因为它们不需要该函数,因此它们永远不必调用它。另一方面,对于链接到 libfoobar.so 的模块,当它们调用时foo(),`foobar() 将被解析(函数定义在模块中)。

我上面描述的方案会按我的预期工作,还是有一些我遗漏的问题?

4

1 回答 1

2

我尝试将您的文件编译成共享库,然后使用它们(我使用 cygwin)。

这是Foo:

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

使用 bin util nm 您可以检查符号(grep for 'foo' 以限制输出)

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

它给出了一个偏移量并说“T”表示终止,告诉你符号是在库中定义的。

现在 FooBar 库必须与 Foo 链接才能获得 foo 符号

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

有了这个,我只能针对 FooBar 进行编译并将 foo 作为已知符号:

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

所以它似乎工作正常。

您可以通过使用仅包含公共数据类型、导出函数原型(声明为 extern)甚至公共全局变量或常量的头文件来改进模块化 C 代码的方法。这样的头文件声明了模块的接口,并且必须包含在使用模块的地方。

这在模块章节中的精彩书籍“Functional C”(Hartel,Muller,1997,Addison Wesley,链接)中有更详细的解释。

好处是您的依赖项更清晰可见(如源文件中包含的那样),并且您不必在 Foo 源代码中使用那种难看的 extern 声明。

对于您的示例:

/*  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);
}
于 2013-01-04T00:07:18.927 回答