关于动态链接库(dynamic library)里static变量和函数的问题

前段时间遇到一个问题,如果有多个动态链接库同时link另外一个静态链接库,这个静态库里的全局变量(或者static变量)会怎么样呢?
拍脑袋想想,总共也就这么几种可能:

  1. link(dlopen)时报错,变量重定义
  2. link(dlopen)时没错,执行时用用同一个变量
  3. link(dlopen)时没错,执行时分别有不同的变量
  4. link(dlopen)时报错,未定义的变量

仔细想想,分析一下,各种可能性都有,得看这些库和可执行文件是怎么编译链接的才行,具体看下面的各种case。
同时,测试的code里在dynamic里加上了同名的函数,看看函数会有什么表现。

假设有如下文件(code见Gist):

    common.h
    common.cpp  // 生成libcommon.a
    dynamic1.h
    dynami1.cpp  // 生成libdynamic1.so
    dynamic2.h
    dynamic2.cpp // 生成libdynamic2.so
    main.cpp  // 生成main

生成libcommon.a总是这么编译:

    g++  -g -Wall -Wextra  -c -o common.o common.cpp
    ar rcs libcommon.a common.o
  • Case 0: 动态链接库不link .a,main直接link .so,生成main的时候也不link .a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp
        g++ -DDIRECT_CALL_SO -o main main.cpp -L. -ldynamic1 -ldynamic2
    

    这种情况结果显然是4,link时出错,找不到.a里的函数。

  • Case 1: 动态链接库不link .a,main直接link .so,生成main的时候link .a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp
        g++ -DDIRECT_CALL_SO -o main main.cpp -L. -ldynamic1 -ldynamic2 -lcommon
        LD_LIBRARY_PATH=. ./main
    

    这种情况下执行的结果是2,link时没错,执行时看到的也是同一个变量。
    GetInt()返回值是1,它依赖于-l的顺序,如果-ldynamic2在前,返回值就是2了。

  • Case 2: 动态链接库link .a,main直接link .so,生成main的时候无所谓要不要link .a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp -L. -lcommon
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp -L. -lcommon
        g++ -DDIRECT_CALL_SO -o main main.cpp -L. -ldynamic1 -ldynamic2
        LD_LIBRARY_PATH=. ./main
    

    执行结果同上

  • Case 3: 动态链接库不link .a,main不链接.so,通过dlopen()调用so,无所谓链接.a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp
        g++ -o main main.cpp -ldl [-L. -lcommon]
        LD_LIBRARY_PATH=. ./main
    

    执行结果是4,dlopen的时候找不到 GetGlobalStatic()

  • Case 4: 动态链接库link .a,main不链接.so,通过dlopen()调用so(不用RTLD_GLOBAL),无所谓链接.a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp -L. -lcommon
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp -L. -lcommon
        g++ -o main main.cpp -ldl
        LD_LIBRARY_PATH=. ./main
    

    执行结果是3,各有各自的变量。
    GetInt()返回的也是各自的变量。

  • Case 5: 动态链接库link .a,main不链接.so,通过dlopen()调用so(使用RTLD_GLOBAL),无所谓链接.a
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic1.so dynamic1.cpp -L. -lcommon
        g++ -g -Wall -Wextra -fPIC -shared -o libdynamic2.so dynamic2.cpp -L. -lcommon
        g++ -DUSE_RTLD_GLOBAL -o main main.cpp -ldl -L. -lcommon
        LD_LIBRARY_PATH=. ./main
    

    执行结果变成2了,执行时看到了同一个变量。
    GetInt()返回的仍然是各自的变量。

看了这么多case,结果1怎么没有出现呢?别急,如果编译的时候全部编译在一起:

    g++ -DDIRECT_CALL_SO -o main main.cpp dynamic1.cpp dynamic2.cpp common.cpp

就出错了,GetInt()重定义。

结论

  • 对于不同so link的.a里的变量:
    1. 如果是直接link的,总是用同一个变量。仔细想想肯定是这样,否则一定会出现multiple definition
    2. 如果是dlopen,它依赖dlopen()的flag:
      • 如果是RTLD_LOCAL(默认),各个so会使用各自的.a里的变量。
      • 如果用RTLD_GLOBAL,就跟直接link一样,用同一个变量了。
  • 对于不同so里的同名函数:
    1. 通过dynamic link或者dlopen时不会出现变量重定义;
    2. 直接link时的顺序决定了main使用哪个so的实现
    3. dlopen的话,它们有各自的函数
