“cdecl”代表什么意思?

41

是的,我知道"Cdecl"是一个著名的调用约定名称,所以请不要向我解释调用约定。我想问的是缩写"Cdecl"实际上代表什么。我认为这是一个糟糕的命名选择,因为乍一看它让人想起"C declarator"(C语言中相当独特的语法方面)。实际上,有一个名为cdecl的程序,其唯一目的是解密C declarators。但据我所知,C declarator语法与调用约定完全无关。

简化版: "stdcall"代表"标准调用约定"。那么"cdecl"代表什么?


2
我一直认为它是“C声明”的 - 大多数C编译器默认使用这种约定,因此如果您想调用已编译的C代码,那么您将指定它作为调用约定。 - Anon.
5
这是2011年的事。普通应用程序员(而不是系统程序员)仍在询问有关Windows荒谬实现细节的问题,这显然说明了Windows开发资源和Windows作为平台的质量问题... - R.. GitHub STOP HELPING ICE
1
@R..:是的,这基本上否定了Java让Windows变得不重要的点状预测。 - MSalters
3
@R.. 你所说的“荒谬的实现细节”是指什么?我只是想问这个名称来自哪里。你真的有看过我的问题吗? - fredoverflow
6
Windows开发资源非常出色。我怀疑你在这个特定的话题上是出于某种无知的立场而胡言乱语。 - David Heffernan
显示剩余3条评论
8个回答

34

它来源于被声明的 C 函数(与在 K&R C 中常见的未声明的 C 函数相对)。

那时它与 pascal 调用约定共存(其中被调用者清空堆栈),因此在编程语言之后称它有点说得通。

关于调用约定,这里有你想知道的一切。


3
我确定链接的PDF很有趣,但它并没有回答我的问题。 - fredoverflow
4
@FredOverflow的回答有没有解决你的问题呢?"它来自于一个已声明的C函数"。例如,int main() { char c = 0; f(c); }传递了一个int,而在二进制代码中,尽管参数类型为char,但void f(c) char c; { }需要将参数数据读取为int。如果你在作用域内有void f(char c)void f(char c) { },那么数据将被传递并读取为char - Johannes Schaub - litb
5
如果未声明的C函数不使用cdecl调用约定,那么@JohannesSchaub-litb的回答才有意义,因此你需要与之对比一些东西 - 但是这是错误的。 - Fred Nurk
1
@JohannesSchaub-litb:我只是按照你之前的评论来说。"答案难道不回答了你的问题吗?"=> 不是的。参数类型之间的差异(你的例子使用int和char)对于所有调用约定都很重要。 - Fred Nurk
嗯,为什么要使用-1呢?有什么可以分享来改进答案吗? - Peer Stritzinger
显示剩余5条评论

17

你对此过于深究了。它代表了调用C函数的实现惯例,这在使用可变参数时尤为重要。

它不一定是"C"和"declaration"的组合缩写; 名称只是名称,尤其是在编程中。助记符有帮助,但是就像"malloc"意味着"分配内存"一样,它有附加的含义,而我们也知道并将其赋予了它的额外含义; "alloca" 也是"分配内存"的意思,例如。

或者以"struct"为例,它的“意思”是“结构”,但是“结构”本身太过普通,如果没有我们下意识地赋予“struct”的附加含义,我们将会彻底迷失——就像新手程序员还在学习术语时常常迷失。


但这确实意味着一些事情...请看我的回答。 - Peer Stritzinger
5
@PeerStritzinger:我并没有说它毫无意义,但是,嗯,谢谢你的踩因为你误解了我的意思。 翻译:我没说那没意义,但是感谢你误解后点踩。 - Fred Nurk
2
这个答案是对一个词源学问题的回答,提供了一个定义。是的,单词除了它们的词源之外还有其他含义,但它们仍然有它们自己的词源。 - user2357112

13

C声明。由C引入的声明。

[编辑]

说实话,我不确定这是否就是它的含义,尽管它确实是由C引入的。但由于调用者必须清理已分配的内存(与大多数其他调用约定不同),它也可能是“Caller Does End CLeaning”的缩写,我认为这实际上是一个很好的记忆辅助方法。:D


