x86-64 上的 C++:何时在寄存器中传递和返回结构/类?

2024-01-09

假设 Linux 上的 x86-64 ABI,在 C++ 中的什么条件下,结构会传递给寄存器中的函数,还是传递给堆栈上的函数?在什么条件下它们会返回到寄存器中?答案会随着课程的变化而变化吗?

如果有助于简化答案,您可以假设单个参数/返回值并且没有浮点值。


ABI规范定义here http://refspecs.linuxbase.org/elf/x86_64-abi-0.21.pdf.
有新版本可用here https://software.intel.com/sites/default/files/article/402129/mpx-linux64-abi.pdf.

我假设读者习惯了文档的术语并且他们可以对原始类型进行分类。


如果对象大小大于两个八字节,则将其传递到内存中:

struct foo
{
    unsigned long long a;
    unsigned long long b;
    unsigned long long c;               //Commenting this gives mov rax, rdi
};

unsigned long long foo(struct foo f)
{ 
  return f.a;                           //mov     rax, QWORD PTR [rsp+8]
} 

如果是非POD,则在内存中传递:

struct foo
{
    unsigned long long a;
    foo(const struct foo& rhs){}            //Commenting this gives mov rax, rdi
};

unsigned long long foo(struct foo f)
{
  return f.a;                               //mov     rax, QWORD PTR [rdi]
}

Copy elision http://en.cppreference.com/w/cpp/language/copy_elision is at work here

如果它包含未对齐的字段,则它会在内存中传递:

struct __attribute__((packed)) foo         //Removing packed gives mov rax, rsi
{
    char b;
    unsigned long long a;
};

unsigned long long foo(struct foo f)
{
  return f.a;                             //mov     rax, QWORD PTR [rsp+9]
}

如果以上都不成立,则考虑对象的字段。
如果其中一个字段本身就是一个结构/类,则递归应用该过程。
目标是对对象中的两个八字节 (8B) 中的每一个进行分类。

考虑每个8B的字段的类别。
请注意,由于上述对齐要求,整数个字段始终占据 1 个 8B。

Set C是 8B 级并且D是考虑类别中字段的类别。
Let new_class被伪定义为

cls new_class(cls D, cls C)
{
   if (D == NO_CLASS)
      return C;

   if (D == MEMORY || C == MEMORY)
      return MEMORY;

   if (D == INTEGER || C == INTEGER)
      return INTEGER;

   if (D == X87 || C == X87 || D == X87UP || C == X87UP)
      return MEMORY;

   return SSE;
}

那么 8B 的类别计算如下

C = NO_CLASS;

for (field f : fields)
{
    D = get_field_class(f);        //Note this may recursively call this proc
    C = new_class(D, C);
}

一旦我们有了每个 8B 的类别,比如 C1 和 C2,那么

if (C1 == MEMORY || C2 == MEMORY)
    C1 = C2 = MEMORY;

if (C2 == SSEUP AND C1 != SSE)
   C2 = SSE;

Note这是我对ABI文档中给出的算法的解释。


Example

struct foo
{
    unsigned long long a;
    long double b;
};

unsigned long long foo(struct foo f)
{
  return f.a;
}

8B 及其领域

First 8B: a 第二个8B: b

a是 INTEGER,所以前 8B 是 INTEGER。b是X87和X87UP,所以第二个8B是MEMORY。 两个 8B 的最后一个类别都是 MEMORY。


Example

struct foo
{
    double a;
    long long b;
};

long long foo(struct foo f)
{
  return f.b;                     //mov rax, rdi
}

8B 及其领域

First 8B: a 第二个8B: b

a是SSE,所以第一个8B是SSE。
b是 INTEGER,所以第二个 8B 是 INTEGER。

最终的班级是计算出来的。


返回值

这些值将相应地返回到它们的类:

  • MEMORY
    调用者将一个隐藏的第一个参数传递给函数,以便将结果存储到其中。
    在 C++ 中,这通常涉及复制省略/返回值优化。 该地址必须返回到eax,从而返回MEMORY类“通过引用”到隐藏的调用者分配的缓冲区。

    如果类型具有 MEMORY 类,则调用者为返回提供空间 值并在 %rdi 中传递此存储的地址,就好像它是第一个一样 函数的参数。实际上,该地址首先成为“隐藏”地址 争论。 返回时 %rax 将包含由 %rax 传入的地址 %rdi 中的调用者。

  • INTEGER and POINTER
    寄存器rax and rdx如所须。

  • SSE and SSEUP寄存器xmm0 and xmm1如所须。

  • X87 AND X87UP登记册st0


PODs

技术定义是here http://en.cppreference.com/w/cpp/concept/PODType.

ABI 的定义如下。

如果 de/构造函数是隐式声明的默认 de/构造函数并且如果:

• 它的类没有虚函数和虚基类,并且
• 其类的所有直接基类都有简单的 de/构造函数,并且
• 对于其类中属于类类型(或其数组)的所有非静态数据成员,每个此类都有一个简单的析构函数/构造函数。


请注意,每个 8B 都是独立分类的,因此每个 8B 都可以相应通过。
特别是,如果没有剩余的参数寄存器,它们可能最终会出现在堆栈上。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

