共享库中static关键字使用经验,与大家分享。

aero
共享库中static关键字使用经验,与大家分享。

我们都明白static关键字在C语言里面的作用。但在使用中,很多人不注意它的使用,容易导致比较隐蔽的错误。尤其在动态库的编程当中。我们用代码来说话,注意下面的代码:

so.c

[code]#include <stdio.h>

void func2(void);

void func1(void)
{
        printf("in func1 of so./n");
        func2();
        return ;
}

void func2(void)
{
        printf("in func2 of so./n");
        return ;
}[/code]

我们将这个代码编译成共享库。

gcc -shared -fPIC -o libso.so so.c

这个库对外提供一个func1的接口。库内自己使用了func2函数。func2函数是只在这个共享库中使用的函数。

再来看主程序:

main.c

[code]#include <stdio.h>
#include <stdlib.h>

void func1(void);
void func2(void);

int main(void)
{
        printf("in main./n");
        func1();
        return 0;
}

void func2(void)
{
        printf("in func2 of main./n");
        return ;
}[/code]

main函数中想使用刚才so动态库中的func1接口。但是巧合的是,main中也定义了一个func2函数(也可能是静态库或者先加载的其他库中的函数),恰好名字一样。

编译这个文件:

gcc main.c -L. -lso

生成了a.out可执行文件。我们来执行这个文件,看看会发生什么现象。

in main.
in func1 of so.
in func2 of main.

看,库中的func1调用其使用的func2的时候,又调回到先前加载的部分了,而不是正确想要的库中私有的func2!而在一般的情况下,使用发布的库,我们无从知道库中私有使用的非接口函数func2的名字!

其实,这个问题也非常好解决,就是在编写库函数的时候严格使用static关键字。将只在库中使用的函数使用static关键字,同时将其放在和调用函数同一个文件内。以保证调用的范围,不会发生调错的事情。

修改过的so.c:

[code]#include <stdio.h>

static void func2(void);

void func1(void)
{
        printf("in func1 of so./n");
        func2();
        return ;
}

static void func2(void)
{
        printf("in func2 of so./n");
        return ;
}[/code]

重新编译这个共享库:
gcc -shared -fPIC -o libso.so so.c
然后直接执行上面编译好的a.out可执行文件:
in main.
in func1 of so.
in func2 of so.

OK,这个就是我们想要的结果。

但对于一个库有多个文件,so1.c,so2.c等等,而把都使用的基础函数再放在一个文件so_common.c,从而方便进行开发的情况下。static关键字就无能为力了,而似乎也没有更好的办法来解决前面提到的同名问题。难道只能靠在库的说明文档中声明或者使用晦涩的____加无数下划线的名字?

[[i] 本帖最后由 aero 于 2008-1-22 17:14 编辑 [/i]]

ruoyisiyu
占座,吃完饭再看:mrgreen: :mrgreen:

eagle518
这个问题我也碰到过,是在开发以.so为插件模式的时候,
因为每个插件对外提供的接口都一样。
那时候那个晕啊!~~
这里可不能使用static解决哦,最后解决办法是编译.so的时候使用-rpath,强制优先搜索自身的.so.

alazer
一般"文档说明"吧,毕竟,接手一个程序,都要先读一下开发的文档
另外共享库的函数命名格式,可以再根据项目的具体情况特殊定义一下,使之在Main中出现的概率非常低

塑料袋
[quote]原帖由 [i]aero[/i] 于 2008-1-22 17:13 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7887071&ptid=1045900][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
但对于一个库有多个文件,so1.c,so2.c等等,而把都使用的基础函数再放在一个文件so_common.c,从而方便进行开发的情况下。static关键字就无能为力了,而似乎也没有更好的办法来解决前面提到的同名问题。难道只能靠在库的说明文档中声明或者使用晦涩的____加无数下划线的名字?[/quote]


1) __attribute__(visibility("hidden"))
2) __attribute__(visibility("protected"))
3) 连接器有一个参数,可指定DT_SYMBOLIC,具体哪个参数不清楚,但是肯定有。指定了动态连接时先从本地符号表开始搜索

2)3)在redhat的某一篇文档中说与ANSI C不符合,大致是因为ANSI C对取函数指针还有要求。

xiaoyun8821
Using the "-B symbolic" Option like this:

gcc -o xxx.so xxx.o -shared -Wl,-Bsymbolic

