如何使用atexit()注册非空函数?

14

我想注册一个返回 int 的函数,使用 atexit() 函数在程序结束时调用它(具体来说是 ncurses 库的 endwin() 函数)。

但由于 atexit() 需要一个指向 void 函数的指针,所以我遇到了问题。我尝试了以下方法:

static_cast<void (*)()>(endwin)

但是从一个 int 函数向一个 void 函数进行 static_cast 似乎是不被允许的。

我想实现的目标是否可能,如果是,如何实现?

注意: 我愿意忽略函数的返回值。


编辑: 我还尝试创建了一个lambda函数,它似乎做到了我想要的:

atexit([]{ endwin(); });

与使用包装器/转发函数相比,这是一种好的解决方案吗?(除了需要C++11并避免定义一个唯一目的是转发另一个函数的新函数之外。)


4
你试过使用reinterpret_cast吗? - Basile Starynkevitch
4
请注意,根据5.2.10 [expr.reinterpret.cast]第6段的规定,通过错误类型的指针调用函数会导致未定义的行为:“通过指向函数类型(8.3.5)的指针调用函数,如果该类型与函数定义中使用的类型不同,则其效果是未定义的。” - Dietmar Kühl
是的,它是未定义的,但我猜在大多数Linux x86-64实现中这并不重要。当然,某些ABI可能会指定不同的调用约定(取决于函数签名),在这种情况下,混乱会发生。 - Basile Starynkevitch
@BasileStarynkevitch 这对我来说似乎有效,但正如Dietmar Kühl所提到的,它似乎不能保证有效。 - emlai
4
如果endwin返回一个复杂数据类型,例如std::stringstruct X,则它会将数据返回到由某个隐藏参数给定的空间。如果返回的数据类型是“简单”的(例如intchar或某些指针),则返回值将传递到寄存器中,这通常是无害的。但Dietmar的解决方案是“每次都有效的解决方案”,[并且编译器可能会对其进行优化,以便在“返回值在寄存器中因此可以被忽略”的情况下几乎没有任何开销] - Mats Petersson
Lambda函数与包装/转发函数完全相同,只不过它是一个匿名的包装/转发函数。 - Raymond Chen
3个回答

23

函数指针不能被转换。只需注册一个转发函数:

#ifdef __cplusplus
extern "C"
#endif
void endwin_wrapper() { endwin(); }
...
atexit(endwin_wrapper);

由于你标记了你的问题为C++,如果你在C++中定义转发函数,你需要将其声明为extern "C"以获得正确的语言链接。


1
你真的需要 extern "C" 吗?名称修饰并不重要,C 和 C++ 的函数指针类型完全兼容。除非这里有一些超级追求法律上的问题...? - Nicholas Wilson
2
@NicholasWilson:请参考:https://dev59.com/PnE85IYBdhLWcg3w03Hz - Dietrich Epp
11
在C++中,atexit()函数有两种形式,其中之一是使用C++链接的函数指针:extern "C++" int atexit(void (*f)(void)) noexcept;。(另一个是使用C链接的函数指针:extern "C" int atexit(void (*f)(void)) noexcept;)请注意,翻译后的内容与原文意思相同,但更易懂。 - T.C.
请注意,如果您检查任何实现的标准库,这些实现不区分 C 和 C++ 链接函数类型,与标准要求相反,您将找不到 atexit 的两个重载版本(以及其他几个函数),因为这些实现将无法将它们检测为重载。 - user743382

12

使用当前的C++标准,lambda表达式可能是解决这个问题的最佳方案:


atexit([]{ endwin(); });

现在不需要定义一个完全新的命名函数,只是为了转发另一个函数。


如果有一天您决定需要在程序退出时调用更多函数,则可以使用新的atexit()调用来定义它们:

atexit(anotherCleanupFunction);

或者,您可以将它们添加到Lambda主体中:

atexit([]{
  anotherCleanupFunction();
  endwin();
});

然而,当您有多个函数需要在 atexit() 中注册时,使用一个单独的包装函数来保存所有这些函数可能是更好的解决方案。虽然没有一个正确的答案,但是只要编写良好且清晰的代码即可。(使用充满函数调用的lambda调用atexit()可能会看起来相当混乱。)


你可以简单地将C++11称为“当前标准”。否则,正如我所提到的,我喜欢这个解决方案。 - πάντα ῥεῖ
确实,那可能会更好。 - emlai
1
C++11不是当前的标准,C++14才是。在C++14中,lambda仍然可能是最好的解决方案,因此你的答案看起来仍然很好。 - user743382
“最好”是指什么?我认为包装器解决方案是最好的,因为它非常清晰地展示了作者正在做什么,特别是如果添加了像“需要将int返回值转换为void”这样的注释。有些人编写C和C++代码是为了完成工作,而不是看看他们能在代码中包含多少模糊的特性。 - jamesqf
@zenith:从上面的“lambda”链接中,再进行一些快速的谷歌搜索,似乎 lambda 函数只在 C++ 中有效(或许作为 gcc 的扩展?)。如果是这样,在 C 代码中它就没有太多用处了,因为 OP 把问题标记为 C。当然,我可能错了,因为在读到这篇文章之前我从未听说过 lambda 函数。 - jamesqf
显示剩余2条评论

6

“我想知道我所尝试的是否可能,如果是,怎么做?”

您只需编写自己的endwin()包装函数并注册即可。

void myEndwin() {
    endwin();
}

我在我的问题中添加了一个关于lambda解决方案的编辑。(只是让您知道,如果您也想在答案中解决这个问题。) - emlai
@zenith 你也可以发布自己的答案并提到它。这是一个好的解决方案。 - πάντα ῥεῖ

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接