30

这个 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 算术、无范围枚举、指针或指向成员类型的指针的纯右值可以转换为 bool 类型的纯右值。将零值、空指针值或空成员指针值转换为 false;任何其他值都将转换为 true。对于直接初始化 (8.5),std::nullptr_t 类型的纯右值可以转换为 bool 类型的纯右值;结果值为假。

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

4

3 回答 3

28

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

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

其中“空指针值”包括空函数指针。由于函数名衰减得到的函数指针不能为空,这给出了true. 您可以通过包含<< std::boolalpha在输出命令中来查看这一点。

以下确实会导致 g++ 中的链接错误:(int)x;


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

名称显示为潜在求值表达式的函数,如果它是唯一的查找结果或一组重载函数的选定成员 [...]

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

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

所以程序是格式错误的,但不需要诊断,这意味着程序的行为是完全未定义的。

顺便说一句,本条款暗示两者都不需要链接错误x();,但是从实现质量的角度来看;那太傻了。在这里选择的课程g++对我来说似乎是合理的。

于 2014-11-25T21:09:07.227 回答
14

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

于 2014-11-25T20:59:31.897 回答
9

[basic.def.odr]/2:

名称显示为潜在求值表达式的函数如果是唯一的查找结果或一组重载函数(3.4、13.3、13.4)的选定成员,则它是 odr-used,除非它是纯虚函数及其名称没有明确限定。

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

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

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

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

于 2014-11-25T21:19:10.080 回答