技术头条 - 一个快速在微博传播文章的方式     搜索本站
您现在的位置首页 --> 编程语言 --> 分享两个强符号,弱符号引起的编译问题

分享两个强符号,弱符号引起的编译问题

浏览:884次  出处信息

   

分享两个强符号,弱符号引起的编译问题

由于SP的编译分为debug和release两种模式(话说也没有谁不这么编吧),往往在debug开发的时候没有遇到的问题,在release阶段暴露了,这里面最容易出现的就是弱符号丢失,导致符号定位出错或者符号没找到的问题。


两个真实的案例

Case1: 模板类的静态函数特例化问题

这个场景是这样的,一个模版类有一个可以特例化的静态初始函数,不同的模版可以去特例化自己的初始化函数。

  • foo.h文件中定义了一个模板类和一个静态方法,这个方法有一个默认的实现。

      //////// foo.h ///////
      template<typename T>
      class foo{
      public:
          static void init(){
              return;
          }
    
      };
    
      template<> //增加int模版的声明,但是没有增加float模版的
          void foo<int>::init();
    
  • foo_int.cpp 文件中有一个int类型的特例化方法的实现

      /////// foo_int.cpp ///////
      #include "foo.h"
      template<>
      void foo<int>::init(){
          printf("init int foo");
      }
    
  • foo_float.cpp 文件定义了一个float类型的特例化方法的实现

      /////// foo_float.cpp ///////
      #include "foo.h"
      template<>
      void foo<float>::init(){
          printf("init float foo");
      }
    
  • main.cpp 文件定义了一个函数分别调用两个方法

      ////// main.cpp ///////
      #include "foo.h"
    
      int main(){
          foo<int>::init();
          foo<float>::init();
      }
    
  • 上述程序在debug模式下会同时执行foo<int>和foo<float>两个foo模版的的init函数,在release模式下只会执行foo<int>模版的init函数

    Case2: CPP里的内联函数展开问题

    这个场景是这样的,一个内联函数定义在了cpp文件内部

  • time.h文件中定义了一个静态函数

      //////// time.h //////////
      class time{
          public:
              static int getTime();
      }
    
  • time.cpp文件中把这个函数声明为内联函数,cpp中的内联函数只在当前cpp文件内展开。

      //////// time.cpp //////////
      inline int getTime(){
          return 0;
      }
    
  • main.cpp文件中调用这个getTime函数

      //////// main.cpp //////////
      #include "time.h"
      int main(){
          time::getTime();
      }
    
  • 上述程序在debug模式下是能编译通过的,在release模式下提示getTime找不到。


    强符号和弱符号的定义,规则与使用

    什么是强符号和弱符号

    教科书上是这么写的:

    编译时,编译器向汇编器输出每个全局符号,或者是强,或者是弱,而汇编器把这
    个信息隐含的编码在重定位目标文件的符号表里。
    

    我们用readelf查看目标文件:

  • LOCAL 开头的是局部符号,这些符号只能在当前目标文件可见,无法给别人使用。

  • GLOBAL 开头的都是全局强符号,这些符号,可以被外部目标文件访问。

  • WEAK 开头的就是全局弱符号,这些符号可以引用,但是是可以被改写的。

  • 简单说来:

  • 已初始化的全局变量是强符号  strong symbol

  • 未初始化的全局变量是弱符号  weak symbol

  • 这两个东西在链接时候的使用规则

  • 不允许有多个强符号

      简单来说,就是重复定义,嗯,就这么简单。
    
  • 如果一个强符号和多个弱符号同时存在,那么使用强符号

      这个也很简单了,如果出现重复定义,比如两个全局变量,第一个初始化了,第二个没有,
      那么第二个变量所指向的符号其实是第一个符号,这是一个经常遇见的坑。所以自定义全
      局变量最好初始化掉,最起码冲突了编译器会报错,不是么。最好在编译时指定-fno-common
      参数,会警告那些重复出现的符号,无论强弱。
    
  • 如果有多个弱符号,那么选择使用弱符号中占用空间最大的那一个

     这个和教科书上写的不太一致了,教科书上说的是任意选取一个。假如,弱符号占用的空
     间比强符号大,怎么办?这时候仍然选择强符号,同时ld会弹一个警告。
    
  • 弱符号在静态链接或者动态链接中都不会生效,以下是gcc里面的一段说明

     When the link editor searches archive libraries [see ``Archive
     File'' in Chapter 7], it extracts archive members that contain
     definitions of undefined global symbols. The member's definition may
     be either a global or a weak symbol. The link editor does not extract
     archive members to resolve undefined weak symbols. Unresolved weak
     symbols have a zero value.
    
  • 为什么要定义这两个东西

  • 弱符号允许相同符号名出现

  • 声明为弱符号的函数或变量,用户可以去改写他,类似于c++里面的重载

  • 弱符号可有可无,可以给程序更多的扩展

      ///////// 如果有定义 foo 函数,就调用;没有就拉倒 /////////
    
      extern void foo(void) __attribute__((weak));
      void fun(void) {
          if (foo) foo();
      }
    
      int main(){
          func();
          return 0;
      }
    

  • 回头解释一下上述两个case

    上述两个case其实是一个原因,弱符号机制把问题屏蔽了。

  • 第一个case中,debug模式下,main.o会生成float模版类的弱符号表,而foo_float.o中是有这个函数的强符号实现的,所以最终会被强符号替换掉。而release模式下,头文件没有声明这个函数的实现,所以不会产生这个弱符号表,直接用默认的init函数去替换到代码段内,导致即使定义了强符号的实现但压根就没被执行。

      //debug模式下的man.o
                       U _ZN4fooIfE7initEv
      0000000000000000 W _ZN4fooIiE7initEv
    
      //release模式下的man.o
                       U _ZN4fooIiE7initEv
    
  • 第二个case中,debug模式下,cpp的内联函数不会被展开,同时在符号表中是弱符号,所以外部调用的时候会优先选择这个弱符号。而release模式下,cpp的内联被展开了,头文件中的定义就找不到对应的实现了,所以编译错误。

      //debug模式下的time.o
      0000000000000000 W _ZN4time7getTimeEv
    

  • Cm2对Zookeeper的改造

    zookeeper客户端提供这样一个日志函数,这个函数会把日志信息打印的到标准输出:

  • 日志信息打满屏幕太乱,

  • zookeeper是CM2的一个重要组成部分需要有日志记录追踪他一个一举一动,

  • 铁丑同学娴熟的使用弱符号,重新改写了cm2接口,接口如下:

        ZOOAPI void log_message(ZooLogLevel curLevel,
            int line, const char* funcName, const char* message);
    

    为了实现重载,在这里把它改成弱符号

        ZOOAPI void log_message(ZooLogLevel curLevel,
            int line, const char* funcName, const char* message)
            __attribute__((weak));
    

    最后在使用的地方重载这个函数,这样程序在链接的时候,会把这个弱符号函数重定向到这个地方。

        void log_message(ZooLogLevel curLevel,
        int line, const char* funcName, const char* message){
            alog::Logger::getRootLogger()
            ->log(ZK_TO_ALOG_LEVEL[curlLevel],"",line,funcName,"%s".message);
        }
    


QQ技术交流群:445447336,欢迎加入!
扫一扫订阅我的微信号:IT技术博客大学习
© 2009 - 2024 by blogread.cn 微博:@IT技术博客大学习

京ICP备15002552号-1