loop_in_codes

低调做技术__欢迎移步我的独立博客 codemaro.com 微博 kevinlynx

静态库中全局变量的初始化问题

 

在我自己写的一个工厂类实现中,每个产品会注册创建接口到这个工厂类。工厂类使用这些
注册进来的创建接口来完成产品的创建。其结构大致如下:

product *factory::create( long product_type )
{
    creator c = m_creators[product_type];
    return c();
}

factory::instance().register( PRODUCT_A_TYPE, productA::create );
...
factory::instance().create( PRODUCT_A_TYPE );

这个很普通的工厂实现中,需要写上很多注册代码。每次添加新的产品种类时,也需要修改
这些的注册代码。而恰好,这些注册代码可能会被放在一个统一的地方。为了消除这个地方
,我使用了偶然间看到的<Modern C++ design>里的做法:

const bool _local = factory::instance().register( PRODUCT_A_TYPE,...

也就是说,通过对全局常量_local的自动初始化,来自动完成对该产品的注册。

结果,因为这些代码全部被放置于一个静态库。最终的代码文件结构大致为:

lib
    - product_a.cpp : 定义了全局常量_local
    - product_a.h
    - factory.cpp
    - factory.h
exe
    - main.cpp

现在看起来世界很美,因为factory甚至不知道世界上还有个跟上层逻辑相关的product_a。
这种模块耦合几乎为0的结构让我窃喜。

悲剧的事情首先发生于,开VC调试器,发现打在product_a.cpp里的断点失效。就是那个总
是提示说没有为该文件加载调试符号。开始还不在意,以为又是代码和调试符号文件不匹配
的原因,折腾了好久,不得其果。

后来分析了下,发现这个调试提示,就像我开着调试器打开了一个非本工程的代码文件,而
断点就打在这个文件里一样。也就是说,VC把我product_a.cpp当成不是这个工程里的代码
文件。

按照这个思路写些实验代码,最终发现问题所在:VC链接器根本没链接进product_a.cpp里
的代码。表现出来的情况就是,该编译单元里的全局常量(全局变量一样)根本没有得到初
始化,因为我跟到factory::register并没有被调用到。为什么VC不链接这个编译单元对应
的目标文件?或者说,为什么VC不初始化这个全局常量?

原因就在于,product_a.cpp太独立了。一个在整个编译链接阶段都无法确定该文件是否被
使用的文件,VC就直接不链接了。相反,当在factory.cpp里写下类似代码:

void test()
{
    product_a obj;
}

虽然说test函数不会被调用,一切情况也变得正常了。好了,不扯了,给最后我的结论:

1、如果静态库中某个编译单元在编译阶段被确认为它并没有被外部使用,那么当这个静态
库被链接进可执行文件时,链接器忽略掉该编译单元里的代码,那么,链接器自然也不会为
该编译单元里出现的全局变量常量生成初始化代码(关于这部分初始化代码可以阅读
<linker and loader>一书);
2、上面那条结论存在一种传染性,意思是,当可执行文件里的代码使用到静态库中文件A里
的代码,A里又有地方使用到B里的代码,那么B依然会被链接。这种依赖性,应该可以让编
译器在编译阶段就发现(显然,上面我举的例子里,factory只有在运行期间才会依赖到
product_a.cpp里的代码)

posted on 2010-01-17 19:34 Kevin Lynx 阅读(15534) 评论(19)  编辑 收藏 引用 所属分类: c/c++通用编程

评论

# re: 静态库中全局变量的初始化问题 2010-01-17 20:08 OwnWaterloo

试试在product_a.cpp中, 将:
const bool _local = factory::instance().register( PRODUCT_A_TYPE,...
改为:
extern const bool local_ = factory::instance()...
或者:
namespace {
extern const bool local = ...
}

可能有效,可能无效。


即使有效,依然不能保证local的初始化顺序。main之后执行的代码,可以保证所有产品已经被注册,main之前没有保证。
  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-17 22:41 LOGOS

const bool _local = factory::instance().register( PRODUCT_A_TYPE,...
wow, are u crazy?  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-17 23:00 Kevin Lynx


@OwnWaterloo
@LOGOS
加extern是什么意思?文章中的例子,只是因为链接器没有为静态库中的全局对象生成初始化代码。我也并不关心每一个local的初始化顺序,因为他们是全局的,所以他们肯定会先于main被初始化。整个文章的意思,其实是说,链接器并没有生成这个自动初始化的代码,因为链接器觉得这几个“没有”被使用的全局对象不需要,所以就没生成。

  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-17 23:04 OwnWaterloo

@Kevin Lynx
在C++中, 非local的const对象, 默认是staic链接……

a.cpp
int g_i;

虽然g_i也不一定能被添加到最终代码中, 但机会应该比:

a.cpp
static int g_i; 要大。
  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-18 04:19 药品中国网

很好 学习了。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-18 08:52 Kevin Lynx

@OwnWaterloo
这个机会是什么意思?  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-19 12:06 OwnWaterloo

@Kevin Lynx
编译器根据什么规则来判断某个没有被使用的符号可以不必链接到binary中?
这个规则我不了解, 可能C++标准有描述, 也可能没有。

我只是猜想 :
1. 如果某个extern符号没有被链接到binary中, 那将其改为static, 应该也不会被链接到binary中。
2. 如果某个static符号没有被链接到binary中, 那将其改为extern, 也许就会被链接到binary中。

这就是机会的意思。

btw, 改为extern const 有效么?
  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-20 09:14 Kevin Lynx

@OwnWaterloo
一样的。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-01-20 19:44 李现民

我以前也试过这样的方法, 项目用的是vc6,只debug版本下对象是可以生生的,release版本中就被人优化掉了,没有找到保持这种级别低耦合度的替代方案。
到现在为止, 我写的那个factory类再也没有用过,因为看不到优势在哪里, 还不如现写一个工厂来得清晰  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-02-09 19:44 金庆

Issues with static variables in static libraries
( http://social.msdn.microsoft.com/Forums/en/vclanguage/thread/5c5ee89d-2a38-483d-8550-484550901170 )

You can use the /INCLUDE linker option:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/_core_.2f.INCLUDE.asp

Or you place a #pragma in your code like this:
#pragma comment(linker, "/include:SymbolToUse")
  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-02-10 12:14 李现民

@金庆
不好使
我想应该是这个问题:
Only problem is that the symbol is not very...simple. I have a class called fxAttributeEditor, but you can't just do /include:fxAttributeEditor. The symbol is actually:

??_C@_0BC@OFGGEBAJ@fxAttributeEditor?$AA@

And that does work, but having to add that seems like a terrible coding decision. It seems like going the dynamic library route would be far better. I've had a variety of really good reasons for not wanting to do that. I'm not against DLLs, but in some cases (like mine) I don't believe it's the best choice. Is there not any better way to go about this?

Thanks,

这是引用原文, 大概意思是“不能够在#pragma comment(linker, /include:)中直接使用你定义的那个变量名”,因为c++编译时会将其改为??_C@_0BC@OFGGEBAJ@fxAttributeEditor?$AA@这样的的形式。

我不知道是不是真的这样, 但我的实验结果是找不到我自己定义的变量名  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-02-10 15:52 金庆

@李现民
试了一下,确实如此。我打开lib文件查到了加了前后缀的变量名,然后在主调程序中添加/include指令,这样才有效。我原来以为只需加在静态库中呢。实际上,静态库中加不加/include指令,都会输出符号。但是static变量是无论如何都不会输出符号的。/include指令与引用该符号的作用一样。所以我的结论是应该输出一个init()函数来初始化静态库。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2010-02-10 16:43 Kevin Lynx

@金庆
@李现民
3q for your comments.
今天随便看了个文档http://linux.chinaunix.net/bbs/thread-910296-1-1.html,<Linker Algorithm.pdf>恰好在里面看到和我这里提到的相同的2点结论  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2011-11-07 01:15 溪流

这个问题最终解决了吗?  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2011-11-07 09:46 Kevin Lynx

@溪流
解决办法就是换成常规方法:显示初始化。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2011-11-07 10:35 李现民

@Kevin Lynx
这个问题后来我试过,在我的项目里( 前面提到的,使用了VC6),问题出在使用singleton生成单个factory对象的地方。如果将factory的构造函数设计为private,同时提供一个类get_instance()的public static方法,会产生多个factory实例。具体表现就是,register()函数的确执行了, 但是所有的注册内容都注册到了第一个factory对象身上,等到运行期使用create方法的时候,使用的却是后面新生成的factory对象,因此会发生没有注册过的假象。

解决方法为:将factory()构造函数声明为public,在factory类外面定义一个singleton函数,并一直使用这个函数进行注册与创建。但这种方法的缺点是你无法从语法上确保所有用户使用的都是这个singleton对象。

怀疑为VC6的bug,不过由于已经基本满足了需要,未继续深究。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2013-06-16 11:14 戴进

在头文件中

#ifndef _DEF_AUTO_FACTORY
#define _DEF_AUTO_FACTORY
static CFactory __AutoFactory;
#endif

这样能解决问题,但是间接的也导致了你的这个类被公开了。  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2013-06-16 13:36 戴进

我暂时性是这么解决自动初始化静态库的。


a.cpp中,有一个类

class a
{
public:
a()
{
...这里初始化
}

~a()
{
...这里做释放
}

bool _LinkProc(){return true;}
//这个函数,也就是为了链接用的。
};

a m_a;//全局变量

bool b = m_a._LinkProc();
//实际上光是这样,也还是不会链接,那怎么办呢,问了下度娘,她说,要访问到的全局变量,才会链接,那么最后我们到头文件中。

h文件中
extern bool b;//注意extern只是引用,不等同于访问。
static bool c = b;//这里对b的访问,就自动链接到



  回复  更多评论   

# re: 静态库中全局变量的初始化问题 2014-05-15 19:35 HK

兄弟,这个问题我也遇到了,我采用的方法是新建一个新的.cpp文件(for_register.cpp)
然后“const bool _local = factory::instance().register( PRODUCT_A_TYPE,... ”这个定义在 for_register.cpp这个文件里
最后在一定能被链接进去的一个.cpp里 include " for_register.cpp"
这样基本上就解决了。
你每新增一个产品就要到 for_register.cpp里面去注册一下。  回复  更多评论   


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理