我有一个只有静态成员的类。
我想在退出时使用“atexit”库函数注册其成员函数之一(下面的代码中的VerifyClean)。
C++ FQA表示我必须为我希望以这种方式注册的函数指定extern“C”,如下例所示。
class Example
{
public:
static void Initialize();
static void DoDirtyStuff {++dirtLevel;}
static void CleanUpStuff {--dirtLevel;}
private:
static void VerifyClean();
// DOESN'T COMPILE: extern "C" static void VerifyClean();
static int dirtLevel;
}
int Example::dirtLevel;
extern "C" void Example::VerifyClean() // DO I NEED extern "C" HERE?
{
assert(dirtLevel == 0);
}
void Example::Initialize()
{
dirtLevel = 0;
atexit(&VerifyClean);
}
我真的必须使用extern“C”吗?
如果我将“atexit”替换为非库函数(在纯C中实现),答案是否会改变?
如果函数VerifyClean是公共的并且我决定直接从C ++代码调用它,我会得到链接错误或运行时崩溃吗?我问这个是因为声明根本没有提到extern“C”,所以常规的C ++代码可能会错误地处理函数调用。这在我的MS Visual Studio 2005系统上运行正常。
答案 0 :(得分:7)
可能,编译器对C和C ++代码使用不同的调用约定;然而,在实践中,这几乎从未发生过。
如果您只是想让它工作而不关心支持模糊编译器,请不要理会extern "C"
。在任何广泛使用的编译器中都没有必要。
如果你想要绝对迂腐,或者需要支持一个迂腐的编译器,写一个包装器:
extern "C" static void ExampleVerifyClean()
{
Example::VerifyClean();
}
void Example::Initialize()
{
dirtLevel = 0;
atexit(&ExampleVerifyClean);
}
答案 1 :(得分:1)
链接错误。
C ++执行所谓的名称修改,它生成带有类型信息的链接时函数名称。
extern C将 off 转换为更简单的标识符。
编辑:
如果所有内容都是由C ++编译器编译的,那么它就不是问题。但是如果你有一个由C编译器编译的目标文件和一个由C ++编译器编译的目标文件,那么你将遇到一些问题。
我似乎回想起需要外部“C”规范的DLL,但此时内存可能已有10年了
好。
我用一个带签名的函数掀起了一个测试用例
int foo(float,float)
并在3种不同的gcc调用下编译它 -
gcc test_c.c -S
g++ test.cpp -S
这两个调用在程序集中生成不同的标识符。 C ++在其通常的类型修改方法中破坏了名称。 (当然编译器可能会这样做)
然后,我将foo
包裹在Extern“C”中并再次调用G ++ ......
g++ test.cpp -S
然后删除了受损的C ++名称,留下了一个普通的C unmangled名称。
虽然这里涉及其他细微之处,例如,参数的顺序被推到了堆栈上,但我根据数据来讨论这一点。
答案 2 :(得分:0)
如果没有extern“C”,您的函数名称将被编译器破坏,因此函数名称最终可能与您期望的不同。您需要使用其错位名称调用该函数(例如在Windows中使用GetProcAddress),否则您将遇到链接器错误。不同的编译器以不同的方式对其进行了修改,因此最好继续使用extern关键字。
答案 3 :(得分:0)
你可以用这个:
class yourname
{
public:
...
static void _cdecl AtExitCall ();
};
int main()
{
ataexit( yourname::AtExitCall );
}
答案 4 :(得分:-1)
在这种情况下使用extern“ c”是一个错误,原因有两个: