为什么我的 hello world 二进制大部分为零?

问题描述 投票:0回答:1

我已经编译好了

#include <stdio.h>

int main() {
    printf("Hello world");
    return 0;
}

在 Mac 上,大小为 48k。然而,当我用

xxd
查看二进制文件时,大部分看起来像这样:

...
0000b990: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000b9a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
0000b9b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
...

为什么会这样?

otool
告诉我:

 otool -L hello
hello:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1292.0.0)

太好了,它再次动态链接到 libSystem,它

printf
就在那里。

那为什么全是零?

c macos assembly executable mach-o
1个回答
8
投票

因为对齐。

XNU 强制映射二进制文件部分的每个段都与平台的页面大小对齐。在 x86_64 上,这是 0x1000 字节,在 arm64 上,这是 0x4000 字节(即使硬件支持 0x1000)。如果某些段的数据必须与某个偏移量对齐,那么文件中必须有“某些东西”来填充之间的间隙 - 通常为零。 现在,如果您的二进制文件是 48KB,那么它的段可能看起来像这样:

LC 00: LC_SEGMENT_64 Mem: 0x000000000-0x100000000 File: Not Mapped ---/--- __PAGEZERO LC 01: LC_SEGMENT_64 Mem: 0x100000000-0x100004000 File: 0x0-0x4000 r-x/r-x __TEXT LC 02: LC_SEGMENT_64 Mem: 0x100004000-0x100008000 File: 0x4000-0x8000 rw-/rw- __DATA_CONST LC 03: LC_SEGMENT_64 Mem: 0x100008000-0x10000c000 File: 0x8000-0xc000 rw-/rw- __DATA LC 04: LC_SEGMENT_64 Mem: 0x10000c000-0x100010000 File: 0xc000-0xc110 r--/r-- __LINKEDIT

对于 0x4000 的对齐,这已经是最小布局。但如果您使用 Intel,则可以通过将 
-Wl,-segalign,0x1000

传递给编译器来强制链接器使用 0x1000。这应该会生成一个只有 12KB 左右的二进制文件:

LC 00: LC_SEGMENT_64  Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--- __PAGEZERO
LC 01: LC_SEGMENT_64  Mem: 0x100000000-0x100001000  File: 0x0-0x1000    r-x/r-x __TEXT
LC 02: LC_SEGMENT_64  Mem: 0x100001000-0x100002000  File: 0x1000-0x2000 rw-/rw- __DATA_CONST
LC 03: LC_SEGMENT_64  Mem: 0x100002000-0x100003000  File: 0x2000-0x3000 rw-/rw- __DATA
LC 04: LC_SEGMENT_64  Mem: 0x100003000-0x100004000  File: 0x3000-0x3110 r--/r-- __LINKEDIT

如果您想进一步优化您的二进制文件,您需要删除段。通过导入和链接,您真正可以摆脱的唯一一个是 
__DATA_CONST

,您可以通过使用

-mmacosx-version-min=10.14
定位 macOS Mojave(或更早版本)来做到这一点。这将为您留下略多于 8KB 的空间:
LC 00: LC_SEGMENT_64  Mem: 0x000000000-0x100000000  File: Not Mapped    ---/--- __PAGEZERO
LC 01: LC_SEGMENT_64  Mem: 0x100000000-0x100001000  File: 0x0-0x1000    r-x/r-x __TEXT
LC 02: LC_SEGMENT_64  Mem: 0x100001000-0x100002000  File: 0x1000-0x2000 rw-/rw- __DATA
LC 03: LC_SEGMENT_64  Mem: 0x100002000-0x100003000  File: 0x2000-0x20f0 r--/r-- __LINKEDIT

如果您力求尽可能最小的可执行文件,您可以进一步放弃 
__DATA

,甚至可能

__LINKEDIT
,但您必须大幅更改代码以仅发出原始系统调用,而不使用动态链接器等。
对于任何现实世界的应用程序,我还想说这些零实际上并不重要。给定四个映射段,它们使用的空间永远不会超过 48KB。二进制越大,零所占的百分比就越小。

至于分布,答案很明显:

xz

用它压缩上述二进制文件会产生:

48KB 二进制文件需要 776 字节。
  • 12KB 二进制文件为 736 字节。
  • 8KB 二进制文件为 684 字节。
© www.soinside.com 2019 - 2024. All rights reserved.