Share

在Calibre Recipe里保存收录过的文章

一般来说,Calibre Recipe关注的内容是定期更新的,比如说知乎日报的内容,文章列表每天都会全部更新。

但如果某些内容并不是完全更新,只是部分更新,怎么处理呢?
比如说简书的每周热门文章,某些文章因为热门会持续几周都在其中。这样的话Recipe应该怎么处理才能保证只抓取新的文章,而忽略以前收录的文章呢?

一个朴素的想法就是,把之前收录过的文章都保存起来,然后在recipe里添加要抓取文章之前先check一下有没有抓取过了。
然而在尝试这个朴素的想法的时候,发现在Recipe里保存数据是个问题——保存的文件找不到!

稍微debug了一下,然后看了一下calibre的实现,发现它在抓取过程中的working directory是`/tmp/`下的一个临时目录,完成之后还会被删掉,当然找不到了。。。

尝试一:绝对路径,它work,但是太丑了。

尝试二:如下python里一般获取cwd的方法都不work

os.getcwd()
print os.path.dirname(os.path.realpath(__file__))

因为calibre在__init__.py里有`self.cwd = None` 然后`os.chdir()`

尝试三:遍历inpsect一层层的stack,结果只能找到`/usr/bin/`里面去

尝试四:最后突然想到,既然只是个环境变量,那在os.environ里一定有相关的信息嘛,直接打印看一下,发现

os.environ['PWD']

就是我想要的目录。于是,用这个目录当CWD就搞定了。

具体的code直接看这个commit了。

Share

比较boost::ptr_vector和std::vector

今天有同事问起来关于boost的smart pointer的事情,原因是别人有一段code用了boost::scoped_ptr<>*,review的时候被揪出来说这不符合常理,讨论应该用啥容器。

基本情况就是有一些资源需要new出来放在一个容器里,这个容器的生命周期由自己控制,但是需要把new出来的东西作为一个数组(或者容器)传给别人。

原来的code写成了一个boost::scoped_ptr<>的数组,然后传给别人,像下面这样:

boost::scoped_ptr someResource[number];
for (int i = 0; i < number; ++i) {
  someResource[i].reset(new T);
}
OtherFunction(someResource);

很显然,这样的code能编译,能正常工作(只要OtherFunction的实现没问题);只是,真的太不符合common sense了。怎么整?

直觉上来说,既然是一个指针的数组,而且要传给别人,那用std::vector<boost::shared_ptr<T>>最合适了,然后传个const&给别人,搞定。

不过看到瑞典同事有人用boost::ptr_vector,这个新鲜的玩意儿不常见,研究一下,原来是Boost.Pointer Container的一部分,用来保存heap-allocated objects,有放进去的指针会在出了作用域之后自动删除,所以有”own”的语义。

相比起shared_ptr的容器,有各种优点(见上面link里的advantages 1~8)。
当然,最主要的是语义上的不同:

  • boost::ptr_vector保存的是“own”的对象;
  • std::vector<boost::shared_ptr<>>保存的对象可以被别人own

然后,从效率上来说,ptr_vector显然要更好一点,因为创建shared_ptr还是有开销的。

回到上面的case,最简单的做法就是用shared_ptr的容器;更合适的做法是用ptr_vector。

那么,它们的效率到底能差多少呢?写段code跑跑看。

#include <boost/ptr_container/ptr_vector.hpp>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <cstdio>
#include <ctime>
#include <stdint.h>

class TSomeData
{
private:
  int data;
public:
  TSomeData(int d)
    : data(d)
  {
    // Empty
  }
};

const int TEST_ITERATIONS = 10000000;

typedef std::vector<boost::shared_ptr > TVectorOfShared;
typedef boost::ptr_vector TPtrVector;

int main()
{
  clock_t start;
  clock_t end;

  start = ::clock();
  TVectorOfShared vectorOfShared;
  for (int i = 0; i < TEST_ITERATIONS; ++i) {
  // Test vector of shared_ptr
    boost::shared_ptr data(new TSomeData(i));
    vectorOfShared.push_back(data);
  }
  end = ::clock();
  printf("Vector of shared:\n  Time executed: %u\n",
         static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));

  start = ::clock();
  TPtrVector ptrVector;
  for (int i = 0; i < TEST_ITERATIONS; ++i) {
  // Test ptr_vector
    TSomeData* data = new TSomeData(i);
    ptrVector.push_back(data);
  }
  end = ::clock();
  printf("PtrVector:\n  Time executed: %u\n",
         static_cast<uint32_t>((end - start) / (CLOCKS_PER_SEC/1000)));
  return 0;
}

