【问题标题】:Are Assembly programs almost the same size as C programs汇编程序的大小与 C 程序的大小几乎相同吗
【发布时间】:2018-07-08 16:04:01
【问题描述】:

例如:我创建了一个打印“Hello, World”的简单 C 程序,编译它并创建了一个大小为 39.8Kb 的可执行文件。

按照this 的问题,我能够创建等效但用Assembly 编写的程序的大小为39.6Kb。

这让我大吃一惊,因为我预计汇编程序会比 C 程序小。正如问题所示,它使用 C 头文件和 gcc 编译器。这会使汇编程序更大还是它们的大小大致相同是否正常?


使用strip 命令我减少了这两个文件。这删除了调试代码,现在两者的文件大小非常相似。均为 18.5Kb。

test.c:

【问题讨论】:

  • 您为什么希望它们有很大不同?他们在做同样的事情。
  • @Barmar 我被引导相信人们(有时)使用汇编器,因为它级别更低、速度更快并且输出更小,但如果我错了,请告诉我。
  • 编译器非常擅长生成最优代码。
  • 对于一个非常小的程序,大小主要由开销和任何链接库决定。如果您使用 C 编译器来编译汇编器,那么它们可能是相同的。
  • 您在汇编中只重写了该应用程序的一小部分(仅调用 printfexit)并将 95% 的代码的实现留给 C 运行时库(您可能非常低估了“下”完成的工作量)。这对于您的 asm 版本和 C 版本都是相同的,所以难怪您以大致相同的可执行文件结束。最小的 windows PE 可执行文件据说是 133 字节,我没有检查它在 DOS 标头区域中是否还有足够的空间用于快速和肮脏的 hello world 输出,可能没有,但假设 200B 可能就足够了。剩余的 39kB 是便利性和 C 运行时。

标签: c windows assembly executable filesize


【解决方案1】:

如果您的手写代码与编译后的函数相当,那么请确保它们的大小相似,它们执行相同的操作,如果您可以与编译器竞争,那么您将是相同或相似的。

现在您的文件大小表明您正在查看错误的内容。您正在查看的称为二进制文件的文件中包含大量其他内容。您想在这种情况下比较苹果和苹果,然后比较函数的大小、机器代码,而不是保存函数的容器的大小、调试信息、字符串以及其他一些东西。

您的实验存在缺陷,但结果非常松散地表明了预期结果。但那是如果您以相同的方式生成代码。这种可能性很小,因此除非您以相同的方式生成代码,否则您不应该期待类似的结果。

使用这个简单的函数

unsigned int fun ( unsigned int a, unsigned int b)
{
    return(a+b+1);
}

同样的编译器产生了这个:

00000000 <fun>:
   0:   e52db004    push    {r11}       ; (str r11, [sp, #-4]!)
   4:   e28db000    add r11, sp, #0
   8:   e24dd00c    sub sp, sp, #12
   c:   e50b0008    str r0, [r11, #-8]
  10:   e50b100c    str r1, [r11, #-12]
  14:   e51b2008    ldr r2, [r11, #-8]
  18:   e51b300c    ldr r3, [r11, #-12]
  1c:   e0823003    add r3, r2, r3
  20:   e2833001    add r3, r3, #1
  24:   e1a00003    mov r0, r3
  28:   e28bd000    add sp, r11, #0
  2c:   e49db004    pop {r11}       ; (ldr r11, [sp], #4)
  30:   e12fff1e    bx  lr

还有这个

00000000 <fun>:
   0:   e2811001    add r1, r1, #1
   4:   e0810000    add r0, r1, r0
   8:   e12fff1e    bx  lr

因为设置不同。 13 条指令 vs 3 条指令,超过 4 倍。

人类可以直接从 C 生成这个,没什么特别的

add r0,r0,r1
add r0,r0,#1
bx lr

如果您在技术上必须先将一个添加到 b,然后再将该总和添加到 a,则不确定操作顺序。或者如果没关系。我从左到右编译器从右到左。

所以你可以说编译器和我的程序集产生了相同数量的二进制文件,或者你可以说编译器产生了超过 4 倍的二进制文件。

将上述内容扩展为一个真正有用的程序。

练习给读者(OP,请不要破坏它)以弄清楚为什么编译器可以产生两个大小如此不同的不同正确解决方案。

编辑

.exe、elf 和其他提到的“二进制”格式可以包含调试信息、包含功能/标签名称的 ascii 字符串,这些功能/标签可以制作漂亮的调试屏幕。它们是“二进制”的一部分,因为它们是包袱的一部分,但不是机器代码,也不是执行该程序时使用的数据,至少不是我提到的东西。您可以在不更改程序所需的机器代码或数据的情况下,使用编译器设置来操纵 .exe 或其他文件格式的大小,因此相同的编译器-汇编器-链接器或汇编器-链接器路径可以使二进制文件在某种意义上通过包括或不包括这个额外的行李,这个词更大或更小。所以这是理解文件大小的一部分,为什么即使你的 hello world 程序的大小不同,整个文件的大小也可能大致相同,如果一个文件长 10 个字节但 .exe 是 40K,那么 10 个字节在噪音。但是,如果我理解您的问题,那么您有兴趣了解它在编译 C 和手写 C 之间的比较。

另请注意,编译器是由人类制造的,因此它们产生的输出至少与那些人类可以产生的输出相当,其他人可以做得更好,许多人做得更差,具体取决于您对更好和更差的定义。

【讨论】:

  • 这个大尺寸绝对与编译器无关。它与crt如何链接到程序有关。如果我们使用静态链接 - crt 代码的很大一部分链接到程序。如果在 dll (msvcrt.dll) 和 c-runtime 中使用 crt - c++ 代码的大小将接近 2500 字节
  • 静态与动态是其中的一部分,但这与编译器与手写 asm 无关。您可以使用这些链接器功能来放大或缩小,以及调试信息和其他包袱。
  • crt 通常相对于程序的其余部分非常小。当然取决于程序。但就像静态与动态一样,它不适用于这个问题,因为 main() 和 main: 可以有相同的包袱。它们不是编译为 asm 与手写 asm 之间区别的一部分。
  • 除了静态 vs 动态使用 crt,还存在 c 运行时 - 即使您使用动态 crt 链接,也始终静态链接到二进制文件的一段代码。例如,我只是构建#include &lt;Windows.h&gt; #include &lt;stdio.h&gt; void ep(void*) { ExitProcess(printf("Hello, World")); },它的 exe 大小为 2560 字节
  • 编译后的代码,大多数主流工具在链接器之前通过汇编器。所以很明显,编译与手写可以有相同的包袱,而包袱不是我阅读的问题的一部分。如果您忽略编译部分和手写部分并比较为什么一个 .exe 的大小与另一个不同,这就是整个问题。请发布您的答案,以便 OP 有机会更改选择的答案。行李或编译与手写。
【解决方案2】:

绝对大小 39+ Kb 与使用的编译器和语言(c/c++asm)不同的优化、调试信息等无关 - 可以更改此大小说 1000 字节的小代码。但不多。 i 用于测试构建下一个程序

#include <Windows.h>
#include <stdio.h>
void ep(void*)
{
    ExitProcess(printf("Hello, World"));
}

链接器选项:

/INCREMENTAL:NO /NOLOGO /MANIFEST:NO /NODEFAULTLIB 
/SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /ENTRY:"ep" /MACHINE:X64 kernel32.lib msvcrt.lib

并获得了 x86/x64 大小为 2560 字节的 exe。

有什么不同?在/NODEFAULTLIB 和我的msvcrt.lib 版本中 - 这是纯导入库。

您通过使用的静态链接 c 运行时提供的其余 35kb+ 大小。即使您在 asm 上编写程序 - 您也需要使用一些库来链接到 printf。并且您的库包含一些与您的代码静态链接的代码。在这段代码中这 35kb。

task 不是 c++ vs asm - 这里没有什么不同。任务在使用 c-runtime 还是不使用

【讨论】:

    【解决方案3】:

    我同意 old_time 但我也对基本事实进行了快速测试。使用 VS-2017 Pro,我在可执行文件的大小上得到类似的结果 (~37KB),但前提是我查看调试输出文件夹。在构建发布后,它接近约 9KB。大部分差异在于调用 OS/C 运行时 DLL 所需的静态库的大小。

    编辑:尽管大多数现代 C 编译器可以匹配或胜过大多数手写汇编代码,但手写的种类可能会更小,因为它不必让所有 C 运行 -时间开销,但差异很少足以保证汇编代码的额外开发和维护成本,特别是对于非平凡的应用程序。大多数现代操作系统内核主要是用 C 或其他高级语言编写的,只有少数关键功能中的针孔汇编程序优化是有原因的。

    琐碎的“hello world”类程序不能很好地比较 C 与汇编程序。编译器或人类没有足够的机会在优化方面做很多事情。编写一个数学或数据处理库和应用程序并比较它们。我愿意打赌编译器会踢你的但是。

    【讨论】:

    • 我没有运行 VS。我正在运行 MinGW (gcc) 但还是谢谢你。
    • @Simon,所有工具链都有类似的要求。调试代码比发布代码大,并且在大多数情况下,针对相同操作系统的编译器之间没有太大区别,因为它们必须链接到基本相同的库。
    • 是的,你是对的。我刚刚发现了如何使用strip 命令减少到 9.5Kb。
    • @Simon,即使那时剩下的大部分仍然是您必须访问 OS API 的那些静态库。
    • 使用汇编程序,如果你真的在使用它,你可以做很多事情来减少琐碎程序的可执行文件大小。例如,参见muppetlabs.com/~breadbox/software/tiny/teensy.html:一个刚刚退出的 Linux ELF 可执行文件可以打包成 42 个字节(ELF 程序头中的机器代码,作为一些无关紧要的字段的值)!在不便携/不支持直接使用系统调用的 Windows 上,您无法避免动态链接某些 DLL,因此您不能只制作直接进行系统调用的微小静态可执行文件(除了作为 hack)。
    猜你喜欢
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    • 2015-02-13
    • 2010-12-22
    • 1970-01-01
    • 2017-03-16
    • 2014-02-26
    相关资源
    最近更新 更多