[url]http://www.docs.hp.com/en/B2355-90654/ch03s09.html[/url]

[[i] 本帖最后由 xiaoyun8821 于 2008-1-23 13:23 编辑 [/i]]

xi2008wang
学习.
昨天看K&R,正好提到:
The external static declaration is most often used for variables, but it can be applied to functions as well. Normally, function names are global, visible to any part of the entire program. If a function is declared static, however, its name is invisible outside of the file in which it is declared.

net_robber
不知道是不是我理解有问题

我觉得你在使用动态库的时候,有点问题
编译经动态库的时候没有问题

主要是使用动态库,也就是编译main的时候
我认为应该是这样的:
[code]gcc -ldl Main.c -o Main[/code]
然后,需要在main.c中显示的支出你所使用的动态库中的具体函数的引用
[code]handle = dlopen( "./lib/libso.so", RTLD_LAZY );
void* (*func_name_in_main)()=dlsym(handle,"func_name_in_so");
[/code]
你可以参考[code]man dlopen[/code]

[[i] 本帖最后由 net_robber 于 2008-2-14 12:12 编辑 [/i]]

net_robber
另外这种显示的调用,对于跨平台移植的情况先,似乎会方便一点

当然我是指类Unix平台到Win平台的移植

Edengundam
[quote]原帖由 [i]net_robber[/i] 于 2008-2-14 11:32 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7959351&ptid=1045900][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
不知道是不是我理解有问题

我觉得你在使用动态库的时候,有点问题
编译经动态库的时候没有问题

主要是使用动态库,也就是编译main的时候
我认为应该是这样的:
gcc -ldl Main.c -o Main
然后,需要在 ... [/quote]


和reflection类似...

考虑写到一个.c满世界#include 吧, 必要时候#undef.

static和extern很重要, 阅读更加清楚. 就算extern是默认的还是extern吧...

满世界include那些static的吧....

bobozhang
不错,前段时间我也遇到了个类似问题导致的bug,由于两个动态库都是同事写的,他们分别在这两个动态库中用了同一个名字的全局变量,我搞了一天才找到根源

lzcxh
[quote]原帖由 [i]塑料袋[/i] 于 2008-1-22 17:47 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7887318&ptid=1045900][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]



1) __attribute__(visibility("hidden"))
2) __attribute__(visibility("protected"))
3) 连接器有一个参数,可指定DT_SYMBOLIC,具体哪个参数不清楚,但是肯定有。指定了动态连接时先从本地符号表开始 ... [/quote]

不是很明白,可否举个例子说明下,谢谢

塑料袋
妈逼的

我两次回帖,想说清楚ld和ld.so,libdl.so的大意,发生楼主这种情况的原因.

都写到一半,写到百八十行的时候,这个论坛就说我什么登入超时???自动跳回来了,写的东西就没了,日日人日日日日日日日     :outu: :outu: :outu: :outu:

net_robber
[quote]原帖由 [i]Edengundam[/i] 于 2008-2-14 17:35 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7961310&ptid=1045900][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]



和reflection类似...

考虑写到一个.c满世界#include 吧, 必要时候#undef.

static和extern很重要, 阅读更加清楚. 就算extern是默认的还是extern吧...

满世界include那些static的吧.... [/quote]



没看明白


和我说的方法有这么相似么????

