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. << 程序员的自我修养—链接、装载与库 >>
  • 相关阅读:
    HYSBZ 3813 奇数国
    HYSBZ 4419 发微博
    HYSBZ 1079 着色方案
    HYSBZ 3506 排序机械臂
    HYSBZ 3224 Tyvj 1728 普通平衡树
    Unity 3D,地形属性
    nginx 的naginx 种包含include关键字
    Redis 出现NOAUTH Authentication required解决方案
    mysql 8.0出现 Public Key Retrieval is not allowed
    修改jar包里的源码时候需要注意的问题
  • 原文地址:https://www.cnblogs.com/yuxin2012/p/4881021.html
Copyright © 2011-2022 走看看