未优化的 clang++ 代码在简单的 main() 中生成不需要的“movl $0, -4(%rbp)”

2024-04-03

我创建了一个最小的 C++ 程序:

int main() {
    return 1234;
}

并使用 clang++5.0 禁用优化(默认-O0). 得到的汇编代码是 https://gcc.godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSAbAQwDtRkBSAJgCFufSAZ1QBXYskwgA5HhYEA1AFsmsiAEp5HAOx8ADAEF5R%2BcUwExLebS4BmACwcbe/doAiHAx/2yFAM1So6po6XsYmZhZWtg5OXm5SaozSAKxSpCzSummo0gDC/PzywmISmra0aQSZCYkA1iA2WgB0dgBsNgCcWq0ddslcdnYdrUlSdmmKdLq66dWkOVJpgiAzVVJZiXCwMFA7EEhoigAOeAyYZBQQhydnxCjMbMnTpL6nBOfLEAD6P7qUiQAjOYA2RMYgAT2kFVIh0UmDkAHkWAxIes0lhlGwznN8KZkAQ8AA3TDLNGkTAAD0wyBE7yhaR8mAYcyoAEcROdUTYmlwmtCzmwCEh6EdTISEQCqNSqt9fpRBHgAF6Ks60J5tLQADhstE6Ni4lBELCwrxYmHQlDOwHhFsgyVIMykFTULsSAuAQropFFmHFkullB%2BXz%2B1AAihyIfraLp9U8uLQ7JquEm2vZdK1aJR8IIjsxUQkhEqVZg1boNdrdR19YbjZhTebLZhrcb4PbHc7XYx4R7hS7SARiHhJk6Cw92AVeIw8ADlpBEqgjgTUCxSQBaQQEdCOVzAFgiCc8Wi0eTrzcMafb88Azi8QqrhG6E%2B%2BYnEAGoQSYVdMQSKJaicSSLQbopGkGRkgsFKaq0q6tHY8jIGO8jJE0ugofIEC4IQJBlDqpDyLkqDHKc5w4UB%2BEHpU1R9ggmBMFgdzqKQ9RPKM4ykJMWiobYXC6JqmpaB0HS0B0XBaHYszgdISwrP2VGkFsiAgKIBBHLS5CUNcxF3PQ5pYdpwFSKkElZPM0jcDq8gAO6EAg8iQdBsHwYhyGoSZawbIkNF0ec/ysRMIBcB0TTdAJR48bxuhaFwrRcMZ2RSUIMnuTUTEgImPJ2JFmViVlWWjDYoFzAslFopsex7AchE3Oc6lXFVWkgMAWg2C8bwfIGcqMUCZIgiwYKotCsLwgQSIojimCYsA2Jkri0pEiScyUtStKSCO5ByEyLLspy0jcry/Ldp6IpihKUr4h1wbykWqrqq0Wo6nqBoQEaJqyA2EBWja8BHg6pAjq6bqHcK3onf652ypdYYRuCUYxlwcYJkmKbtJlGZZngOZ5lJiQKsqN1lndFaPTWr1mhaH1Nl9kA/e2NR9u6R19gOQ70m6rDjrek5XrOjELkuK7SKeW42Due4HkeJ4AOpMAwDAng%2Bf4lIBBlGWBJkQVBMFwcAyDIPIzQ2OhmFEMQpF4QRRG3DhXAaPknM8CVHmkF59G%2BcxMzMmM/liShrStMkgmdH0TzDHFpmLIlqxyQp%2BxKbSqkELVmm3AF5L4CbdAq4VklSOZXBWTZdma45Ot6wbjspS7PmMZ7bGTPDTRVmm6a6MkgwdGWYfFZHsmlXUIDJLQwUB/DIkjyPIyewVXcJclfb1DFTTJDYrS6LYrd2FoyTqqMXD%2BdGMxq/FEdzwZe8zyfVGJC%2BCrLmlQA:

  pushq %rbp
  movq %rsp, %rbp
  movl $1234, %eax # imm = 0x4D2
  movl $0, -4(%rbp)
  popq %rbp
  retq

