编译器如何为 C++ 中条件声明的自动变量分配内存?

2024-04-03

假设我有一个函数,根据某些运行时条件创建昂贵的自动对象或创建便宜的自动对象:

void foo() {
   if (runtimeCondition) {
       int x = 0;
   } else {
       SuperLargeObject y;
   }
}

当编译器为此函数分配堆栈帧的内存时,它是否会分配足够的内存来存储SuperLargeObject,并且如果条件导致int额外的内存真的不会被使用吗?或者它会以其他方式分配内存吗?


这取决于您的编译器和优化设置。在未优化的构建中,大多数 C++ 编译器可能会为两个对象分配堆栈内存,并根据采用的分支使用其中之一。在优化的构建中,事情变得更加有趣:

如果两个对象(intSuperLargeObject没有被使用,编译器可以证明构造SuperLargeObject没有副作用,两个分配都将被省略。

如果对象逃逸该函数,即它们的地址被传递给另一个函数,则编译器必须为它们提供内存。但由于它们的生命周期不重叠,因此它们可以存储在重叠的内存区域中。这是否真的发生取决于编译器。

正如你在这里看到的 https://godbolt.org/z/4Efn19s69,不同的编译器为这两个函数生成不同的程序集:(来自OP和参考的修改示例,全部针对x86-64编译)

void escape(void const*);

struct SuperLargeObject {
    char data[104];
};

void f(bool cond) {
    if (cond) {
        int x;
        escape(&x);
    }
    else {
        SuperLargeObject y;
        escape(&y);
    }
}

void g() {
    SuperLargeObject y;
    escape(&y);
}

请注意,所有堆栈分配都是 8 的奇数倍,因为 x86-64 ABI 要求堆栈指针按 16 字节对齐,并且 8 字节由call返回地址的说明(感谢@PeterCordes 在 上向我解释这一点另一个帖子 https://stackoverflow.com/questions/76291937/size-of-stack-allocations-produced-by-llvms-x86-64-backend?noredirect=1#comment134535786_76291937).

ICC

f(bool):
        sub       rsp, 120
        test      dil, dil
        lea       rax, QWORD PTR [104+rsp]
        lea       rdx, QWORD PTR [rsp]
        cmovne    rdx, rax
        mov       rdi, rdx
        call      escape(void const*)
        add       rsp, 120
        ret
g():
        sub       rsp, 104
        lea       rdi, QWORD PTR [rsp]
        call      escape(void const*)
        add       rsp, 104
        ret

ICC 似乎分配了足够的内存来存储两个对象,然后根据运行时条件在两个非重叠区域之间进行选择(使用cmov) 并将选定的指针传递给转义函数。

在参考函数中g它只分配 104 字节,正好是SuperBigObject.

GCC

f(bool):
        sub     rsp, 120
        mov     rdi, rsp
        call    escape(void const*)
        add     rsp, 120
        ret
g():
        sub     rsp, 120
        mov     rdi, rsp
        call    escape(void const*)
        add     rsp, 120
        ret

GCC 也分配 120 字节,但它将两个对象放置在同一地址,因此不会发出任何信号cmov操作说明。

Clang

f(bool):
        sub     rsp, 104
        test    edi, edi
        mov     rdi, rsp
        call    escape(void const*)@PLT
        add     rsp, 104
        ret
g():
        sub     rsp, 104
        mov     rdi, rsp
        call    escape(void const*)@PLT
        add     rsp, 104
        ret

Clang 还合并了两个分配,并将分配大小减少到必要的 104 字节。

不幸的是我不明白为什么它测试功能中的条件f.


您还应该注意,当编译器可以将一个或两个变量放入寄存器中时,根本不会分配内存,即使它们在整个函数中使用和重新分配也是如此。为了int's and long和其他小对象是最常见的情况,如果它们的地址不转义该函数。

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

编译器如何为 C++ 中条件声明的自动变量分配内存? 的相关文章

  • C++ 中的软(不是:弱)引用 - 这可能吗?有实施吗?

    在 C 中我正在使用boost shared ptr and boost weak ptr自动删除不再需要的对象 我知道这些与引用计数一起工作 在 Java 中 内存由垃圾收集器管理 它将内置对象引用视为strong WeakReferen
  • 添加对共享类的多个 WCF 服务的服务引用

    我正在尝试将我的 WCF Web 服务拆分为几个服务 而不是一个巨大的服务 但是 Visual Studio Silverlight 客户端 复制了两个服务共享的公共类 这是一个简单的例子来说明我的问题 在此示例中 有两个服务 两者都返回类
  • 如何进行带有偏差的浮点舍入(始终向上或向下舍入)?

    我想以偏置舍入浮动 要么总是向下 要么总是向上 代码中有一个特定的点 我需要这个 程序的其余部分应该像往常一样四舍五入到最接近的值 例如 我想四舍五入到最接近的 1 10 倍数 最接近 7 10 的浮点数约为 0 69999998807 但
  • 为什么基类必须有一个带有 0 个参数的构造函数?

    这不会编译 namespace Constructor0Args class Base public Base int x class Derived Base class Program static void Main string a
  • 如果.Net Core可以在Windows上运行,为什么不能在.Net Framework中引用.Net Core DLL?

    我明白为什么 Net Framework 可能会在 Net Core IE 中导致问题 因为不存在特定于 Windows 平台的 API 但是为什么不能直接引用 Net Core 作为 Net Framework 中的库呢 如果 Net C
  • 使用实体框架从集合中删除项目

    我正在使用DDD 我有一个 Product 类 它是一个聚合根 public class Product IAggregateRoot public virtual ICollection
  • 如何用 kevent() 替换 select() 以获得更高的性能?

    来自Kqueue 维基百科页面 http en wikipedia org wiki Kqueue Kqueue 在内核和用户空间之间提供高效的输入和输出事件管道 因此 可以修改事件过滤器以及接收待处理事件 同时每次主事件循环迭代仅使用对
  • 在 C# 中将位从 ulong 复制到 long

    所以看来 NET 性能计数器类型 http msdn microsoft com en us library system diagnostics performancecounter aspx有一个恼人的问题 它暴露了long对于计数器
  • 转到 C# WPF 中的第一页

    我正在 WPF 中使用导航服务 为了导航到页面 我使用 this NavigationService Navigate new MyPage 为了返回我使用 this NavigationService GoBack 但是如何在不使用的情况
  • Xamarin Android:获取内存中的所有进程

    有没有办法读取所有进程 而不仅仅是正在运行的进程 如果我对 Android 的理解正确的话 一次只有一个进程在运行 其他所有进程都被冻结 后台进程被忽略 您可以使用以下代码片段获取当前正在运行的所有 Android 应用程序进程 Activ
  • 事件日志写入错误

    很简单 我想向事件日志写入一些内容 protected override void OnStop TODO Add code here to perform any tear down necessary to stop your serv
  • 通过等待任务或访问其 Exception 属性都没有观察到任务的异常

    这些是我的任务 我应该如何修改它们以防止出现此错误 我检查了其他类似的线程 但我正在使用等待并继续 那么这个错误是怎么发生的呢 通过等待任务或访问其 Exception 属性都没有观察到任务的异常 结果 未观察到的异常被终结器线程重新抛出
  • 从匿名类型获取值

    我有一个方法如下 public void MyMethod object obj implement 我这样称呼它 MyMethod new myparam waoww 那么我该如何实施MyMethod 获取 myparam 值 Edit
  • Silverlight Datagrid:在对列进行排序时突出显示整个列

    我的 Silverlight 应用程序中有一个 DataGrid 我想在对该列进行排序时突出显示整个列 它在概念上与上一个问题类似 Silverlight DataGrid 突出显示整列 https stackoverflow com qu
  • gdb查找行号的内存地址

    假设我已将 gdb 附加到一个进程 并且在其内存布局中有一个文件和行号 我想要其内存地址 如何获取文件x中第n行的内存地址 这是在 Linux x86 上 gdb info line test c 56 Line 56 of test c
  • Fluent NHibernate 日期时间 UTC

    我想创建一个流畅的 nhibernate 映射来通过以下方式映射 DateTime 字段 保存时 保存 UTC 值 读取时 调整为本地时区值 实现此映射的最佳方法是什么 就我个人而言 我会将日期存储在 UTC 格式的对象中 然后在读 写时在
  • 同时从多个流中捕获、最佳方法以及如何减少 CPU 使用率

    我目前正在编写一个应用程序 该应用程序将捕获大量 RTSP 流 在我的例子中为 12 个 并将其显示在 QT 小部件上 当我超过大约 6 7 个流时 问题就会出现 CPU 使用率激增并且出现明显的卡顿 我认为它不是 QT 绘制函数的原因是因
  • 如何查明CONFIG_FANOTIFY_ACCESS_PERMISSIONS是否启用?

    我想利用fanotify 7 http man7 org linux man pages man7 fanotify 7 html我遇到的问题是在某些内核上CONFIG FANOTIFY ACCESS PERMISSIONS不起作用 虽然C
  • 热重载时调用方法

    我正在使用 Visual Studio 2022 和 C 制作游戏 我想知道当您热重新加载应用程序 当它正在运行时 时是否可以触发一些代码 我基本上有 2 个名为 UnloadLevel 和 LoadLevel 的方法 我想在热重载时执行它
  • boost::program_options:带有固定和可变标记的参数?

    是否可以在 boost program options 中使用此类参数 program p1 123 p2 234 p3 345 p12 678 即 是否可以使用第一个标记指定参数名称 例如 p 后跟一个数字 是动态的吗 我想避免这种情况

随机推荐