browser icon
You are using an insecure version of your web browser. Please update your browser!
Using an outdated browser makes your computer unsafe. For a safer, faster, more enjoyable user experience, please update your browser today or try a newer browser.

__purecall 链接错误

Posted by on 2009 年 05 月 05 日

你可以任意转载本文,但请在转载后的文章中注明作者和原始链接。
媒体约稿请联系 titilima_AT_163.com(把“_AT_”换成“@”)。

不知道诸位有没有过这样的经历:本是简单合法的 C++ 代码,但编译链接的时候却出现了如下的链接错误:

> error LNK2001: 无法解析的外部符号 __purecall

在解决这个问题之前,我们可以一起重现这个错误,先。新建一个 Win32 工程,打开 VS 的工程设置,修改如下项目:

  1. 将“启用 C++ 异常”设为“否”;
  2. 将“基本运行时检查”设为“默认值”;
  3. 将“缓冲区安全检查”设为“否(/GS-)”;
  4. 将“启用运行时类型信息”设为“否(/GR-)”;
  5. 将“忽略所有默认库”设为“是(/NODEFAULTLIB)”。

然后,在源文件中输入如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <Windows.h>
#include <tchar.h>
 
void* operator new(size_t _Size)
{
    return HeapAlloc(GetProcessHeap(), 0, _Size);
}
 
void operator delete(void* ptr)
{
    HeapFree(GetProcessHeap(), 0, ptr);
}
 
class A
{
public:
    virtual void foo(void) = 0;
};
 
class B : public A
{
public:
    void foo(void)
    {
        OutputDebugString(_T("Hello, World!"));
    }
};
 
extern "C" void WinMainCRTStartup(void)
{
    A* p = new B;
    p->foo();
    delete p;
}

我来解释一下:修改工程设置实质上是剥离了工程对 VS 默认 CRT 的依赖,因此这里再使用的 C++ 内容就必须都要自己实现,包括 new、delete 以及程序的入口——也就是 WinMainCRTStartup。在代码输入完成后,构建这个工程,就可以如愿得到本文开头的那个链接错误了。
虽然工程并未构建成功,不过你仍然可以用反汇编工具打开编译器生成的 obj 文件,秘密就藏在其中。下面我只是简单说明一下 B 对象的构造过程,不列汇编代码了就。

  1. 调用 new 申请内存空间。
  2. 调用 A::A,使用 A 类的虚表来初始化 A 类的子对象。
  3. 调用 B::B,使用 B 类的虚表来初始化对象。

问题就出现在第 2 步:A 类是个纯虚类,那么它的虚表是什么样子的?我在 obj 文件中找到了这个表,如下。

1
2
3
public ??_7A@@6B@
; const A::`vftable’
??_7A@@6B@      dd offset __purecall

如你所见,虽然 A::foo 是个纯虚函数,但编译器仍然为之准备了虚表中的一个栏位,只不过这个栏位的内容被一个名为 __purecall 的符号代替了。由于 __purecall 这个符号存在于默认的 CRT 之中,但我们先前又剥离了对 CRT 的依赖,因此在链接的时候就会出现 LNK2001 的链接错误。

解决方法很简单,再准备一个简单的 _purecall 就可以了:

1
2
3
4
extern "C" int __cdecl _purecall(void)
{
    return 0;
}

当然,其中最好加个断言什么的,以示这是一个不应到达的区域。
此外,VS 的 C++ 编译器对此种情况提供了特殊的支持,也就是使用 __declspec(novtable) 来定义无虚表的纯虚类。考虑如下代码:

1
2
3
4
5
class __declspec(novtable) A
{
public:
    virtual void foo(void) = 0;
};

这样亦能解决这个问题。

订阅本站

4 Comments

  • At 2009.05.06 16:16, sPhinX said:

    novtable,ATL中也有类似的宏?

    • At 2009.05.15 00:12, bzero1982 said:

      用这种方式可以扑捉纯虚函数调用
      void PureCall() {
      __debugbreak();
      }
      _set_purecall_handler(PureCall);

      • At 2009.05.15 00:15, bzero1982 said:

        还有定义纯接口使用
        DECLARE_INTERFACE 这个宏比较好

        是这样定义的
        #define DECLARE_INTERFACE(iface) interface DECLSPEC_NOVTABLE iface

        #define DECLSPEC_NOVTABLE __declspec(novtable)

        • At 2009.05.15 00:28, bzero1982 said:

          不过我记得有这样编译的啊
          都编译通过了,没出现什么问题,要不然我肯定有印象的

          (Required)
          (Required, will not be published)