为什么在外部"C"出现时,Visual Studio无法给出未定义的引用错误?被指定? [英] Why does Visual Studio fail to give an undefined reference error when extern "C" is specified?

查看:130
本文介绍了为什么在外部"C"出现时,Visual Studio无法给出未定义的引用错误?被指定?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出以下代码:

A2.H

_declspec(dllimport) void SomeFunc();

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

inline Foo::Foo() { }

inline Foo::~Foo()
{
  SomeFunc();
}

A1.H

#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(),则该问题仍然存在. >

这是由于clTriggerIssue()退出时调用Foo的析构函数时生成的代码调用SomeFunc()引起的,而不是由两个声明之间的任何怪异或相互作用引起的.如果不注释掉非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声明,在main()中调用了TriggerIssue().

我对您的代码进行了几处更改:

// 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:提供了外部符号.

使用与测试2 中相同的A2.hA1.hMyTest.cpp,我制作了一个简单的DLL,用于导出符号,然后告诉编译器进行链接:/p>

// SomeLib.cpp
void __declspec(dllexport) SomeFunc() {}

编译为:

 cl SomeLib.cpp /LD
 

这将创建SomeLib.dllSomeLib.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 
 


解决方案:

要解决此问题,您需要为编译器或链接器提供与从中导入DLL SomeFunc()相对应的库;如果提供给编译器,它将直接传递给链接器.例如,如果SomeFuncLib.dll中包含SomeFunc(),则应使用以下命令进行编译:

 cl MyTest.cpp SomeFuncLib.lib
 


为说明不同之处,我成功地两次编译了测试代码(每次都进行了少量修改),并在生成的目标文件上使用了dumpbin /symbols.

 dumpbin/symbols MyTest.obj > MyTest.txt
 

示例1:非inline声明被注释掉,TriggerIssue()未调用.

此目标文件是通过注释掉示例代码中的TriggerIssue()的第一个声明而生成的,但未以任何方式修改A2.hMyTest.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
 

请注意,如果生成的话,唯一的功能符号是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 d时,如果调用了以下四个符号,则将生成以下四个符号;否则,将省略以下四个符号:

  • _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()的符号,则无论链接器是否已声明,都无需链接它.



总结一下:

  • 当链接器没有任何SomeFunc()链接到呼叫时,~Foo()调用SomeFunc()引起的问题.
  • 通过TriggerIssue()创建Foo的实例来暴露问题,并且如果TriggerIssue()被设置为非inline(通过第一个声明)或被调用,则会显示该问题当inline.
  • 如果您注释掉TriggerIssue()的第一个声明而没有实际调用它,则问题是隐藏的.由于您希望内联函数并且实际上并未调用它,因此cl可以自由地对其进行完全优化.优化TriggerIssue()输出还可以使其优化Fooinline成员函数,从而防止生成~Foo().这样就可以防止链接程序抱怨析构函数中的SomeFunc()调用,因为从未生成过调用SomeFunc()的代码.

或更短:

  • TriggerIssue()的第一个声明间接阻止编译器优化对SomeFunc()的调用.如果您注释掉该声明,则编译器可以自由地完全优化TriggerIssue()~Foo(),这反过来会阻止编译器生成对SomeFunc()的调用,从而允许链接程序完全忽略它.

要修复此问题,您需要提供一个库,link可以使用该库来生成适当的代码以从适当的DLL导入SomeFunc().



编辑:作为.从问题的示例程序开始:

  • 如果extern "C"完全从两个声明中删除,并且没有其他任何更改,则编译器将在编译代码时优化TriggerIssue()(并扩展为~Foo()),从而生成相同的符号表到上面的示例1 中的一个.
  • 如果从两个声明中都删除了"C",但该函数保留为extern,并且未进行任何其他更改,则链接阶段将失败,并生成与 Tests 1& 1中相同的日志文件. 2 .

这表明extern声明专门负责通过强制编译器生成可以在其他模块中进行外部链接的符号来防止cl优化问题代码.如果编译器不需要担心外部链接,它将完全从已完成的程序中优化TriggerIssue()并通过扩展名~Foo()进行优化,从而消除了链接至另一个模块的SomeFunc()的需要.

Given this code:

A2.H

_declspec(dllimport) void SomeFunc();

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

inline Foo::Foo() { }

inline Foo::~Foo()
{
  SomeFunc();
}

A1.H

#include "A2.h"

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

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

MyTest.cpp

#include "A1.h"

int main()
{
  return 0;
}

Please see here for a background to the issue.

When MyTest.cpp is compiled into an executable, the linker complains that SomeFunc() is an unresolved external.

This seems to be caused because of an extraneous (erroneous?) declaration of TriggerIssue in A1.h. Commenting that out causes the linker error to go away.

Can someone tell me what's going on here? I just want to understand what specifically causes the compiler to behave differently in the presence and absence of that declaration. The snippet above is my attempt to write a minimally verifiable example of a scenario I am running into. Please don't ask me why its written the way it is.

Note to downvoters: This is NOT a question about how to fix unresolved external symbol errors. So please STOP voting to close this as duplicate. I don't have enough cred to remove that link that keeps showing up at the top of this post claiming this question "may have a possible answer".

解决方案

The issue is present regardless of the first declaration, and will still be present if you comment out the first declaration and call TriggerIssue() in your program.

It's caused by cl generating the code to call SomeFunc() when it calls Foo's destructor upon TriggerIssue()'s exit, not by any quirk or interaction between the two declarations. The reason it shows up if you don't comment out the non-inline declaration is that the other declaration tells the compiler that you want it to generate a symbol for the function so it can be exported to other modules, which prevents it from actually inlining the code, instead forcing it to generate a normal function. When the function's body is generated, it ends with an implicit call to ~Foo(), which is the source of the issue.

