当前位置: 首页 > news >正文

【QA】C和C++有哪些常用的调用约定

#cpp

1. __stdcall

  • 参数传递顺序:参数从右到左依次压入栈中,这一点和 __cdecl 一样。例如对于函数 int func(int a, int b, int c),调用时参数入栈顺序为 cba
  • 栈清理方式:由被调用函数负责清理栈。在函数返回之前,被调用函数会将之前压入栈的参数从栈中移除。这意味着被调用函数需要知道参数的数量和大小,所以它不适合可变参数函数。
  • 名称修饰:在 Visual C++ 中,函数名会被修饰,通常在函数名前加下划线,后面跟着 @ 符号以及参数的总字节数。例如 int func(int a, int b)(假设 int 为 4 字节),修饰后的名称可能是 _func@8
  • 应用场景:常用于 Windows API 函数,这样可以减少代码重复,因为每个函数自身清理栈,无需调用者处理。

示例代码

#include <iostream>

// 使用 __stdcall 调用约定的函数
int __stdcall add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(1, 2);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

2. __fastcall

  • 参数传递顺序:部分参数通过寄存器传递,其余参数从右到左压入栈中。一般来说,前几个较小的参数(如 int 类型)会使用寄存器(如 ECX、EDX)传递,以提高参数传递的效率。
  • 栈清理方式:由被调用函数负责清理栈中剩余的参数。
  • 名称修饰:不同编译器的名称修饰规则不同。在 Visual C++ 中,函数名前会加 @ 符号,后面跟着参数的总字节数。例如 int func(int a, int b) 修饰后的名称可能是 @func@8
  • 应用场景:适用于对性能要求较高的函数,因为通过寄存器传递参数可以减少栈操作,提高函数调用的速度。

示例代码

#include <iostream>

// 使用 __fastcall 调用约定的函数
int __fastcall subtract(int a, int b) {
    return a - b;
}

int main() {
    int result = subtract(5, 3);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

3. __thiscall

  • 参数传递顺序__thiscall 主要用于 C++ 类的成员函数。this 指针通过寄存器(通常是 ECX)传递,其他参数从右到左压入栈中。
  • 栈清理方式:如果是普通成员函数,由被调用函数负责清理栈;如果是可变参数的成员函数,由调用者负责清理栈。
  • 名称修饰:和 __cdecl 类似,不同编译器有不同的名称修饰规则。
  • 应用场景:C++ 类的成员函数默认使用 __thiscall 调用约定。

示例代码

#include <iostream>

class MyClass {
public:
    // 成员函数默认使用 __thiscall 调用约定
    int add(int a, int b) {
        return a + b;
    }
};

int main() {
    MyClass obj;
    int result = obj.add(1, 2);
    std::cout << "Result: " << result << std::endl;
    return 0;
}

4. __vectorcall(仅适用于 Visual C++)

  • 参数传递顺序:使用寄存器和栈混合传递参数,部分参数通过寄存器传递,其余参数从右到左压入栈中。它会优先使用寄存器来传递向量类型的参数,以提高性能。
  • 栈清理方式:由被调用函数负责清理栈。
  • 名称修饰:函数名会被修饰,遵循特定的规则。
  • 应用场景:适用于处理向量类型数据且对性能有较高要求的函数。

调用者负责清理栈时之所以可以使用可变参数,主要是因为调用者清楚具体传递了多少个参数,从而能够正确地清理栈空间,下面从可变参数函数的工作原理、栈清理的要求以及调用者和被调用者的信息差异等方面详细解释。

为什么调用者清理就可以使用可变参?

可变参数函数的工作原理

可变参数函数允许在调用时传递数量和类型不确定的参数。在 C 和 C++ 中,常见的可变参数函数有 printf 系列函数,其使用 ... 表示可变参数部分。例如:

#include <stdio.h>

// 可变参数函数示例
int sum(int count, ...) {
    va_list args;
    va_start(args, count);
    int result = 0;
    for (int i = 0; i < count; ++i) {
        result += va_arg(args, int);
    }
    va_end(args);
    return result;
}

在上述代码中,sum 函数接收一个固定参数 count 表示可变参数的数量,然后使用 va_listva_startva_arg 和 va_end 等宏来处理可变参数。

栈清理的要求

在函数调用过程中,参数会被压入栈中。当函数执行完毕后,需要将这些参数从栈中移除,以恢复栈的原始状态,保证后续的函数调用能够正常进行。为了正确清理栈,必须知道压入栈的参数数量和每个参数的大小。

调用者和被调用者的信息差异

  • 被调用者不清楚参数数量:对于可变参数函数,被调用者(函数本身)在编译时无法确定调用时具体传递了多少个参数。因为可变参数的数量和类型是在调用时动态确定的,所以被调用者无法准确知道需要清理多少栈空间。例如,在 sum 函数中,它只知道有一个固定参数 count,但不知道具体的可变参数数量,因此无法自行清理栈。
  • 调用者知道参数数量:调用者在调用可变参数函数时,明确知道自己传递了多少个参数。例如,在调用 sum 函数时:
int main() {
    int result = sum(3, 1, 2, 3);
    return 0;
}

main 函数作为调用者,清楚地知道传递了 3 个可变参数,加上固定参数 count,总共压入了 4 个参数到栈中。因此,调用者可以根据这些信息正确地清理栈。

总结

由于调用者在调用可变参数函数时知道具体传递的参数数量和大小,所以由调用者负责清理栈可以确保栈空间被正确恢复。而被调用者由于无法在编译时确定可变参数的具体情况,不适合负责栈清理工作。这就是为什么调用者清理栈的调用约定(如 __cdecl)可以支持可变参数函数的原因。

相关文章:

  • 记录一次,rabbitmq开启stomp插件之后,还是连不上15674端口的问题
  • Baklib企业CMS元数据与协作管理优化
  • Java Spring 中循环依赖的解决之道
  • npm error gyp info
  • AI里的RAG到底是什么?
  • 春天遇到了冬天的吻
  • 《解锁元宇宙构建:AI与云原生区块链的协同奥秘》
  • Web爬虫利器FireCrawl:全方位助力AI训练与高效数据抓取。本地部署方式
  • openEuler24.03 LTS下安装Hive3
  • 十四、OSG学习笔记-事件响应
  • WEB攻防-PHP反序列化-字符串逃逸
  • 如何测试交换机数据回流
  • C#中修饰符——abstract、virtual
  • 天梯赛 PTAL2-009 抢红包
  • Hugging Face模型国内镜像HF Mirror下载
  • Python Pyecharts面试题及参考答案
  • OpenHarmony 开源鸿蒙北向开发——linux使用make交叉编译第三方库
  • 计算机四级 - 数据库原理(操作系统部分)- 第3章「进程线程模型」
  • 数据结构-------栈
  • AJAX的理解和原理还有概念
  • 空调+零食助顶级赛马备战,上海环球马术冠军赛将焕新登场
  • 湖北鄂州通报4所小学学生呕吐腹泻:供餐企业负责人被采取强制措施
  • 医学统计专家童新元逝世,终年61岁
  • 光明网评“泉州梦嘉商贸楼不到5年便成危楼”:监管是否尽职尽责?
  • 78家公募年度业绩比拼:23家营收净利双升,十强座次微调
  • “人工智能是年轻的事业,也是年轻人的事业”,沪上高校师生畅谈感想