我理解大部分行,但我不理解“movl $0, -4(%rbp)”。程序似乎将某些局部变量初始化为 0。为什么?

哪些编译器内部细节导致此存储与源代码中的任何内容都不对应?


TL;DR:在未优化的代码中,您的 CLANG++ 为返回值留出了 4 个字节main并根据 C++(包括 C++11)标准将其设置为零。它生成了一个代码main不需要它的功能。这是未优化的副作用。通常,未经优化的编译器会生成它可能需要的代码,然后最终不再需要它,并且没有采取任何措施来清理它。


因为你正在编译-O0对代码进行的优化非常少(-O0可能会删除死代码等)。试图理解未优化代码中的工件通常是一种浪费。未优化代码的结果是额外的加载和存储以及原始代码生成的其他工件。

在这种情况下main很特殊,因为在 C99/C11 和 C++ 中,标准有效地表明,当到达外部块时main默认返回值为0。C11标准 http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1570.pdf says:

5.1.2.2.3 程序终止

1 如果 main 函数的返回类型是与 int 兼容的类型,则从 对 main 函数的初始调用相当于使用该值调用 exit 函数 由主函数作为其参数返回;11)到达终止的 } main函数返回值0。如果返回类型与 int 不兼容, 返回到主机环境的终止状态未指定。

The C++11标准 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf says:

3.6.1 主要功能

5) main 中的 return 语句具有离开 main 函数的效果(使用自动销毁任何对象) 存储持续时间)并以返回值作为参数调用 std::exit。如果控制到达终点 main 没有遇到 return 语句,效果就是执行

 return 0;

在 CLANG++ 版本中,您默认使用未优化的 64 位代码,返回值 0 位于dword ptr [rbp-4].

问题是您的测试代码有点太琐碎,无法看出这个默认返回值是如何发挥作用的。这是一个应该是更好演示的示例:

int main() {
    int a = 3;
    if (a > 3) return 5678;
    else if (a == 3) return 42;
}

此代码有两个退出显式退出点return 5678 and return 42;但没有最终结果return在函数的末尾。如果}达到时默认返回 0。如果我们检查神箭输出 https://gcc.godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(j:1,lang:c%2B%2B,source:'int+main()+%7B%0A++++int+a+%3D+3%3B%0A++++if+(a+%3E+3)+return+5678%3B%0A++++else+if+(a+%3D%3D+3)+return+42%3B%0A%7D%0A%0Aint+foo()+%7B%0A++++return+1234%3B%0A%7D'),l:'5',n:'0',o:'C%2B%2B+source+%231',t:'0')),k:37.46397694524496,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((g:!((g:!((h:compiler,i:(compiler:clang500,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',trim:'1'),lang:c%2B%2B,libs:!(),options:'-std%3Dgnu%2B%2B11+-Wall+-stdlib%3Dlibc%2B%2B+-O0+-fverbose-asm',source:1),l:'5',n:'0',o:'x86-64+clang+5.0.0+(Editor+%231,+Compiler+%231)+C%2B%2B',t:'0')),header:(),k:50,l:'4',m:70.23208879919274,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:1,editor:1),l:'5',n:'0',o:'%231+with+x86-64+clang+5.0.0',t:'0')),header:(),l:'4',m:29.767911200807262,n:'0',o:'',s:0,t:'0')),k:48.24074074074074,l:'3',n:'0',o:'',t:'0'),(g:!((g:!((h:compiler,i:(compiler:g73,filters:(___0:(),b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'1',intel:'0',jquery:'3.2.1',length:1,prevObject:(___0:(sizzle1504678313932:(undefined:(legend:!(11,0,'1')))),length:1,prevObject:(___0:(jQuery3210325021482824634061:(display:''),sizzle1504678313932:(undefined:(legend:!(11,0,'1')))),length:1)),trim:'1'),lang:c%2B%2B,libs:!(),options:'-std%3Dgnu%2B%2B11+-Wall+-O0+-fverbose-asm',source:1),l:'5',n:'0',o:'x86-64+gcc+7.3+(Editor+%231,+Compiler+%232)+C%2B%2B',t:'0')),header:(),k:50,l:'4',m:74.06659939455096,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compiler:2,editor:1),l:'5',n:'0',o:'%232+with+x86-64+gcc+7.3',t:'0')),header:(),l:'4',m:25.93340060544904,n:'0',o:'',s:0,t:'0')),k:51.75925925925926,l:'3',n:'0',o:'',t:'0')),k:62.53602305475504,l:'2',m:100,n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4我们看到这个:

