为什么可执行文件这么大? (为什么死代码没有被删除?)

发布于 2025-01-08 05:33:40 字数 911 浏览 3 评论 0原文

编译并链接此文件会生成 1-KiB 可执行文件

#pragma comment(linker, "/Entry:mainCRTStartup") // No CRT code (reduce size)
#pragma comment(linker, "/Subsystem:Console")    // Needed if avoiding CRT

#define STRINGIFIER(x)    func##x
#define STRINGIFY(x)      STRINGIFIER(x)
#define G   int STRINGIFY(__COUNTER__)(void) { return __COUNTER__; }

int mainCRTStartup(void) { return 0; }  // Does nothing

#if 0
    // Every `G' generates a new, unused function
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
#endif

当您将 #if 0 更改为 #if 1 时,输出大小 <翻倍至 2 KiB。

尽管我的命令行选项包含我能想到的所有优化,但迄今为止所有版本的 Visual C++ 似乎都是这样做的:

/Ox /MD /link /fixed /OPT:ICF /OPT:REF

而且,具体来说,我没有包含任何调试信息。

有谁知道为什么 /OPT:REF 不会导致链接器删除未使用的函数?

Compilng and linking this file results in a 1-KiB executable:

#pragma comment(linker, "/Entry:mainCRTStartup") // No CRT code (reduce size)
#pragma comment(linker, "/Subsystem:Console")    // Needed if avoiding CRT

#define STRINGIFIER(x)    func##x
#define STRINGIFY(x)      STRINGIFIER(x)
#define G   int STRINGIFY(__COUNTER__)(void) { return __COUNTER__; }

int mainCRTStartup(void) { return 0; }  // Does nothing

#if 0
    // Every `G' generates a new, unused function
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
    G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G G
#endif

When you change #if 0 to #if 1), the output size doubles to 2 KiB.

It seems to do this with all versions of Visual C++ to date, even though my command-line options contain all optimizations I could think of:

/Ox /MD /link /fixed /OPT:ICF /OPT:REF

and, specifically, I did not include any debugging information.

Does anyone know why /OPT:REF is not causing the linker to remove the unused functions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

墨洒年华 2025-01-15 05:33:40

从广义上讲......编译器在“对象记录”中生成代码,其中包含一堆汇编代码和支持信息。链接器将这些对象记录链接在一起以创建可执行文件。

通常,编译器会为整个源文件创建一个单个对象记录。在这种情况下,链接器只能决定是否链接整个对象记录。由于对象记录中至少有一个函数被使用,因此它必须链接所有函数。

在某些编译器上,您可以告诉它为每个函数生成单独的对象记录(一个对象文件可以有多个对象记录)。在这种情况下,链接器可以决定省略一些从未被调用过的对象记录。

来自 /OPT 的 Microsoft 文档:

/OPT:参考

<块引用>

LINK 默认删除未引用的封装函数。如果对象是使用 /Gy 选项编译的,则该对象包含打包函数 (COMDAT)。这种优化称为传递 COMDAT 消除。要覆盖此默认值并在程序中保留未引用的 COMDAT,请指定 /OPT:NOREF。您可以使用 /INCLUDE 选项来覆盖特定符号的删除。

/Gy 编译器选项启用函数级链接。

作为参考,这个功能在 gcc 中也存在:

-ffunction-sections
-fdata-sections

<块引用>

如果目标支持任意部分,请将每个函数或数据项放入输出文件中自己的部分中。函数的名称或数据项的名称决定了输出文件中的节的名称。

在链接器可以执行优化的系统上使用这些选项,以提高指令空间中引用的局部性。大多数使用 ELF 对象格式和运行 Solaris 2 的 SPARC 处理器的系统都具有具有此类优化的链接器。 AIX 将来可能会有这些优化。

仅当这样做可以带来显着好处时才使用这些选项。当您指定这些选项时,汇编器和链接器将创建更大的目标文件和可执行文件,并且速度也会变慢。如果指定此选项,您将无法在所有系统上使用“gprof”,并且如果同时指定此选项和 ​​-g,则可能会出现调试问题。

以及 ld 中的伴随选项:

--GC 部分

<块引用>

启用未使用的输入部分的垃圾收集。在不支持此选项的目标上,它将被忽略。此选项与 -r 或 --emit-relocs 不兼容。可以通过在命令行上指定 --no-gc-sections 来恢复默认行为(不执行此垃圾收集)。

In broad terms... the compiler generates code in "object records" that contains a bunch of assembly code and supporting information. The linker links these object records together to create an executable.

Often a compiler will create a single object record for an entire source file. In this case, the linker can only decide to link in the entire object record, or not. Since there is at least one function in the object record that is used, it must link in all of it.

On some compilers, you can tell it to generate a separate object record for each function (an object file can have multiple object records). In this case, the linker can make the decision to omit some of the object records if they're never called.

From the Microsoft documentation for /OPT:

/OPT:REF

LINK removes unreferenced packaged functions by default. An object contains packaged functions (COMDATs) if it has been compiled with the /Gy option. This optimization is called transitive COMDAT elimination. To override this default and keep unreferenced COMDATs in the program, specify /OPT:NOREF. You can use the /INCLUDE option to override the removal of a specific symbol.

The /Gy compiler option enables function-level linking.

For reference, this feature also exists in gcc:

-ffunction-sections
-fdata-sections

Place each function or data item into its own section in the output file if the target supports arbitrary sections. The name of the function or the name of the data item determines the section’s name in the output file.

Use these options on systems where the linker can perform optimizations to improve locality of reference in the instruction space. Most systems using the ELF object format and SPARC processors running Solaris 2 have linkers with such optimizations. AIX may have these optimizations in the future.

Only use these options when there are significant benefits from doing so. When you specify these options, the assembler and linker will create larger object and executable files and will also be slower. You will not be able to use "gprof" on all systems if you specify this option and you may have problems with debugging if you specify both this option and -g.

And the companion option in ld:

--gc-sections

Enable garbage collection of unused input sections. It is ignored on targets that do not support this option. This option is not compatible with -r or --emit-relocs. The default behaviour (of not performing this garbage collection) can be restored by specifying --no-gc-sections on the command line.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文