给出以下代码:
A2
_declspec(dllimport) void SomeFunc();
struct Foo
{
Foo();
~Foo();
};
inline Foo::Foo() { }
inline Foo::~Foo()
{
SomeFunc();
}
1小时
#include "A2.h"
extern "C" void TriggerIssue(); // <-- This!
extern "C" inline void TriggerIssue()
{
Foo f;
}
MyTest.cpp
#include "A1.h"
int main()
{
return 0;
}
请在此处查看问题背景。
当MyTest.cpp编译为可执行文件时,链接器会抱怨它SomeFunc()
是未解决的外部文件。
这似乎是由于A1.h中的TriggerIssue的多余(错误?)声明引起的。注释掉将导致链接器错误消失。
有人可以告诉我这是怎么回事吗?我只想了解到底是什么导致编译器在有或没有该声明的情况下表现不同。上面的代码段是我试图写出我所遇到的情况的最小可验证示例。请不要问我为什么它是这样写的。
下注者注意:这不是有关如何解决未解决的外部符号错误的问题。因此,请停止投票以将其作为重复项关闭。我没有足够的信誉来删除该链接,该链接一直显示在此帖子的顶部,并声称此问题“可能有一个可能的答案”。
无论第一个声明如何,都存在该问题,如果注释掉第一个声明并TriggerIssue()
在程序中调用,该问题仍然存在。
这是由于cl
生成的代码在退出SomeFunc()
时调用Foo
的析构函数时要调用而引起的TriggerIssue()
,而不是由两个声明之间的任何怪癖或相互作用引起的。如果您不注释掉非inline
声明,它之所以出现,是因为另一个声明告诉编译器您希望它为该函数生成一个符号,以便可以将其导出到其他模块,从而阻止它实际内联。代码,而是强制其生成常规函数。生成函数主体时,它以对的隐式调用结尾~Foo()
,这是问题的根源。
inline
但是,如果注释掉了非声明,则编译器会很乐意将代码视为内联代码,并且只有在您实际调用它时才会生成它;因为您的测试程序实际上并未调用TriggerIssue()
,所以代码永远不会生成,~Foo()
也不会调用;由于析构函数也是inline
,因此允许编译器完全忽略它,而不为其生成代码。但是,如果您确实TriggerIssue()
在测试程序中插入了对的调用,则会看到完全相同的错误消息。
测试1:两个声明都存在。
我直接编译了代码,将输出传递到日志文件。
cl MyTest.cpp > MyTest.log
生成的日志文件为:
MyTest.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:MyTest.exe
MyTest.obj
MyTest.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) void __cdecl SomeFunc(void)" (__imp_?SomeFunc@@YAXXZ) referenced in function "public: __thiscall Foo::~Foo(void)" (??1Foo@@QAE@XZ)
MyTest.exe : fatal error LNK1120: 1 unresolved externals
测试2:未inline
声明的注释掉了,TriggerIssue()
称为main()
。
我对您的代码进行了几处更改:
// A2.h was unchanged.
// -----
// A1.h:
#include "A2.h"
//extern "C" void TriggerIssue(); // <-- This!
extern "C" inline void TriggerIssue()
{
Foo f;
}
// -----
// MyTest.cpp
#include "A1.h"
int main()
{
TriggerIssue();
return 0;
}
我再次编译代码,并使用与之前相同的命令行将结果通过管道传输到日志文件:
MyTest.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:MyTest.exe
MyTest.obj
MyTest.obj : error LNK2019: unresolved external symbol "__declspec(dllimport) void __cdecl SomeFunc(void)" (__imp_?SomeFunc@@YAXXZ) referenced in function "public: __thiscall Foo::~Foo(void)" (??1Foo@@QAE@XZ)
MyTest.exe : fatal error LNK1120: 1 unresolved externals
请注意,如果您愿意的话,两次尝试编译代码都会在相同的函数中针对相同的符号导致相同的链接器错误。这是因为问题实际上是由引起的~Foo()
,而不是TriggerIssue()
;TriggerIssue()
通过强制编译器为生成代码,仅公开它的第一个声明~Foo()
。
[请注意,以我的经验,Visual C ++会尝试尽可能安全地优化类inline
,如果实际未使用该类,则拒绝为其成员函数生成代码。这就是为什么阻止调用函数的原因:由于未调用TriggerIssue()
该inline
函数,因此编译器可以自由地对其进行完全优化,从而可以对其进行完全优化,包括对。的调用。SomeFunc()
TriggerIssue()
~Foo()
SomeFunc()
测试3:提供了外部符号。
使用相同的A2.h
,A1.h
以及MyTest.cpp
在测试2,我做了一个简单的DLL,出口标志,然后告诉编译器与它链接:
// SomeLib.cpp
void __declspec(dllexport) SomeFunc() {}
编译:
cl SomeLib.cpp /LD
这将创建SomeLib.dll
和SomeLib.lib
,以及编译器和链接器使用的一些其他文件。然后,您可以使用以下代码编译示例代码:
cl MyTest.cpp SomeLib.lib > MyTest.log
这将生成一个可执行文件和以下日志:
MyTest.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
Copyright (C) Microsoft Corporation. All rights reserved.
/out:MyTest.exe
MyTest.obj
SomeLib.lib
解决方案:
要解决此问题,您需要为编译器或链接器提供与SomeFunc()
从其中导入DLL对应的库;如果提供给编译器,它将直接传递给链接器。例如,如果SomeFunc()
包含在中SomeFuncLib.dll
,则可以使用以下代码进行编译:
cl MyTest.cpp SomeFuncLib.lib
为了说明两者之间的区别,我成功地两次编译了测试代码(每次都稍作修改),然后将其用于dumpbin /symbols
生成的目标文件。
dumpbin/symbols MyTest.obj > MyTest.txt
示例1:未inline
声明注释掉,TriggerIssue()
未调用。
该对象文件是通过注释掉TriggerIssue()
示例代码中的第一个声明而生成的,但未修改任何内容A2.h
或MyTest.cpp
以任何方式进行修改。TriggerIssue()
是inline
,未调用。
如果未调用该函数,并且允许使用编译器inline
,则仅生成以下内容:
COFF SYMBOL TABLE
000 00AB9D1B ABS notype Static | @comp.id
001 00000001 ABS notype Static | @feat.00
002 00000000 SECT1 notype Static | .drectve
Section length 2F, #relocs 0, #linenums 0, checksum 0
004 00000000 SECT2 notype Static | .debug$S
Section length 68, #relocs 0, #linenums 0, checksum 0
006 00000000 SECT3 notype Static | .text
Section length 7, #relocs 0, #linenums 0, checksum 96F779C9
008 00000000 SECT3 notype () External | _main
请注意,如果您愿意的话,生成的唯一功能符号是for main()
(隐式的,extern "C"
因此可以链接到CRT)。
示例2:上述测试3的结果。
成功编译上述测试3的结果是生成了此目标文件。TriggerIssue()
是inline
,并且被叫入main()
。
COFF SYMBOL TABLE
000 00AB9D1B ABS notype Static | @comp.id
001 00000001 ABS notype Static | @feat.00
002 00000000 SECT1 notype Static | .drectve
Section length 2F, #relocs 0, #linenums 0, checksum 0
004 00000000 SECT2 notype Static | .debug$S
Section length 68, #relocs 0, #linenums 0, checksum 0
006 00000000 SECT3 notype Static | .text
Section length C, #relocs 1, #linenums 0, checksum 226120D7
008 00000000 SECT3 notype () External | _main
009 00000000 SECT4 notype Static | .text
Section length 18, #relocs 2, #linenums 0, checksum 6CFCDEF, selection 2 (pick any)
00B 00000000 SECT4 notype () External | _TriggerIssue
00C 00000000 SECT5 notype Static | .text
Section length E, #relocs 0, #linenums 0, checksum 4DE4BFBE, selection 2 (pick any)
00E 00000000 SECT5 notype () External | ??0Foo@@QAE@XZ (public: __thiscall Foo::Foo(void))
00F 00000000 SECT6 notype Static | .text
Section length 11, #relocs 1, #linenums 0, checksum DE24CF19, selection 2 (pick any)
011 00000000 SECT6 notype () External | ??1Foo@@QAE@XZ (public: __thiscall Foo::~Foo(void))
012 00000000 UNDEF notype External | __imp_?SomeFunc@@YAXXZ (__declspec(dllimport) void __cdecl SomeFunc(void))
通过比较这两个符号表中,我们可以看到,当TriggerIssue()
被inline
剐,将产生以下四个符号,如果它被调用,或省略如果不是:
_TriggerIssue
(extern "C" void TriggerIssue()
)??0Foo@@QAE@XZ
(public: __thiscall Foo::Foo(void)
)??1Foo@@QAE@XZ
(public: __thiscall Foo::~Foo(void)
)__imp_?SomeFunc@@YAXXZ
(__declspec(dllimport) void __cdecl SomeFunc(void)
)如果SomeFunc()
未生成for的符号,则无论链接器是否已声明,都无需链接它。
因此,总结一下:
~Foo()
调用SomeFunc()
,当链接器没有任何SomeFunc()
的号召链接。TriggerIssue()
的Foo
,如果TriggerIssue()
不是inline
(通过第一个声明)或在时调用,则会显示此问题inline
。TriggerIssue()
。由于您希望内联函数并且实际上并未调用它,因此cl
可以自由地对其进行完全优化。优化TriggerIssue()
输出还使其能够优化Foo
的inline
成员函数,从而防止~Foo()
生成成员函数。反过来,这可以防止链接程序抱怨SomeFunc()
析构函数中的调用,因为SomeFunc()
从未生成过要调用的代码。甚至更短:
TriggerIssue()
间接阻止编译器优化对的调用SomeFunc()
。如果您注释掉该声明,则编译器可以自由地进行优化TriggerIssue()
和~Foo()
完全退出,从而使编译器无法生成对的调用SomeFunc()
,从而使链接程序可以完全忽略它。要修复此问题,您需要提供一个库,该库link
可用于生成SomeFunc()
从适当的DLL导入的适当代码。
编辑:正如user657267在注释中指出的TriggerIssue()
,暴露该问题的第一个声明的特定部分是extern "C"
。从问题的示例程序开始:
extern "C"
从两个声明中完全删除了,并且没有其他任何更改,则编译器将在编译代码时进行优化TriggerIssue()
(并扩展为~Foo()
),从而生成与上述示例1中的符号表相同的符号表。"C"
从两个声明中都删除了,而将函数保留为extern
,则其他任何事情都没有改变,则链接阶段将失败,并生成与测试1和2中相同的日志文件。这表明extern
声明cl
通过强制编译器生成可以在其他模块中进行外部链接的符号,专门负责防止优化问题代码。如果编译器不需要担心外部链接,它将完全优化TriggerIssue()
并扩展~Foo()
整个已完成的程序,从而消除了链接到另一个模块的需求SomeFunc()
。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句