net_robber
[quote]原帖由 [i]塑料袋[/i] 于 2008-2-14 22:30 发表 [url=http://bbs.chinaunix.net/redirect.php?goto=findpost&pid=7962042&ptid=1045900][img]http://bbs.chinaunix.net/images/common/back.gif[/img][/url]
妈逼的

我两次回帖,想说清楚ld和ld.so,libdl.so的大意,发生楼主这种情况的原因.

都写到一半,写到百八十行的时候,这个论坛就说我什么登入超时???自动跳回来了,写的东西就没了,日日人日日日日日日日     :outu: :outu: :outu: :outu:   ... [/quote]
:mrgreen: :mrgreen: :mrgreen: :mrgreen:

塑料袋
首先,为什么C里是"extern"?而不是"import","export"?不知道别人,反正我是前阵子看了看ld和ld.so刚明白。
   gcc里,某个module中(module指带某个ET_EXEC或者ET_DYN文件,既可执行文件或共享库,非kernel里的module),但凡expot的symbol,必定同时也import这个symbol;而import的symbol,也export这个symbol,不过,在只有声明而无定义时,export的值为0。
    所有以extern声明(不需要定义,只要声明即可)的symbol,如果其属性不是 __attribute__(visibility("hidden")),那么,这个module在连接时,所有对symbol的引用均不会连接到symbol上,都要等到动态连接时才连接,而不论module内是否存在有symbol的定义。
    这样做是为了解决多个module中,都定义了同名符号 extern symbol aaa时的情况。假设moduleAAA要引用symbol aaa,而module AAA及module BBB中,都有extern symbol aaa的定义。这时,根据某个被翻译的血肉模糊的文档(大概是ELF ABI),不应该将module AAA对symbol aaa的引用,简单的定位到module AAA里边的定义上,而是应该根据一个搜索规则,进行搜索,如果先搜索到的是module BBB里的symbol aaa的定义,那么,module AAA里引用的symbol aaa,就是module BBB里的symbol aaa,而不是module AAA里的symbol aaa。这个规则翻译过来大概叫“抢占”,英文怎么写的忘了,“呸爱米特抱”。

    这就是为什么extern的symbol,不能在连接时进行连接,而要等到动态连接时才连接。因为连接时,并没有加载所有的module,无法进行搜索,只有ld.so或libdl.so程序在运行时,已经加载了所有的module,这时才可以进行搜索。

   这个搜索规则比较烦琐。module中,除了一般的DT_NEEDED之外(DT_NEEDED为_DYNAMIC节里的一部分,指明本module依赖于哪个module),还有DT_AUXILIARY及DT_FILTER两部分,这两部分我现在还不知道什么意思,只知道本module也依赖于DT_AUXILIARY及DT_FILTER这两种文件,而且这两种文件的搜索顺序更靠前。后边我想忽略掉有DT_AUXILIARY及DT_FILTER这两部分,因为通常的module依赖,指的就是DT_NEEDED。而且加上那两部分的说明后,恐怕没有个四五百字很难说明。

    执行一个可执行程序时,生成可执行程序的搜索规则,同时也是全局搜索的第一条搜索规则(a,b,c为任意数值):
EXEC =====>EXEC->DT_NEEDED[1]=====>EXEC->DT_NEEDED[2]=====>EXEC->DT_NEEDED[3].......=====>EXEC->DT_NEEDED[a]=====>EXEC->DT_NEEDED[1]->DT_NEEDED[1]=====>EXEC->DT_NEEDED[1]->DT_NEEDED[2]=====>EXEC->DT_NEEDED[1]->DT_NEEDED[3].......=====>EXEC->DT_NEEDED[1]->DT_NEEDED[ b ] =====>EXEC->DT_NEEDED[2]->DT_NEEDED[1]=====>EXEC->DT_NEEDED[2]->DT_NEEDED[2]=====>EXEC->DT_NEEDED[2]->DT_NEEDED[3].......=====>EXEC->DT_NEEDED[2]->DT_NEEDED[c].................


    dlopen某个未加载的共享库时,生成这个共享库的搜索规则,同时全局搜索规则里也要新加上这一条。生成的这个规则和上边一样,只不过最开始的EXEC变成了DYN。

   通常可执行程序以及它的所有DT_NEEDED,在修改PLT时,只搜索可执行程序的搜索规则。值得一提的是,dlopen时,若指定RTLD_GLOBAL,则可以修改可执行程序的搜索规则。

  dlsym()中,handle参数可为某个数值(既dlopen的返回值,指代某共享库的link_map),或者RTLD_DEFAULT,RTLD_NEXT。
1)某dlopen的返回值时,搜索的是dlopen时,为共享库生成的搜索规则。
2)RTLD_DEFAULT时,搜索的是全局的搜索规则,首先搜索可执行程序的搜索规则,再搜索各次dlopen时生成的搜索规则。
3)RTLD_NEXT时,同样搜索的是dlopen时,为共享库生成的搜索规则。只不过它不是从头搜索,而是忽略掉这个规则中的前几个元素。



另外,extern,extern xxx __attribute__(visibility("hidden")),extern xxx __attribute__(visibility("protected"))这三个东西,差别很大。
extern是既expoer又import;
extern xxx __attribute__(visibility("protected"))是只export,而不import;
extern xxx __attribute__(visibility("hidden"))是既不export,也不import。

[[i] 本帖最后由 塑料袋 于 2008-2-15 14:18 编辑 [/i]]