x86-64 上的 C++:何时在寄存器中传递和返回结构/类? 的相关文章

  • 在动态事件处理程序中引用“this”

    在我的 myClass 类中 我使用 Reflection Emit 为 myClass 类成员之一动态编写事件处理程序 我已经成功地做到了这一点 现在 我想修改事件处理程序以调用 myClass 类中的实例方法之一 但是 我无法弄清楚如何
  • 在Application_AquireRequestState事件中用POST数据重写Url

    我有一个在其中注册路线的代码Application AcquireRequestState应用程序的事件 注册路由后 我会在 Http 运行时缓存中设置一个标志 这样我就不会再次执行路由注册代码 在此事件中注册路线有特定原因Applicat
  • C# 中四舍五入到偶数

    我没有看到 Math Round 的预期结果 return Math Round 99 96535789 2 MidpointRounding ToEven returning 99 97 据我了解 MidpointRounding ToE
  • 如何在编译C代码时禁用警告?

    我正在使用 32 位 Fedora 14 系统 我正在使用编译我的源代码gcc 有谁知道如何在编译c代码时禁用警告 EDIT 是的 我知道 最好的办法是修复这些警告以避免任何未定义 未知的行为 但目前在这里 我第一次编写了巨大的代码 并且在
  • 有没有办法使用 i387 fsqrt 指令获得正确的舍入?

    有没有办法使用 i387 fsqrt 指令获得正确的舍入 除了改变精确模式在 x87 控制字中 我知道这是可能的 但这不是一个合理的解决方案 因为它存在令人讨厌的重入型问题 如果 sqrt 操作中断 精度模式将出错 我正在处理的问题如下 x
  • 存储过程上的 OdbcCommand - 输出参数上出现“未提供参数”错误

    我正在尝试执行存储过程 通过 ODBC 驱动程序针对 SQL Server 2005 但收到以下错误 过程或函数 GetNodeID 需要参数 ID 但未提供该参数 ID 是我的过程的 OUTPUT 参数 在存储过程中指定了一个输入 mac
  • 我可以仅在少数情况下关闭模拟吗

    我有一个始终使用模拟的应用程序 但是 当用户以管理员身份登录时 一些操作需要他们写入服务器本身 现在 如果这些用户在实际服务器上没有权限 有些用户没有 则不会让他们写入 我想做的是关闭几个命令的模拟 有没有办法做这样的事情 using Ho
  • “rep stos”x86 汇编指令序列有什么作用?

    我最近偶然发现了以下汇编指令序列 rep stos dword ptr edi For ecx重复 存储内容eax到哪里edi指向 递增或递减edi 取决于方向标志 每次 4 个字节 通常 这用于memset型操作 通常 该指令简单地写成r
  • C#中Enum中定义的value__是什么

    What value 可能在这里 value MSN ICQ YahooChat GoogleTalk 我运行的代码很简单 namespace EnumReflection enum Messengers MSN ICQ YahooChat
  • 在简单注入器中注册具有多个构造函数和字符串依赖项的类型

    我正在尝试弄清楚如何使用 Simple Injector 我在项目中使用了它 注册简单服务及其组件没有任何问题 但是 当组件具有两个以上实现接口的构造函数时 我想使用依赖注入器 public DAL IDAL private Logger
  • 如何用 C 语言练习 Unix 编程?

    经过五年的专业 Java 以及较小程度上的 Python 编程并慢慢感觉到我的计算机科学教育逐渐消失 我决定要拓宽我的视野 对世界的一般用处 并做一些 对我来说 感觉更重要的事情就像我真的对机器有影响一样 我选择学习 C 和 Unix 编程
  • 错误左值需要作为赋值C++的左操作数

    整个程序基本上只允许用户移动光标 如果用户位于给定的坐标范围 2 2 内 则允许用户键入输入 我刚刚提供了一些我认为足以解决问题的代码 我不知道是什么导致了这个问题 你能解释一下为什么会发生吗 void goToXY int int 创建一
  • 将非算术类型作为参数传递给 cmath 函数是否有效?

    给定以下用户定义类型S具有转换功能double struct S operator double return 1 0 以及以下调用cmath http en cppreference com w cpp header cmath使用类型的
  • 设计 Javascript 前端 <-> C++ 后端通信

    在我最近的将来 我将不得不制作一个具有 C 后端和 Web 前端的系统 要求 目前 我对此了解不多 我认为前端将触发数据传输 而不是后端 所以不需要类似 Comet 的东西 由于在该领域的经验可能很少 我非常感谢您对我所做的设计决策的评论
  • 从 C 线程调用 Python 代码

    我对从 C 或 C 线程调用 Python 代码时如何确保线程安全感到非常困惑 The Python 文档 http docs python org c api init html non python created threads似乎是
  • TPL 数据流块下游如何获取源生成的数据?

    我正在使用 TPL Dataflow 处理图像 我收到处理请求 从流中读取图像 应用多次转换 然后将生成的图像写入另一个流 Request gt Stream gt Image gt Image gt Stream 为此 我使用块 Buff
  • #pragma pack(16) 和 #pragma pack(8) 的效果总是相同吗?

    我正在尝试使用来对齐数据成员 pragma pack n http msdn microsoft com en us library 2e70t5y1 28v vs 100 29 aspx 以下面为例 include
  • 如何获取 QIcon 的文件/资源​​路径

    假设我做了这样的事情 QIcon myIcon resources icon ico 我稍后如何确定该图标的路径 例如 QString path myIcon getPath 问题是 没有getPath 会员 我找不到类似的东西 但肯定有办
  • 启动画面后主窗口出现在其他窗口后面

    我有一个带有启动屏幕的 Windows 窗体应用程序 当我运行该应用程序时 启动屏幕显示正常 消失并加载应用程序的主窗体 但是 当我加载主窗体时 它出现在包含该应用程序的 Windows 资源管理器目录下 这是运行启动画面然后运行主窗体的代
  • c# 模拟 IFormFile CopyToAsync() 方法

    我正在对一个异步函数进行单元测试 该函数将 IFormFile 列表转换为我自己的任意数据库文件类列表 将文件数据转换为字节数组的方法是 internal async Task

随机推荐