为什么C ++链接器允许未定义的函数?

时间:2014-11-25 20:56:27

标签: c++ compilation linker language-lawyer undefined-function

这个C ++代码,或许令人惊讶地打印出1

#include <iostream>

std::string x();

int main() {

    std::cout << "x: " << x << std::endl;
    return 0;
}

x是一个函数原型,它似乎被视为一个函数指针,而C ++标准部分4.12布尔转换说:

  

4.12布尔转换[conv.bool] 1算术,无范围枚举,指针或指向成员类型的指针的prvalue可以是   转换为bool类型的prvalue。零值,空指针值,   或null成员指针值转换为false;任何其他价值   转换为真。对于直接初始化(8.5),类型的prvalue   std :: nullptr_t可以转换为bool类型的prvalue;该   结果值是假的。

但是,x永远不会绑定到函数。正如我所料,C链接器不允许这样做。但是在C ++中,这根本不是问题。任何人都可以解释这种行为吗?

3 个答案:

答案 0 :(得分:28)

这里发生的是函数指针被隐式转换为bool。这由[conv.bool]指定:

  

零值,空指针值或空成员指针值转换为false;   任何其他值都转换为true

其中&#34;空指针值&#34;包括空函数指针。由于从函数名称的衰减中获得的函数指针不能为空,因此给出true。您可以通过在输出命令中包含<< std::boolalpha来查看此内容。

以下操作会导致g ++中出现链接错误:(int)x;


关于是否允许这种行为,C ++ 14 [basic.odr.ref]/3说:

  

如果名称显示为可能已评估的表达式,则该函数将使用odr   唯一查找结果或一组重载函数的所选成员[...]

确实涵盖了这种情况,因为输出表达式中的x被查找到上面x的声明,这是唯一的结果。然后在/4我们有:

  

每个程序应该只包含该程序中使用的每个非内联函数或变量的一个定义;无需诊断。

所以程序结构不合理,但不需要诊断,这意味着程序的行为是完全未定义的。

顺便说一句,这条款意味着x();也不需要链接错误,但是从实现质量的角度来看;那会很傻。 g++选择的课程对我来说似乎很合理。

答案 1 :(得分:13)

X不需要“绑定”到函数,因为您在代码中声明存在此类函数。因此编译器可以安全地假设该函数的地址不能为NULL。为了实现这一点,你必须声明函数是一个弱符号,而你却没有。链接器没有抗议,因为你从不调用你的函数(你从不使用它的实际地址),所以它没有看到任何问题。

答案 2 :(得分:9)

[basic.def.odr] / 2:

  

显示名称的功能   如果它是唯一的查找结果或a的选定成员,则可能使用的表达式是odr-used   重载函数集(3.4,13.3,13.4),除非它是纯虚函数且其名称未明确   合格。

因此,严格地说,代码odr-使用函数,因此需要定义。
但是现代编译器会意识到函数的确切地址实际上并不与程序的行为相关,因此会忽略使用而不需要定义。

还要注意[basic.def.odr] / 3指定的内容:

  

每个程序都应包含每个非内联的一个定义   在该程序中使用的函数或变量; 没有诊断   必需的。

实现没有义务停止编译并发出错误消息(= diagnostic)。它可以做到它认为最好的。换句话说,任何行动都是允许的,我们有UB。