【问题标题】:Position of functions in executable函数在可执行文件中的位置
【发布时间】:2013-01-01 20:12:24
【问题描述】:

C 标准中是否有要求编译(和链接)二进制文件中的函数将按照它们在 C 文件中写入的顺序出现?

请假设在下面的示例中编译器没有删除/内联任何函数,并且它们都存在于二进制文件中。问题不在于编译器如何处理空函数,而在于函数的顺序。

例如,如果我编译example.c:

void bar() {  }
void foo() {  bar();  }
int main() {   foo();  }

我可以确定foo 在输出文件中会出现在bar 之后吗?

【问题讨论】:

  • 我认为 C 标准中没有关于目标文件布局(甚至关于目标文件)的任何内容。即使有,为什么这是一个有趣的保证?
  • @Mat - 我正在与某人讨论通过从下一个函数的指针中减去其指针来找到函数大小(近似值)的选项。在某些情况下它会派上用场。
  • @BinyaminSharet 这是一个合理的用例,但应该使用平台的 ABI 库(libelf、libmacho 等)来完成。如果一个实现正确地记录了这些细节,那么它本身(应该是?)足以正确地执行这样的计算。
  • 编译器可以将您的代码(内存中)更改为int main() { } 并编译它。请注意,现在没有函数foo()bar() :)
  • @H2CO3 - 谢谢,我没有提到具体的格式或工具链,因为我感兴趣的平台不是标准的,实际上可能没有这样的库,因为其中一些是嵌入的,它们可能根本没有可用的库,因为整个图像是一个二进制文件。

标签: c compiler-construction linker


【解决方案1】:

不,C 标准中没有这样的要求。在编译和链接方面,仅明确提到了函数的特定属性,例如外部或静态链接等,但即使是这些,也大多以独立于实现的方式描述。到目前为止,任何标准文档中都没有任何条款(据我所知)对可执行文件中的符号顺序施加期望。

【讨论】:

  • 甚至不能保证函数会占用连续的地址集(较新的 GCC 版本可能会将函数拆分为“热”和“冷”区域,MSVC 编译器也会这样做)。另见gcc.gnu.org/ml/gcc-patches/2011-09/msg01440.html
  • @EmployedRussian 是的,没错,就像C++03中旧的std::string的存储一样。
【解决方案2】:

语言中对此没有规定。通常,它们确实按照您查看代码时所期望的顺序出现,但没有什么说编译器不能构建函数堆栈,并以完全相反的顺序输出它们——当然,一个没有被调用的函数可以删除,类似地,内联函数并且编译器可以确定它不需要外部引用可以以其原始形式删除。

你可以通过char *ptr = (char *)bar;找到一个函数在哪里。

编辑:请注意,通过获取函数的地址,您可能会更改函数的内联,因此不要指望这是确定编译器“在正常情况下”做什么的好方法。

【讨论】:

  • 谢谢,我知道如何找到函数指针,编译器可能会省略/内联一些函数,问题是关于编译后的二进制文件中实际存在的函数的顺序。
  • 语言本身没有关于它的规则。由于编译器必须在内存中保留大量有关代码的数据,因此“按其进入的顺序”输出是有意义的,但绝对不能保证 [就像不能保证 if 语句的顺序一样,或者if 的 else 紧接在 if 之后 - 我当然已经调试过 Microsoft 编译的代码,其中 else 在某些情况下位于函数的末尾,尽管最初的 if 是函数中的第一个语句,并且有很多之后在同一“级别”的其他 if/else 语句。
【解决方案3】:

无法通过编译器开关单独来控制它。你需要一个两步的过程;在这里为 ELF(UN*X 对象文件格式)说明了这一点,但对于 Windows PE 对象,它可以类似地完成。

  1. 指示编译器为必须严格控制其代码放置的函数生成单独的/特定的目标文件ELF Sections。这可以在 GCC 中通过function attributescommand line switches 完成。
    根据您想要实现的放置类型,GCC 的某些功能属性(hotcold、...)可能已经满足您的需求,但如果没有,特定的排序/特定位置是绝对必要的,那么……
  2. 指示链接器以特定方式将输入部分排序/重新排列/合并/定位到输出中。

实际的代码/数据放置发生在链接时间 - 链接器可以控制“构成对象”的目标代码放置 - 源对象的 ELF 部分 - 在生成的目标“复合对象”内,即生成的可执行文件/库。这通过链接器脚本发生。如果使用自定义链接器脚本指示这样做,链接器将把输入部分放在用户指定的位置/以用户指定的顺序到输出对象中。请参阅有关链接描述文件的GNU binutils (ld) manual

因此(反映链接器实际将输入的各个部分放入输出的位置)您可以请求生成Linker Mapfile;如果您使用非默认/自定义链接器脚本来严格控制代码放置,那么您应该指示链接器执行此操作,以便您可以交叉检查它是否符合您的要求。否则,如果您使用链接器的默认值,则映射文件会告诉您在没有特定覆盖的情况下做了什么——这可能是也可能不是您想要的,但它至少是一种检查方法。

【讨论】:

    【解决方案4】:

    没有这样的要求。但是,在您的示例中,如果 bar() 出现在 foo() 之后,则 foo 会将 bar() 视为返回整数的尚未定义的函数。

    【讨论】:

    • 仅当没有bar()的前向声明时。
    猜你喜欢
    • 2016-06-13
    • 1970-01-01
    • 1970-01-01
    • 2013-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多