If the non-inline declaration is commented out, however, the compiler will merrily treat the code as inline, and only generate it if you actually call it; since your test program doesn't actually call TriggerIssue(), the code is never generated, and ~Foo() is never called; since the destructor is also inline, this allows the compiler to ignore it entirely and not generate code for it. If you do insert a call to TriggerIssue() in your test program, however, you'll see the exact same error message.


Test #1: Both declarations present.

I compiled your code directly, piping the output to a log file.

cl MyTest.cpp > MyTest.log

The resulting log file was:

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


Test 2: Non-inline declaration commented out, TriggerIssue() called in main().

I made a couple changes to your code:

// 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;
}

I again compiled the code and piped the results to a log file, using the same command line as before:

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

Note, if you will, that both attempts to compile the code resulted in the same linker error, for the same symbol, in the same function. This is because the problem is actually caused by ~Foo(), not TriggerIssue(); the first declaration of TriggerIssue() merely exposed it, by forcing the compiler to generate code for ~Foo().

[Note that in my experience, Visual C++ will attempt to optimise a class out as much as is safely possible, and refuse to generate code for its inline member functions, if the class isn't actually used. This is why making TriggerIssue() an inline function prevented SomeFunc() from being called: Since TriggerIssue() wasn't called, the compiler was free to optimise it out entirely, which allowed it to optimise ~Foo() out entirely, including the call to SomeFunc().]


Test 3: External symbol provided.

Using the same A2.h, A1.h, and MyTest.cpp as in Test 2, I made a simple DLL that exports the symbol, then told the compiler to link with it:

// SomeLib.cpp
void __declspec(dllexport) SomeFunc() {}

Compile with:

cl SomeLib.cpp /LD

This creates SomeLib.dll and SomeLib.lib, along with some other files the compiler & linker use. You can then compile your example code with:

cl MyTest.cpp SomeLib.lib > MyTest.log

This results in an executable, and the following 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 


The solution:

To resolve this issue, you need to provide either the compiler or the linker with the library corresponding to the DLL SomeFunc() is imported from; if given to the compiler, it'll be passed directly to the linker. If SomeFunc() is contained in SomeFuncLib.dll, for example, you would compile with:

cl MyTest.cpp SomeFuncLib.lib


To illustrate the difference, I compiled the test code successfully twice (with slight modifications each time), and used dumpbin /symbols on the resulting object files.

dumpbin/symbols MyTest.obj > MyTest.txt

Example 1: Non-inline declaration commented out, TriggerIssue() not called.

This object file was generated by commenting out the first declaration of TriggerIssue() in your example code, but not modifying either A2.h or MyTest.cpp in any way. TriggerIssue() is inline, and not called.

If the function isn't called, and the compiler is allowed to inline it, then only the following will be generated:

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

Note, if you will, that the only function symbol generated was for main() (which is implicitly extern "C" so it can link to the CRT).

Example 2: Result from Test 3 above.

This object file was generated as a result of successfully compiling Test 3 above. TriggerIssue() is inline, and called in 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))

By comparing these two symbol tables, we can see that when TriggerIssue() is inlined, the following four symbols will by generated if it is called, or omitted if it isn't:

  • _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))

If the symbol for SomeFunc() isn't generated, the linker doesn't need to link it, regardless of whether it was declared or not.



So, to summarise:

  • The problem is caused by ~Foo() calling SomeFunc(), when the linker doesn't have any SomeFunc() to link the call to.
  • The problem is exposed by TriggerIssue() creating an instance of Foo, and will show up either if TriggerIssue() is made non-inline (by the first declaration) or called when inline.
  • The problem is hidden if you comment out TriggerIssue()'s first declaraction and don't actually call it. Since you want the function to be inlined, and it isn't actually called, cl is free to optimise it out entirely. Optimising TriggerIssue() out also lets it optimise Foo's inline member functions out, which prevents ~Foo() from being generated. This, in turn, prevents the linker from complaining about the SomeFunc() call in the destructor, since the code to call SomeFunc() was never generated.

Or even shorter:

  • The first declaration of TriggerIssue() indirectly prevents the compiler from optimising out the call to SomeFunc(). If you comment out that declaration, the compiler is free to optimise TriggerIssue() and ~Foo() out entirely, which in turn stops the compiler from generating a call to SomeFunc(), allowing the linker to ignore it entirely.

To fix it, you need to provide a library that link can use to generate the proper code to import SomeFunc() from the appropriate DLL.



Edit: As user657267 pointed out in the comments, the specific part of TriggerIssue()'s first declaration that exposes the issue is the extern "C". Starting with the question's example program:

  • If the extern "C" is removed entirely from both declarations, and nothing else is changed, then the compiler will optimise TriggerIssue() (and by extension, ~Foo()) out as it compiles the code, generating a symbol table identical to the one in Example 1 above.
  • If the "C" is removed from both declarations but the function is left as extern, and nothing else is changed, then the linking stage will fail, producing the same log file as in Tests 1 & 2.

This suggests that the extern declaration is specifically responsible for preventing cl from optimising the problem code out, by forcing the compiler to generate a symbol that can be externally linked in other modules. If the compiler doesn't need to worry about external linkage, it will optimise TriggerIssue(), and by extension ~Foo(), out of the finished program entirely, thus removing the need to link to another module's SomeFunc().

这篇关于为什么在外部"C"出现时,Visual Studio无法给出未定义的引用错误?被指定?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