未解析的外部符号静态变量(头文件中定义的方法使用的变量)
Posted
技术标签:
【中文标题】未解析的外部符号静态变量(头文件中定义的方法使用的变量)【英文标题】:Unresolved external symbol static variable (variable used by method defined in header) 【发布时间】:2012-09-01 16:20:24 【问题描述】:这是.h:
class Logger
private:
static int mTresholdSeverity;
public:
static __declspec(dllexport) void log(const char* message);
static __declspec(dllexport) void logFormat(const char* format, ...);
static __declspec(dllexport) int getTresholdSeverity() return mTresholdSeverity;
static __declspec(dllexport) void setTresholdSeverity(int tresholdSeverity) mTresholdSeverity = tresholdSeverity;
;
还有.cpp:
#include "Logger.h"
#include <cstdarg>
int Logger::mTresholdSeverity = 200;
void Logger::log(const char* message)
//...
void Logger::logFormat(const char* format, ...)
//...
我收到此错误: 错误 LNK2001:未解析的外部符号“私有:静态 int TransformationViewer_Utility_Logging::Logger::mTresholdSeverity”(?mTresholdSeverity@Logger@TransformationViewer_Utility_Logging@@0HA) ...
显然,mTresholdSeverity 已初始化。如果我注释掉 getTresholdSeverity() 和 setTresholdSeverity() 或者如果我将它们的定义移到 .cpp 文件中,则会删除该错误。
为什么头文件中定义的静态方法(getTresholdSeverity() 或 setTresholdSeverity())使用静态变量(mTresholdSeverity)时会出现链接错误?
【问题讨论】:
您是否忘记在链接中包含上述cpp编译产生的目标文件? @JohnB 不,我可以使用 log() 方法,例如,如果我通过执行倒数第二段中提到的事情之一来编译它。将定义移动到 .cpp 对我来说是可以接受的解决方案,我只是想知道为什么当定义在标题中时它不起作用。 【参考方案1】:这是它的工作原理。
每个 DLL(或 EXE)或其他完整的“完全链接”二进制文件都必须定义所有引用名称,包括静态变量,包括 C++ 类中的静态数据成员。
它们将在应用程序中的 DLL 中分开。这意味着,这个变量值会有所不同,具体取决于您查看的 DLL。将函数移动到 CPP 文件将使它们做不同的事情:它们现在将看到 DLL 的变量副本,而不是 EXE 的副本。
要使您编写的内容编译,必须在所有用户二进制文件中的一个位置存在来自 CPP 的定义。这意味着,DLL 和 DLL 的用户(EXE 或 DLL)必须拥有一个具有此定义的 CPP 文件。
这是一个非常大的麻烦,因为它使单例模式成为不可能(为程序中的所有用户提供共享数据对象)并且每个 DLL 副本都必须有自己的静态状态。此类问题仅存在于 Windows 上,DLL 是动态 *加载的 * 库。在 UNIX 系统上,有一种称为共享对象 (SO) 的不同技术。它们支持真正的动态链接,这意味着运行链接器以在运行时解析外部名称。
【讨论】:
我是一个 C# 人,所以我不完全理解这些东西(或你所说的一切),但让我直截了当:如果我在标题中有这些方法定义,它们不会不知道在哪里可以找到他们使用的静态变量的初始化/定义;如果方法定义在 .cpp 文件中,变量会在同一个文件的顶部初始化,所以没有问题? 这是错误的。静态数据成员具有外部链接,并且在有效程序中恰好定义在一个位置。您不会在每个使用模块的模块中获得定义;仅在定义它的那个中。所以整个程序只有一个,不管你有多少个DLL。 [Pete 评论的真实部分] 静态数据成员具有外部链接,并且在有效程序中的一个位置上定义。您不会在每个使用模块的模块中获得定义; [/皮特评论的真实部分]。 其余的都是假的。在下面回答您的问题。 既然@PavelRadzivilovsky 现在已经认识到他的信息已经过时了二十年,你可以忽略他在这里所说的一切。【参考方案2】:问题是 mThresholdSeverity
不是从 DLL 导出的,但是两个访问器是内联定义的,所以无论在哪里调用它们,它们都必须能够看到 mThresholdSeverity
。有两种解决方案:要么从 DLL 中导出 mThresholdSeverity
(抱歉,我不记得该怎么做),或者使访问器成为非内联函数,在 DLL 中定义它们,然后导出它们来自 DLL。
【讨论】:
无法在进程中跨 Windows DLL 边界导出或共享静态变量。 (虽然对于 unix 共享对象是可能的) 在我看来,__declspec(dllexport)
做到了;我刚刚查看了一些 Dinkumware 库代码(我在多年前做过 Windows 的东西),这是那里使用的机制之一。这可能在最近发生了变化。
好吧,问题是它做了什么。无法共享变量的原因是,在构建时,或者更准确地说是链接时,当外部名称被解析为地址时,目标进程地址空间中的变量地址是未知的。 UNIX 系统会在运行时链接 SO 来解决这个问题。
嗯,答案是它有效。编译器必须生成通过重定位表间接访问变量的代码,并且加载器在加载时修复地址。正如我之前所暗示的,这是在整个 Microsoft 的 C++ 运行时库中完成的。
@PeteBecker 我在变量上尝试了 __declspec(dllexport),但如果 get/setTresholdSeverity() 定义保留在标题中,错误仍然存在。【参考方案3】:
这样就不会迷失在噪音中:您可以将访问器更改为普通的非内联函数,并将它们定义在与静态数据成员相同的源文件中。只要你导出它们,你就可以从任何地方调用它们,并且它们会很好地访问静态数据。
【讨论】:
以上是关于未解析的外部符号静态变量(头文件中定义的方法使用的变量)的主要内容,如果未能解决你的问题,请参考以下文章