跑一下结果如下(老式T400,跑着Ubuntu 14.04).

# g++ -O0
Vector of shared:
  Time executed: 7227
PtrVector:
  Time executed: 1507

# g++ -O2
Vector of shared:
  Time executed: 5090
PtrVector:
  Time executed: 731

无论是-O0还是-O2,都有着明显的差距。
结论:在语义合适的情况下,用ptr_vector有更好的效率。

最后,在stackoverflow上看到,如果项目的编译环境已经用c++11了,可以用std::vector<std::unique_ptr<T>>,测试一下,结果有点吃惊。

首先,unique_ptr相关的测试code如下:

  for (int i = 0; i < TEST_ITERATIONS; ++i) {
    std::unique_ptr data(new TSomeData(i));
    vectorOfUnique.push_back(std::move(data));
  }

测试结果:

# g++ -O0 -std=c++0x
Vector of Unique:
  Time executed: 5057
Vector of shared:
  Time executed: 4080
PtrVector:
  Time executed: 1681

# g++ -O2 -std=c++0x
Vector of Unique:
  Time executed: 835
Vector of shared:
  Time executed: 1794
PtrVector:
  Time executed: 743

让我吃惊的是:

  • 在-O0下,unique_ptr反而是最慢的,不明白(如果有人知道为什么,请告诉我)
  • 在-O2下,unique_ptr和ptr_vector有着差不多的效率;但是vector<shared_ptr>相比没有c++0x的时候效率也提升了很多!

结论:

  • 在合适的语义下,ptr_vector最好(好吧,我更习惯用boost…)
  • 能体会到,c++0x在标准库下有着更好的效率,至于是不是适合项目使用,看项目情况吧
  • 在不用考虑效率的时候(个人觉得,开发中不需要在执行效率上太抠,把优化留到实际使用发现性能之后),vector<shared_ptr>最万能。

Q.E.D

Share

自家网络的连接与设备

以前介绍过在一根网线上承载两路网络,用了近两年,感觉不错。现在家里的网络设备越来越多,所以分享一下我家的网络连接和设备情况。

先上图
家庭网络设备

稍微详细的介绍:

  • 电信的光猫,4个LAN口中其中一个是IPTV的VLAN,其余的3个可以当LAN来使用;
    用admin帐户登录,删除自带的PPPoE拨号,因为我要用自己的路由器拨号(注意记住原来的VLAN ID)
  • 路由器是Netgear的WNDR3700,UpLink接光猫的LAN口来PPPoE拨号;
    DHCP为PC、RaspberryPi、Laptop设置静态IP,设置DMZ主机为RaspberryPi;
    自带DDNS功能,不过Dyndns的免费服务没了,没用。。。
  • 树莓派用来24*7不间断工作(当然,时不时重启时例外^_^),上面装的主要服务有:
    WorkdPress (nginx + php + mysql)
    Samba Server
    DDNS(dyn.dns.he.net)
    接个老式硬盘当NAS(注意最好格式化成ext4,比NTFS快多了)
    Crontab里用几个脚本把Wordpress和一些重要文件备份到PC
    同时把PC上的Download文件夹rsync到老硬盘上
  • 台式机一般只有晚上才开,所以crontab是晚上备份,备份到Dropbox的文件夹,所以自动同步到Dropbox上;
    下载的电影、美剧都放在Download文件夹里,以便让RPi同步到老硬盘上,这样就算PC关机,一样能看;
    当然,更多的时候是开着PS3 Media Server当DMS,然后在PS3上看高清电影~
    不过台式机放在小房间,没铺网线,只能用电力线适配器来接,网速大概只能到90M左右,有待改善(如果有好建议,一定告诉我!)
  • PS3,不用说,高清的游戏机+播放器,画质、音效最佳;
    客厅的电视机还接着一个山寨的机顶盒,能看Samba上的内容。
  • 在卧室里放一个OTT的机顶盒(比如说小米盒子),既可以看在线的,也可以看PC和RPi上的内容。
    当然,还有AirPlay和Miracast的功能,偶尔会用,也挺不错的。

其它手机、Pad、笔记本,都是wifi连接,做该做的事情,就不多说了。

