Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/cplusplus/147.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C++ 程序是如何通过链接器的?_C++ - Fatal编程技术网

C++ 程序是如何通过链接器的?

C++ 程序是如何通过链接器的?,c++,C++,这是程序link\u函数.cpp #include <iostream> class Base { public: Base() { init(); // first condition // log(); // second condition } virtual ~Base() {} virtual void log() = 0; pr

这是程序
link\u函数.cpp

#include <iostream>

class Base {
    public:
        Base() {
            init();     // first condition
         //   log();    // second condition
        }

        virtual ~Base() {}

        virtual void log() = 0;
    private:
        void init() {
            log();
        }
};

class Derived: public Base {
    virtual void log() {}
};

int main() {
    Derived d;
    return 0;
}
init()
函数如何传递链接器而不产生
未定义到引用日志()的
错误

第二个条件

当我注释
int()
并只调用log()时,它产生了一个
链接器错误

对“Base::log()”的未定义引用


它们之间的区别是什么?

您的
日志功能是虚拟的,所以链接器不必担心。链接器不关心虚拟函数,因为它们的地址只有在运行时才知道

代码的问题在于,您从
Base
构造函数调用虚函数。但是在调用
Base
构造函数时,
派生的
尚未构造,因此
log
函数有点未定义


Derived
类的每个对象都包含
Base
类的“子对象”,该类在对象本身之前构造(即,在
Derived
之前调用
Base
构造函数)。当
Base
构造函数执行时,它不知道
派生的
类的虚拟表,因此它无法解析
日志
要调用的地址。因此,它将其视为纯虚拟函数并中止执行。

派生类是从基类开始构建的。在Base的构造函数期间,派生的构造函数尚未执行。因此,vtable尚未完成

在构造函数中调用虚拟函数(即使是非纯函数)是很淘气的。这意味着类设计不正确

这就是为什么这是个坏主意:

#include <iostream>

using namespace std;

class Base {
    public:
        Base() {
            init();
         //   log();
        }

        virtual void log() { cout << "stange outcome 1" << endl; }
    private:
        void init() {
            log();
        }
};

class Derived: public Base {
    public:
    virtual void log() { cout << "stange outcome 2" << endl;}

    Derived() 
    : Base() 
    { 
        log();
    }
};

int main() {
    Derived d;
    d.log();
    return 0;
}
解释原因:

当我们创建派生类的对象时,程序执行以下操作:

  • 为派生内存分配足够大的内存
  • 将对象的vtable指针设置为指向基础的vtable
  • 执行Base::Base()[对log()的调用将调用Base::log()]
  • 将对象的vtable指针设置为指向一个虚拟对象的vtable
  • 执行派生::派生()[对log()的调用将调用Dervied::log()]
  • 对象现在已完全构造

  • 这是一个强大的实现细节。但是链接器无法可靠地发现v-table包含未初始化的条目。最重要的是因为C++类有两个V表。在构造函数完成执行之前,真正的类不会被放置到位。我猜链接器可能会在第一种情况下通过v-tables找到派生类的
    log()
    实现。但为什么它找不到一个处于第二种状态的。它知道在基构造函数中调用虚函数是一种糟糕的做法。我只是好奇细节。你能给我推荐一些这方面的书吗?非常感谢。您说过,
    链接器不关心虚拟函数,因为它们的地址只有在运行时才知道。
    但在条件2中,链接器会抱怨。看起来编译器将构造函数中的
    日志
    识别为非虚拟方法(因为它无论如何都不能调用虚拟方法)并尝试直接调用
    log
    。它不会抱怨init,因为1)它可以在构造函数外部调用,2)完整的源代码分析在计算上很昂贵(并且在某种程度上受到限制,请参见停止定理)。但这是我的意见。非常感谢你的回答。我知道在基构造函数中调用虚函数实际上是一种糟糕的做法。然而,如此相似的条件1和条件2产生了不同的结果。我只是对这些很好奇。在构造过程中,Base完全不知道有任何派生自它的东西。它的vtable是一个基地的vtable。当调用派生的构造函数时,它会修改Base的vtable(实际上可能会用一个新的替换它)。因此,在完成编译阶段后,Base的vtable在两种情况下是否相同?因为它们都通过了编译器阶段,而条件2在链接器阶段失败。
    #include <iostream>
    
    using namespace std;
    
    class Base {
        public:
            Base() {
                init();
             //   log();
            }
    
            virtual void log() { cout << "stange outcome 1" << endl; }
        private:
            void init() {
                log();
            }
    };
    
    class Derived: public Base {
        public:
        virtual void log() { cout << "stange outcome 2" << endl;}
    
        Derived() 
        : Base() 
        { 
            log();
        }
    };
    
    int main() {
        Derived d;
        d.log();
        return 0;
    }
    
    Compiling the source code....
    $g++ -std=c++11 main.cpp -o demo -lm -pthread -lgmpxx -lgmp -lreadline 2>&1
    
    Executing the program....
    $demo 
    stange outcome 1
    stange outcome 2
    stange outcome 2