main:                                   # @main
        push    rbp
        mov     rbp, rsp
        mov     dword ptr [rbp - 4], 0        # Default return value of 0
        mov     dword ptr [rbp - 8], 3
        cmp     dword ptr [rbp - 8], 3        # Is a > 3
        jle     .LBB0_2
        mov     dword ptr [rbp - 4], 5678     # Set return value to 5678
        jmp     .LBB0_5                       # Go to common exit point .LBB0_5
.LBB0_2:
        cmp     dword ptr [rbp - 8], 3        # Is a == 3?
        jne     .LBB0_4
        mov     dword ptr [rbp - 4], 42       # Set return value to 42
        jmp     .LBB0_5                       # Go to common exit point .LBB0_5
.LBB0_4:
        jmp     .LBB0_5                       # Extraneous unoptimized jump artifact 
# This is common exit point of all the returns from `main`
.LBB0_5:
        mov     eax, dword ptr [rbp - 4]      # Use return value from memory
        pop     rbp
        ret

正如我们所看到的,编译器生成了一个公共退出点来设置返回值(EAX) 从堆栈地址dword ptr [rbp-4]。在代码的开头dword ptr [rbp-4]显式设置为 0。在更简单的情况下,未优化的代码仍然生成该指令,但未使用。

如果您使用选项构建代码-ffreestanding您应该看到默认返回值main不再设置为 0。这是因为要求默认返回值为 0main适用于托管环境而不是独立环境。

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