总之,因为路由器和树莓派是不关机的,而且树莓派的硬盘上的内容基本上和PC是同步的,所以我觉得这样最大的好处是:

  1. 免费的(喂,电费呢!)个人博客,以及Dropbox的备份
  2. 无论何时(不管PC是不是开着),都能在pad或者电视上看片~

Q.E.D.

Share

宜兴自驾&踏青

每年春天都会和艾威PMP学友会的同学们出去踏青,美其名曰“压力管理”,其实也就是从腐败到入门级的户外活动。
今年在启波同学的提议下决定自驾去宜兴竹海徒步&露营,论坛上发个贴,微信群里吼一声,很快就定下来有17个人/5辆车同行~

周五晚20:00,江桥收费站,集合点,只等到了3辆车,一辆表示只能去阳澄湖集合,另外一辆杯具地到了花桥,发现不是集合点。。。于是也只能去阳澄湖集合了。
在阳澄湖休息区,5辆车终于汇合,成为艾威PMP车友会的车队(虽然只有5辆车,俺选了7号,哈哈Open-mouthed smile),分配好手台,正式出发~
俺的7号车

晚23:00,顺利到达宜兴。

周六一早,开往竹海森林公园。在当地向导地带领下,光明正大地从山路进入,不用门票~
原生态的山路,没有台阶,全是在竹林里的小路,如果不跟着向导走,很容易找不到方向。原计划3个小时登上“黄塔顶”,但是2个小时之后,我们才爬了一个山头,剩下的路,是沿着山脊线上上下下地走到“黄塔顶”。
竹海徒步_1
竹海徒步_2

真正登顶已经是到下午了,终于“爬”到了苏南第一峰——黄塔顶。有些地方真是的“爬”上来的,相对来说难度是要大一点。
休整一番,拍拍照,时间不早了,俺们就不继续走原计划的罗岕村了,还是回去吧~
竹海徒步@黄塔顶

下山还是得跟着向导,可以走到竹海的景区里边,然后坐景区的交通车到出口。当我们坐交通车的时候,发现另外一队跟我们同时进山的人马,已经从罗岕村回来了,太厉害了!相比起来,我们真是个“腐败”的团队。。。
徒步线路通过RunKeeper记了,中间间断过几次,所以这是粗略的线路图竹海徒步_线路

周六晚上,露营露营!扎营点在”离墨山”,就是善卷洞所在的那座山的山顶。具体位置见我新建的foursqure的地点。往山顶的盘山路修得很好,走汽车没有任何问题,山顶上有一大片空地,还有水井,条件是相当不错的。旁边还有一个往更高处的台阶,走上台阶就是真正的山顶了,是看日出的最佳地点。

停好车,大家拿出了各式各样的装备,有正常的露营帐篷,有迪卡侬的公园帐篷,还有开着福特探险者,直接把后排放下变成床的~露营_帐篷

到了晚上,燃起篝火,点着露营灯,在月光的照耀下,围坐起来玩杀人游戏,这是真正地放下一切烦恼,释放压力,享受自然~
露营_篝火

之前特意查过日出的时间,06:12分,周日早上,我睡懒觉没爬起来。。。但是有不少同学真的5点多就起床,看日出了,还拍了很多精彩的照片!
露营_日出_1露营_日出_2

离开露营地之前,大家把车排成了人字形,留影一张~
露营_车队

按照计划,周日早上开车到安徽游太极洞。话说这太极洞的大部分应该还是在江苏境内,只是入口在安徽罢。
太极洞_1

上一次玩宜兴的洞,还是在小学的时候。现在终于又一次玩了一下洞,感觉跟小时候真是不同。小时候游洞,大家带着手电,吸血鬼的牙套,在黑漆漆的洞里吓人,很好玩;成年了再看宜兴的山洞,感叹大自然的鬼斧神工,顺便感叹和吐槽一下里面的“人造”设施——某些光线打上去,效果还蛮灵异的。。。
太极洞_2太极洞_3太极洞_4

游完洞,觅完食,车队就浩浩荡荡地开回上海了。值得一提的是,周日傍晚回上海,沪宁高速的江桥段一向很堵,所以可以走S26进上海,一点都不堵,只是G2 往S26拐的时候,路口相当复杂,而往上海方向的路口又很小,容易错过。俺们车队就不幸有一辆车走错了口子,只能在手台里说bye bye了 😀

最后,来张大合影。
大合影

Q.E.D

Share