以下代码在g++
10.1.0版中成功编译:
int main()
{
int& x = x;
return x;
}
编译器甚至为此定义了一个警告,暗示它不是错误。使用编译时-Wall
,将返回以下输出:
<stdin>: In function ‘int main()’:
<stdin>:3:14: warning: reference ‘x’ is initialized with itself [-Winit-self]
3 | int& x = x;
| ^
<stdin>:3:14: warning: ‘x’ is used uninitialized in this function [-Wuninitialized]
3 | int& x = x;
|
我的测试表明,这导致对地址0处的值的引用,就像将其声明为一样int& x = *(int*)nullptr;
。当然,如果x
曾经使用引用的值,则会导致段错误。
我可以想到一个可能会有用的情况:如果您要在没有可访问构造函数的类上调用方法,则当该方法不是static
但仍然不使用该类的数据时:
struct S {
S() = delete;
int value() { return 69; }
};
int main()
{
S& s = s;
return s.value();
}
但是,还有其他方法可以做到这一点,甚至不发出警告,-Wall
并且不太可能是未定义的行为。(例如,((S*)0)->value()
。)
那么,我要问的是:是这其实不确定的行为?看起来肯定是这样。在任何可能的情况下,以自我参照为最佳解决方案吗?如果不是,为什么默认情况下不显示警告?
之所以接受此代码,是因为C ++语法允许它;不是因为有任何实际原因。
在C ++语法中,定义的第一部分引入了名称标识符,该名称标识符可能在范围的其余部分中,包括在同一语句中。这就是让您拥有如下代码的原因:
int x = 1, &y = x;
第一个int x
产生的名称x
可以在看到之后在任何地方使用。这也会带来自我分配的副作用,例如:
int x = x;
实际上,此分配是未定义的行为,因为x
它尚未初始化,因此正在从未初始化的数据(UB)中读取。
自定义引用也是如此。会int& x
引入名称x
,而= x
绑定到可与引用绑定的类型(也恰好是int& x
)。该引用在分配时尚未初始化,因此此访问是未定义的行为。
编译器可以自由选择处理未定义的行为,但是可以选择-似乎您正在观察gcc将引用的地址视为0x0
。这不是保证的行为,并且可能会受编译器版本,优化级别等影响。
这样做没有真正的实际目的。但是使用诸如这样的现代实践很容易解决auto
,因为auto
在自构建中使用的代码将无法推断出底层类型:
auto x = x; // error, cannot deduce x
auto& y = y; // error, cannot deduce y
为什么默认情况下禁止显示警告?
C ++标准仅指示少数需要诊断消息的错误。许多错误属于“无需诊断”,编译器可以选择通过可选标志添加警告。这取决于实现的自由裁量权,对于编译器的作者来说将是一个问题。
大多数未初始化的访问错误都不需要诊断,我认为这属于诊断范围。
至于使用自引用的可疑原因:这将是未定义的行为,如您的示例所示((S*)0)->value()
。
由于引用是从UB构建的,因此从中访问任何成员也是UB。
对于您的另一个示例,对空指针的任何形式的成员访问都是未定义的行为,而不管该成员函数是否访问任何成员状态或是否内联。这并不意味着它可能无法正常工作与正确的编译器正确的编译器标志; 但这不是便携式,可靠或安全的解决方案。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句