未优化的 clang++ 代码在简单的 main() 中生成不需要的“movl $0, -4(%rbp)” 的相关文章

  • 将 System.Drawing.Image 转换为 System.Windows.Media.ImageSource 但没有结果

    我想在我的 WPF 应用程序中将 Image 转换为 ImageSource 我使用正常工作的 Code128 库 已在 WinForms 应用程序中检查 下面的函数返回具有适当大小的 ImageSource 但没有任何内容可见 priva
  • 如何在联系我们页面中使用用户电子邮件发送电子邮件?

    我正在创建一个联系我们页面 并且我想从该页面接收邮件 因为它的邮件来自用户邮件 我写了这段代码 var client new SmtpClient smtp gmail com 587 Credentials new NetworkCred
  • 类型转换 sockaddr 结构

    我正在尝试学习网络编程 并在这个过程中学习C 我对结构感到困惑sockaddr这是一个通用地址 并且sockaddr in 我的书里是这么说的 因此 我们可以填写 sockaddr in 的字段 然后强制转换 a 指向 它指向 指向 soc
  • 线程安全的get(访问器方法)

    我目前正在使用以下代码对变量进行线程安全访问 int gnVariable void getVariableValue int pnValue acquireLock Acquires the protection mechanism pn
  • MikeOS 引导加载程序中的堆栈段

    我不明白这段代码 mov ax 07C0h Set up 4K of stack space above buffer add ax 544 8k buffer 512 paragraphs 32 paragraphs loader cli
  • VBA 中的 VSTO:AddIn.Object 有时不返回任何内容 (null)

    Given VSTO 插件 An override object RequestComAddInAutomationService 它返回一个名为的类的实例Facade在我的场景中 Excel 2007 中的 VBA 宏可访问AddIn O
  • 无法使用 Unity 函数在 Visual Studio Code 中获得完整的 Intellisense

    好吧 我知道这个问题已经被问过并回答过很多次了 但我花了大约 3 天的时间试图解决这个问题 但到目前为止我所做的一切都没有奏效 我基本上在 Visual Studio Code 中有部分智能感知 也就是说 它似乎只识别 Unity 类和变量
  • 在 C++ 中,严格别名规则中的“访问”是什么意思?

    3 10 10 说 如果一个程序试图access通过除以下类型之一之外的泛左值存储对象的值 行为未定义 然而 术语 访问 并没有在任何地方定义 在这种情况下这意味着read or 读取或修改 在 C 标准中 它被明确定义为读取或修改 然而在
  • 使用 C 创建立体声正弦波

    我正在尝试用 C 创建立体声正弦 WAV 并且可能有不同的 可能是空白的 左声道和右声道 使用此函数为每个通道生成一个音调 int16 t create tone float frequency float amplitude float
  • 让 WIX 在项目中包含引用

    我对 WiX 和设置自定义安装程序完全陌生 所以我对问题的主题表示歉意 我有一个内部业务应用程序 日记 它构建并运行良好 因此我按照教程 官方文档添加 WiX 项目并引用日记的 csproj 然后构建并运行这个最基本版本的 WiX 安装程序
  • Boost async_write问题

    我将展示一些代码 void wh const boost system error code ec std size t bytes transferred std cout lt lt test int main int argc cha
  • 发生错误。", ExceptionMessage: "提供的 'HttpContent' 实例无效

    尝试将文件添加到 http 休息调用时出现此错误 responseJson 消息 发生错误 ExceptionMessage 提供了无效的 HttpContent 实例 它确实 正在使用 多部分 参数名称 内容 异常类型 System Ar
  • Parallel.For 和 Break() 误解?

    我正在研究 For 循环中的并行性中断 看完之后this http tipsandtricks runicsoft com CSharp ParallelClass html and this http reedcopsey com 201
  • 允许 .NET WebApi 忽略 DOCTYPE 声明

    我正在尝试通过 WebApi 方法将 XML 反序列化为对象 我有以下课程 XmlRoot IsNullable false public class MyObject XmlElement Name public string Name
  • 读取所有进程内存以查找字符串变量c#的地址

    我有 2 个用 C 编写的程序 第一个名为 ScanMe 的程序包含一个包含值 FINDMEEEEEEE 的字符串变量 以及一个值为 1546 22915487 的双精度变量 另一个名为 MemoryScan 的程序读取第一个程序的所有内存
  • 无论表单上的焦点控件如何,如何捕获 Keys.F1?

    我使用了 KeyDown 事件和一些简单的代码 例如if e KeyCode Keys F1 捕获在表单上按下 F1 但如果表单上有一些文本框 或者表单上有一些带有 Dock Fill 的电子表格 则上面的代码将毫无用处并且不执行任何操作
  • 在同一条线上铸造两次

    我在项目中看到了这段代码 b的类型是void void b int a int unsigned long b 这条线毫无意义吗 我的意思是 这与a int b在所有情况下 这可能会避免 64 位 Unix 系统上的编译器警告unsigne
  • jquery ajax“发布”调用

    我是 jQuery 和 Ajax 的新手 并且在 发布 方面遇到问题 我正在使用 jQuery Ajax post 调用将数据保存到数据库 当我尝试保存数据时 它将 null 传递给我的 C 方法 jQuery 看起来像这样 functio
  • 当另一个进程使用 std::fstream 写入文件时从文件读取[重复]

    这个问题在这里已经有答案了 我需要从文件中逐行读取 它是由 std getline 完成的 另一个进程的问题是一直向其附加数据 然后我需要读取新行 例如 文件一开始包含10行 我的程序读取了10行 那么我的程序应该等待 过了一会儿 另一个进
  • 在 WPF 树视图中获取 FullPath?

    如果我以编程方式创建 WPF TreeView 例如 TreeView treeView lt added in the designer TreeViewItem rootNode new TreeViewItem rootNode He

随机推荐