为什么线程函数需要声明为 '__cdecl'?

6

展示如何使用MFC创建线程的示例代码将线程函数声明为静态和__cdecl。为什么需要后者?Boost线程不关心这个约定,所以这只是一种过时的做法吗?

例如(MFC):

static __cdecl UINT MyFunc(LPVOID pParam)
{
...
}

CWinThread* pThread = AfxBeginThread(MyFunc, ...);

相比之下,Boost:

static void func()
{
...
}

boost::thread t;
t.create(&func);

(由于我离集成开发环境很远,因此代码示例可能不是100%正确)。

__cdecl的意义是什么?在创建线程时它如何帮助?

5个回答

4

__cdecl告诉编译器使用C调用约定(而不是stdcall、fastcall或其他调用约定)。我相信,VC++默认使用stdcall。

调用约定影响参数如何被推入堆栈(或在fastcall的情况下,被推入寄存器)以及谁弹出堆栈中的参数(调用者或被调用者)。

对于Boost而言,我相信它使用模板特化来确定适当的函数类型和调用约定。


Boost不考虑调用约定。这不是语言级别的特性(更多是链接器级别的特性)。微软使用它来保持代码的向后兼容性。 - Martin York
洛基提供了最好的答案。 - SChalice

4

看一下 AfxBeginThread() 的原型:

CWinThread* AfxBeginThread(
   AFX_THREADPROC pfnThreadProc,
   LPVOID pParam,
   int nPriority = THREAD_PRIORITY_NORMAL,
   UINT nStackSize = 0,
   DWORD dwCreateFlags = 0,
   LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL 
);

AFX_THREADPROCUINT(AFX_CDECL*)(LPVOID) 的 typedef。当你将一个函数传递给 AfxBeginThread() 时,它必须匹配这个原型,包括调用约定。

在 MSDN 页面上有关于 __cdecl__stdcall(以及 __fastcall__thiscall)的说明,解释了每个调用约定的优缺点。

boost::thread 构造函数使用模板允许你传递一个函数指针或可调用函数对象,因此它没有与 MFC 相同的限制。


1

因为你的线程将由运行时函数调用,该函数会为你管理它,并且该函数希望它是这样的。Boost以不同的方式设计了它。

在你的线程函数开头设置断点并查看调用时的堆栈,你会看到调用你的运行时函数。


1

C/C++编译器默认使用C调用约定(将最右边的参数先推入堆栈),因为它允许处理具有可变参数数量的函数,如printf。

Pascal调用约定(又称“fastcall”)将最左边的参数先推入。这样做更快,但会使您失去易于使用可变参数函数的可能性(我在某个地方读到过它们仍然是可能的,但需要使用一些技巧)。

由于使用Pascal约定所带来的速度优势,Win32和MacOS API默认使用该调用约定,除非在某些情况下。

如果该函数只有一个参数,则理论上使用任何调用约定都是合法的,但编译器可能会强制使用相同的调用约定以避免任何问题。

Boost库的设计考虑了可移植性,因此它们应该对特定编译器使用的调用约定保持不可知。


umm fastcall将前两个参数放入ecx和edx中,然后按照其他调用约定的顺序将其余参数推入堆栈。stdcall和cdecl不同之处在于stdcall清理自己的堆栈,而cdecl调用者必须清理堆栈。还有thiscall,其中ecx == this。 - Raindog

1

真正的答案与Windows内部如何调用线程过程例程有关,它期望函数遵守特定的调用约定,在这种情况下是一个宏,WINAPI,根据我的系统定义为:

#define WINAPI      __stdcall

这意味着被调用的函数负责清理堆栈。boost::thread 能够支持任意函数的原因是它将指向在调用 thread::create 函数时使用的函数对象的指针传递给 CreateThread。与线程相关联的线程过程仅在函数对象上调用 operator()。

MFC 需要 __cdecl 的原因与其内部调用传递给 AfxBeginThread 的函数有关。除非他们计划允许 vararg 参数,否则没有好的理由这样做...


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