软件开发(3):软件集成重名解决方案

本文介绍了一种在软件集成过程中,遇到重名文件 和/或 重名函数时的解决方案。

why

software development(2):variant handling一文中介绍了很多种variant管理的方法,其中有关于link different libs的方法。
设想如下场景:开发过程中(针对C语言),各个模块分别由不同的团队开发,最后集成的时候,如果遇到重名的函数怎么处理?
本文针对这种场景,搜集了几种解决方案。

what

场景设置: 三个模块,a b os,其中ab模块都包含一个名字为func_internal.c的文件,这个同名文件中包含了名字一样的函数,分别为int func_internal(void)int func_internal(int a)。这两个函数都没有申明为static,但是没有外部调用。

demo in github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.
├── README.md
├── component_a
│   ├── exported_sym.list
│   ├── func_a.c
│   ├── func_internal.c
│   ├── func_internal.h
│   ├── functions.h
│   ├── makefile
│   └── redefine.syms
├── component_b
│   ├── exported_sym.list
│   ├── func_b.c
│   ├── func_internal.c
│   ├── func_internal.h
│   ├── functions.h
│   └── makefile
├── makefile
├── obj
└── os
├── exported_sym.list
├── main.c
└── makefile

component_a/func_a.cview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "functions.h"
#include "func_internal.h"
static int func_internal_s(void)
{
return 1;
}
int func_a(void)
{
int a = func_internal();
printf("this is func a. %d\n", a);
printf("this is static internal %d\n", func_internal_s());
return 0;
}
component_a/func_internal.cview raw
1
2
3
4
5
6
#include "functions.h"
int func_internal(void)
{
return 1;
}
component_b/func_b.cview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include "functions.h"
#include "func_internal.h"
static int func_internal_s(int a)
{
printf("func B %d\n", a);
return a;
}
int func_b(void)
{
int a = func_internal(100);
printf("this is func b. %d", a);
printf("internal func %d\n.", func_internal_s(-100));
return 0;
}
component_b/func_internal.cview raw
1
2
3
4
5
6
#include "functions.h"
int func_internal(int a)
{
return a;
}

how

分析

  1. 由于有文件重名,所以不能每个.c单独编译成.o,然后再链接;
    出现两个同名的.o,要不然链接器报错,要不然只用了其中一个.o,这样会导致运行时异常。

  2. 由于有函数重名,且函数没有申明为局部,链接的时候必然会出现错误:

    1
    2
    3
    duplicate symbol _func_internal in:
    component_a/func_internal.o
    component_b/func_internal.o

解决方案

编译

本文采用的方法:
每个模块单独编译成一个.o文件。

其他方法:

  1. 重名文件改名;重名函数改名;局部函数申明为static。
  2. 定义函数时定义visibility属性(GUN only)
    1
    __attribute__((visibility("hidden")))

链接

本文采用的方法:

-exported_symbols_list filename(Mac) 或者 --retain-symbols-file=filename(linux)

link all the .o in each component to one .o file; and only export symbols in the list
1
ld -r -exported_symbols_list exported_sym.list ${OBJECTS} -o ${TARGET}.o
ld.png

其实是强制将符号表里的全局符号(T)变成了局部符号(t),这样除了列表文件里的函数,其他对外均不可见。

其他方法:

  1. gcc在链接时设置 -fvisibility=hidden,则不加 visibility声明的都默认为hidden; gcc默认设置 -fvisibility=default,即全部可见;

  2. 使用export map,gcc -Wl,–version-script=export.map, 在export.map中指定

    1
    2
    3
    4
    5
    6
    7
    {
    global:export_func;
    local:*;
    };

objcopy

  1. 将全局符号修改为局部符号:
    -L symbolname

  2. 替换符号名
    --redefine-sym old=new
    --redefine-syms=filename

recap

  1. 由于C语言没有namespace的概念,可以在开发之前约定好,所有的全局函数全局变量都加上特殊的前缀,eg. CompanyName_ProductName_ComponentName_FunctionName
  2. 所有的局部函数定义都加上static。
  3. 可以先编译各个模块,再链接(类似于每个模块编译成静态库),这样可以防止重名的文件名报错。
  4. 可以在链接的时候控制各个模块编译后全局函数,将内部使用的但没有申明为static的函数,在符号表中强制修改为local的符号。
  5. 链接后,可以通过objcopy改变符号的属性(全局->局部)或者 修改全局符号的名字。

Reference:

  1. Choosing Visibility Options for Mac
  2. Introduction to symbol visibility
  3. man ld
  4. 将两个GCC编译的.o目标文件合并到第三个.o文件中
  5. export obly centain functioins in a static lib
  6. visibility of GCC