zoukankan      html  css  js  c++  java
  • 使用内联函数的一个问题

    最近碰到一个与内联方法有关的编译问题,记叙如下。

    问题背景

    类Scheduler的实现如下所示,其中方法SetStates()仅仅被类本身使用(暂且先不管它的public属性)。

    // scheduler.hpp
    class Scheduler {
    
    public:
      Scheduler():m_state1(0),m_state2(0) {}
      ~Scheduler() {}
    
      inline void SetStates(int state1, int state2);
    
      int GetState1() {
        return m_state1;
      }
      int GetState2() {
        return m_state2;
      }
    
    private:
      int m_state1;
      int m_state2;
    };
    
    // scheduler.cpp
    void Scheduler::SetStates(int state1, int state2) {
      m_state1 = state1;
      m_state2 = state2;
    }
    
    

    如上代码构建正常。之后,新建一个新的类SchedulerMgmt,并且在其中使用了类Scheduler当中的SetStates()方法:

    // SchedulerMgmt.cpp
    void SchedulerMgmt::UpdateSchedulerState(int state1, int state2) {
      m_scheduler.SetStates(state1, state2);
    }
    

    重新编译,提示错误:

    scheduler.hpp:10: warning: inline function ‘void Scheduler::SetStates(int, int)’ used but never defined
    /tmp/ccsOhsNk.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)':
    scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)'
    collect2: ld returned 1 exit status
    

    由于编译错误的产生仅仅是在新增加类SchedulerMgmt,并在其中调用了Scheduler中的SetStates()之后才产生,同时该方法恰好声明为内联方法,很自然的便怀疑到这个方法的声明之上:inline。于是先后尝试了下面两种方法来排错:

    • A: 在scheduler.cpp当中为SetScheduleParams方法的实现加上"inline"关键字。
    • B: 去掉scheduler.hpp当中SetScheduleParams方法的“inline”声明。

    测试的结果是第一种方法A不管用,编译错误依然存在,方法B解决了问题。

    这样的结果让自己感到疑惑不已。众所周知,将方法声明为内联的方式有两种,其一为在class的定义当中定义成员方法;其二是使用inline关键字。第一种方式经过测试是可行的,然而为什么第二种方式并没有达到目的呢?难道问题出在外部调用内联方法上面?这是第一个疑惑。

    方法B在将SetStates()的内联属性去掉问题消失,这是不难理解的。但当我仅仅在其定义上加上inline修饰符,便又会出现问题,只是这个时候仅仅剩下链接的问题:

    /tmp/ccoizmZA.o: In function `SchedulerMgmt::UpdateSchedulerState(int, int)':
    scheduler-mgmt.cpp:(.text+0x111): undefined reference to `Scheduler::SetStates(int, int)'
    collect2: ld returned 1 exit status
    

    这是另外一个疑惑。看来对于内联的许多细节,自己之前并未注意到。

    内联方法的基本要求

    在查阅了C++标准最新的Draft之后,从中找到了针对“方法A没有解决编译错误”的解释:

    An inline function shall be defined in every translation unit in which it is odr-used and shall have exactly
    the same definition in every case (3.2). [ Note: A call to the inline function may be encountered before its
    definition appears in the translation unit. —end note ].

    这里提到内联函数的基本要求:任一调用该函数的地方均需要看到它的定义(这一点上,C++中的template也有这样的要求)。所以在每一个编译单元都需要定义(注:在程序的构建工程当中,编译阶段会对每一个源代码文件分别编译、汇编,之后将之后的输出文件进行链接等处理,这里的编译单元可以看做是一个个独立的源代码文件。)可见,将内联成员方法定义在类的定义里面是最为稳妥的。在方法A当中,类SchedulerMgmt中尽管可以看到SetStates()声明为inline,但是却无法见着它的定义,便提示错误。这即是如上第一个疑惑的解答案。

    编译器对于内联方法处理上的差异

    对于第二个疑惑,其实可以归结于一点:编译阶段对于类中的普通成员方法与内联成员方法的处理有差异,但差异在什么地方?要回答这个问题,可以从编译之后的结果上去看。

    使用objdump -s -d scheduler.o分别查看输出修改前后的scheduler.cpp编译之后的汇编码:

    可见,在将SetStates()定义为内联方法时,在编译之后的目标文件中并不会包含该函数的指令,也就是说编译器将一个方法内联之后,并不会在目标文件当中将其保留,就如同宏在预处理阶段直接文本扩展一样。因此,到这里第二个疑惑解开。

    上面是笔记的主要内容。其实在参考资料2链接的一篇文章介绍了有关extern inline的知识,权因本笔记是对工作中问题的一次小总结,所以不再将其纳入讨论。

    编译器:

    g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-48)
    Copyright (C) 2006 Free Software Foundation, Inc.

    参考:
    1. << Working Draft, Standard C++, n4296.pdf >>
    2. http://www.cnblogs.com/cnmaizi/archive/2011/01/19/1939686.html
    3. << 程序员的自我修养—链接、装载与库 >>
  • 相关阅读:
    把DataSet转换成JSON
    adb devices无法连接设备
    fiddler运行报错:Could not load type 'System.Runtime.CompilerServices.ExtensionAttribute'
    Jira 通过csv导入数据
    postman设置环境变量
    VirtualBox主机与虚拟机文件夹共享
    python selenium环境配置
    python json.dump中文乱码问题
    python字典
    python练习:猜价钱小游戏
  • 原文地址:https://www.cnblogs.com/yuxin2012/p/4881021.html
Copyright © 2011-2022 走看看