C++ 为什么VisualStudio在extern时未能给出未定义的引用错误;";具体是什么?

C++ 为什么VisualStudio在extern时未能给出未定义的引用错误;";具体是什么?,c++,visual-c++,inline,extern-c,C++,Visual C++,Inline,Extern C,鉴于此代码: A2.H _declspec(dllimport) void SomeFunc(); struct Foo { Foo(); ~Foo(); }; inline Foo::Foo() { } inline Foo::~Foo() { SomeFunc(); } #include "A2.h" extern "C" void TriggerIssue(); // <-- This! extern "C" inline void TriggerIssue(

鉴于此代码:

A2.H

_declspec(dllimport) void SomeFunc();

struct Foo
{
  Foo();
  ~Foo();
};

inline Foo::Foo() { }

inline Foo::~Foo()
{
  SomeFunc();
}
#include "A2.h"

extern "C" void TriggerIssue(); // <-- This!

extern "C" inline void TriggerIssue()
{
  Foo f; 
}
A1.H

_declspec(dllimport) void SomeFunc();

struct Foo
{
  Foo();
  ~Foo();
};

inline Foo::Foo() { }

inline Foo::~Foo()
{
  SomeFunc();
}
#include "A2.h"

extern "C" void TriggerIssue(); // <-- This!

extern "C" inline void TriggerIssue()
{
  Foo f; 
}
有关此问题的背景信息,请参阅

当MyTest.cpp被编译成可执行文件时,链接器会抱怨
SomeFunc()
是未解析的外部文件

这似乎是由于一个无关的(错误的?)声明 A1.h中的触发问题。注释该输出会导致链接器错误消失

有人能告诉我这是怎么回事吗?我只想了解在有无声明的情况下,是什么具体导致编译器的行为不同。上面的代码片段是我试图为我遇到的场景编写一个可验证性最低的示例。请不要问我为什么它是这样写的

选民注意事项:这不是一个关于如何修复未解决的外部符号错误的问题。因此,请停止投票,以重复的方式关闭此文件。我没有足够的可信度来删除一直出现在这篇文章顶部的链接,该链接声称这个问题“可能有一个可能的答案”。

SomeFunc
在您的程序中,因此必须提供一个定义,但您没有提供一个(在这个翻译单元中或通过链接到另一个翻译单元中)并且您的程序有未定义的行为,不需要诊断™.


链接器向您提供错误的原因是编译器已为
TriggerIssue
生成定义;当然奇怪的是,根据额外声明的存在,行为会有所不同,您希望它们至少有相同的行为。抛开UB不谈,编译器仍然可以自由选择:函数是内联的,因此您可以保证函数的任何和所有定义都是相同的,因此如果在链接时有任何重复符号,链接器可以简单地将其丢弃。

无论第一次声明如何,问题都存在,如果您注释掉第一个声明并在程序中调用
TriggerIssue()
,则仍然存在

它是由
cl
TriggerIssue()
退出时调用
Foo
的析构函数时生成调用
SomeFunc()
的代码引起的,而不是由两个声明之间的任何怪癖或交互引起的。如果不注释掉非
内联
声明,它会出现的原因是,另一个声明告诉编译器,您希望它为函数生成一个符号,以便它可以导出到其他模块,这会阻止它实际内联代码,而不是强制它生成一个普通函数。生成函数体时,它以隐式调用
~Foo()
结束,这是问题的根源

但是,如果非内联声明被注释掉,编译器将愉快地将代码视为内联,并且仅当您实际调用它时才生成它;由于您的测试程序实际上没有调用
TriggerIssue()
,因此不会生成代码,也不会调用
~Foo()
;由于析构函数也是内联的,因此编译器可以完全忽略它,而不为它生成代码。但是,如果在测试程序中插入对
TriggerIssue()
的调用,您将看到完全相同的错误消息


测试#1:两个声明都存在。

我直接编译了您的代码,将输出传输到日志文件

cl MyTest.cpp>MyTest.log
生成的日志文件为:

MyTest.cpp
Microsoft(R)增量链接器版本10.00.40219.01
版权所有(C)微软公司。版权所有。
/输出:MyTest.exe
MyTest.obj
MyTest.obj:错误LNK2019:函数“public:u thiscall Foo::~Foo(void)”中引用的未解析外部符号“\uu declspec(dllimport)void”\uu cdecl SomeFunc(void)”(\uuu imp?usomefunc@@YAXXZ)(??1Foo)@@QAE@XZ)
MyTest.exe:致命错误LNK1120:1个未解析的外部

测试2:Non-
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;
}
请注意,如果您愿意,两次编译代码的尝试都会在同一函数中针对同一符号导致相同的链接器错误。这是因为问题实际上是由
~Foo()
引起的,而不是
TriggerIssue()
TriggerIssue()
的第一个声明只是通过强制编译器为
~Foo()
生成代码来公开它

注意,根据我的经验,VisualC++将尽可能安全地优化一个类,如果该类没有实际使用,则拒绝生成其<代码>内联成员函数的代码。这就是为什么制作<代码> TrutGrices()/Cuth> <代码>内联< /代码>函数防止<代码>的函数()不被调用:由于未调用

TriggerIssue()
,编译器可以完全优化它,这允许它完全优化
~Foo()
,包括调用
SomeFunc()
]


测试3:提供外部符号。

使用与测试2中相同的
A2.h
A1.h
MyTest.cpp
,我制作了一个简单的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)增量链接器版本10.00.40219.01
版权所有(C)微软公司。版权所有。
/出局:迈特斯