我正在重构一些用 C 语言编写的旧遗留代码。这些代码耦合非常紧密,我正在努力将其重构为逻辑清晰、耦合松散的模块。
在我的早期迭代中,我设法确定了逻辑模块——但是紧密耦合给我带来了问题,因为许多功能对系统的其他部分有深入的了解。
我打算解决这个问题的方法是使用外部声明。希望下面的伪代码解释了这种情况:
假设我有两个逻辑上独立的模块 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() 将被解析(函数定义在模块中)。
我上面描述的方案是否会像我预期的那样工作,或者我是否遗漏了一些问题?
最佳答案
我试过你将你的文件编译成共享库然后使用它们(我使用 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
所以它似乎工作正常。
您可以改进模块化 C 代码的方法,方法是让头文件仅包含公共(public)数据类型、导出的函数原型(prototype)(声明为 extern),甚至可能包括公共(public)全局变量或常量。这样的头文件声明了模块的接口(interface),并且必须包含在使用模块的地方。
这本精彩的书 'Functional C' 对此进行了更详细的解释。 (Hartel, Muller, 1997, Addison Wesley, link)在关于模块的章节中。
好处是您的依赖项更清晰可见(如包含在源文件中),并且您不必在 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);
}
关于c - 重构遗留 C 代码——使用外部声明来帮助拆分模块(潜在的链接和运行时问题),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14146280/