3
那篇明显的虚构作品让他们赚了38美元!如果能这么轻松地赚钱当然很不错,但你是那个必须要设法入眠的人!!;-) - David Heffernan
5
我喜欢那个“主叫方负责结束清理”的故事情节,但不幸的是,“主叫方”和“被叫方”都以字母“C”开头,所以它并不能很好地作为记忆辅助方式。 :-( - fredoverflow
1
我想知道你想奖励哪个答案。毕竟,唯一将其表述清楚的答案(即cdecl == C声明)是你自己的... - Support Ukraine
我希望能得到一个更具有决定性的答案,因为这被宣称为无意义 :0 - GolezTrol
好的,这段文字的意思是“奖励现有答案”,不需要新的答案。 - Support Ukraine
显示剩余7条评论

4

cdecl(C声明)调用约定通常是x86 C编译器的默认调用约定。

在计算机科学中,调用约定是子程序如何从其调用者接收参数以及如何返回结果的实现级别(低级别)方案。各种实现的差异包括放置参数、返回值、返回地址和作用域链接的位置(寄存器、堆栈或内存等),以及准备函数调用和恢复环境的任务如何在调用者和被调用者之间分配。

(来源)

调用约定可能与特定编程语言的求值策略有关,但通常不被认为是其一部分(或反之),因为求值策略通常在更高的抽象级别上定义,并被视为语言的一部分,而不是特定语言编译器的低级实现细节。

(来源)

cdecl(意为C类型声明)是一种调用约定,起源于Microsoft的C编程语言编译器,并被许多x86体系结构的C编译器所使用。在中,子例程参数通过堆栈传递。整数值和内存地址在EAX寄存器中返回,浮点值在ST0 x87寄存器中返回。寄存器EAXECXEDX由呼叫者保存,其余由被呼叫者保存。x87浮点寄存器ST0ST7在调用新函数时必须为空(出栈或释放),而在退出函数时,ST1ST7必须为空。ST0在不用于返回值时也必须为空。
在C编程语言的上下文中,函数参数按从右到左的顺序推送到堆栈中,即最后一个参数首先推送。
考虑以下C源代码片段:
int callee(int, int, int);

int caller(void)
{
    return callee(1, 2, 3) + 5;
}


在 x86 上,它可能会产生以下汇编代码(Intel 语法):
caller:
    ; make new call frame
    ; (some compilers may produce an 'enter' instruction instead)
    push    ebp       ; save old call frame
    mov     ebp, esp  ; initialize new call frame
    ; push call arguments, in reverse
    ; (some compilers may subtract the required space from the stack pointer,
    ; then write each argument directly, see below.
    ; The 'enter' instruction can also do something similar)
    ; sub esp, 12      : 'enter' instruction could do this for us
    ; mov [ebp-4], 3   : or mov [esp+8], 3
    ; mov [ebp-8], 2   : or mov [esp+4], 2
    ; mov [ebp-12], 1  : or mov [esp], 1
    push    3
    push    2
    push    1
    call    callee    ; call subroutine 'callee'
    add     esp, 12   ; remove call arguments from frame
    add     eax, 5    ; modify subroutine result
                      ; (eax is the return value of our callee,
                      ; so we don't have to move it into a local variable)
    ; restore old call frame
    ; (some compilers may produce a 'leave' instruction instead)
    mov     esp, ebp  ; most calling conventions dictate ebp be callee-saved,
                      ; i.e. it's preserved after calling the callee.
                      ; it therefore still points to the start of our stack frame.
                      ; we do need to make sure
                      ; callee doesn't modify (or restores) ebp, though,
                      ; so we need to make sure
                      ; it uses a calling convention which does this
    pop     ebp       ; restore old call frame
    ret               ; return

调用者在函数调用返回后清理堆栈。
对于x86 C编译器来说,通常情况下cdecl调用约定是默认的调用约定,尽管许多编译器提供选项以自动更改所使用的调用约定。要手动定义一个函数为cdecl,一些编译器支持以下语法:
return_type __cdecl func_name();

调用约定是调用约定的名称。对于支持这些约定的编译器,可以在C ++函数声明中明确指定__cdecl,__stdcall,__pascal和__fastcall。__cdecl是应用程序和静态库的默认值。__stdcall是系统调用(包括Windows API调用)的默认值,并推荐在32位Windows中用于库DLL。Microsoft编译器默认使用__thiscall来处理16位和32位模式下的成员函数。Microsoft、Borland、Watcom和Gnu是编译器品牌。Windows的Intel编译器与Microsoft兼容。Linux的Intel编译器与Gnu兼容。Symantec、Digital Mars和Codeplay编译器与Microsoft兼容。在64位模式下,每个操作系统都有一个默认的调用约定,而其他调用约定在64位模式下很少使用。

其他约定:

  • __pascal: 指示函数使用Pascal调用约定
  • __fortran: 指示函数使用Fortran调用约定
  • __thiscall: 指示函数使用thiscall调用约定
  • __stdcall: 指示函数使用stdcall调用约定
  • __fastcall: 指示函数使用fastcall调用约定
  • __msfastcall: 指示函数使用Microsoft fastcall调用约定
  • __regcall: 指示函数使用register调用约定
  • __vectorcall: 指示函数使用向量调用约定

(来源)


4
“CDECL”一词起源于微软的BASIC和他们的混合语言编程生态系统。该生态系统允许微软的四种主要语言(BASIC、FORTRAN、Pascal和C)中的任何一种调用其他语言。每种语言都有稍微不同的调用约定,并且每种语言都有一种声明外部函数或过程使用特定约定的方式。
在BASIC中,必须使用“DECLARE”语句才能在“CALL”语句中调用外部函数。为了表示外部FORTRAN或Pascal过程或函数,您可以编写以下之一:
DECLARE SUB Foo ()
DECLARE FUNCTION Foo ()

C语言的调用约定与其他语言不同,部分原因在于参数是按相反顺序推送到堆栈上的。您可以通过添加CDECL修饰符来告知BASIC这一点:
DECLARE SUB Foo CDECL ()
DECLARE FUNCTION Foo CDECL ()

相比之下,当使用FORTRAN或Pascal编写代码时,修饰符是[C]。这表明CDECL是特定于BASIC的DECLARE语句而不是先前已建立的术语。在当时,没有专门的术语来表示“C调用约定”。只有随着WIN32中新的调用约定的出现(stdcall,fastcall等),"cdecl"被占用并成为在没有其他术语的情况下指代遗留约定的事实上的名称。
总之,CDECL代表“C声明”。它起源于BASIC编译器,而不是C编译器,并且是一个任意选择的BASIC关键字,有些多余,因为纯粹的“C”不能成为关键字。
有关CDECL的详细信息可以在此1987年的文档中找到:

https://archive.org/details/Microsoft_Macro_Assembler_5.1_Mixed_Language_Programming_Guide/page/n1/mode/2up?q=cdecl


1
C语言有函数和变量的概念,而汇编/机器码则没有。当我们想要将值传递给函数时,需要使用CPU寄存器或者在寄存器(通常是指定的堆栈指针)的固定偏移处的内存中传递值。因此,当我们跳转到函数开始的新地址时,正确的值就在正确的位置,以便函数正确执行。返回值同理。
具体如何实现由系统调用约定所描述。调用约定对于CPU架构和操作系统都是独特的。
在x86 Windows上,有许多使用的调用约定。在“cdecl”调用约定中,调用者将函数放在堆栈上供被调用者使用。一旦函数完成,调用者会清除自己的堆栈。
Windows API使用“stdcall”调用约定,类似于cdecl,但是被调用者清除堆栈。(这意味着vararg函数调用无法使用)。它的好处是节省代码空间。
Windows还有一个“fastcall”调用约定,利用寄存器进行参数传递。
由于Windows允许多种调用约定,C编译器(cl)扩展了C语言,包括__cdecl、__stdcall和__fastcall。这修饰了函数声明,并允许程序员指定所使用的调用约定。
其他平台如ARM、Itanium等也有自己的调用约定。

1

CDECL没有缩写,它只是调用约定的名称。

如果这不是你要找的,而实际上是关于CDECL的历史:

这是一种微软特定的调用约定属性(在介绍时期介于1985年至1995年之间),后来成为标准。

太糟糕了,页面从互联网上消失了(wayback也没有),但是谁有旧的MSDN CD可能会在“C语言参考”内找到主题“声明摘要”,其中清楚地说明如下:

attribute-seq:/* attribute-seq是Microsoft特定的*/

attribute attribute-seq opt attribute:其中之一/* Microsoft Specific */

__asm __fastcall __based __inline __cdecl __stdcall

同样在同一份旧的MSDN文档中__cdecl

__cdecl 主页 | 概述 | 如何操作
Microsoft 特定 —>
这是 C 和 C++ 程序的默认调用约定。由于堆栈由调用者清理,因此它可以执行 vararg 函数。__cdecl 调用约定创建的可执行文件比 __stdcall 更大,因为它需要每个函数调用都包括堆栈清理代码。以下列表显示了此调用约定的实现。

0

更新 在看到评论指出我的错误后,我已经完全修订了这篇文章。 cdecl 表示该函数使用与 C 函数相同的调用约定。此外,extern "C" 还表示该函数名不应该经过 C++ 的名称重整。

至于为什么它被称为 cdecl,我不知道了。


1
"extern "C""并不等同于cdecl。你也是在胡说八道!" - David Heffernan
C++函数也是_cdecl(事实上,除非它是非静态成员函数,在大多数编译器中这是默认的,否则就是_thiscall)。 - Billy ONeal

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