更新:我不是在要求人们尝试这种方法,看看代码是否适合他们。我在问代码模式是否合法的C ++,无论它是否对您有用。
我正在调查我认为是瑞萨RX CPU的IAR C ++编译器中的错误。以下代码模式的示例有时可以在我的小型嵌入式系统上运行,而有时它在parentRefToChildInstance
初始化期间崩溃,跳转到地址0x00000000(或附近);我也看到跳转到0x00000038。对于给定版本的源代码,行为似乎在编译之间是一致的,但是如果代码以看似无关的方式受到干扰,则有时行为会切换。
拥有对静态分配的子类对象的纯虚拟父类引用是合法的,还是因为不能保证静态分配的对象的初始化顺序而合法?
char aGlobalVar = 0;
struct parent
{
virtual ~parent() {}
virtual void method1() = 0;
};
struct child : public parent
{
child(int someValue) : m_someData(someValue) {}
virtual ~child() {}
virtual void method1() { ++aGlobalVar; }
int m_someData;
};
child childInstance(0x1234abcd);
parent &parentRefToChildInstance = childInstance;
在发生崩溃的情况下,在初始化父类引用时尚未构造子类对象;我怀疑编译器以某种方式使用子对象的vtable指针来初始化父类引用,尽管我还没有确定。 但是我认为编译器应该能够仅知道其引用对象的类型及其地址,就可以初始化一个引用,这两者应分别在编译时和链接时知道。如果是这样,那么初始化childInstance
和parentRefToChildInstance
的顺序似乎无关紧要。
此外,如果这很重要,我们仍然限于C ++ 03。
这里是main()
以及上面的代码...
int main()
{
printf("aGlobalVar = %u\n", aGlobalVar);
childInstance.method1();
printf("aGlobalVar = %u\n", aGlobalVar);
parentRefToChildInstance.method1();
printf("aGlobalVar = %u\n", aGlobalVar);
}
通常,我希望它能打印出此图像,并且在静态对象初始化期间(甚至在main()
开始之前也不会崩溃):
aGlobalVar = 0
aGlobalVar = 1
aGlobalVar = 2
答案 0 :(得分:2)
显示的代码是合法的。
的确,当定义位于不同的翻译单元中时,在命名空间范围内或作为static
类成员定义的对象和引用的初始化顺序是不可预测的,这通常会导致麻烦。
但是初始化引用实际上并不需要初始化绑定的对象,除非涉及到虚拟继承。
C ++ 17 [basic.life]第7段说:
在一个对象的生命周期开始之前,但是在分配了该对象将要占用的存储空间之后……,可以使用任何引用原始对象的glvalue,但是只能以有限的方式使用。有关正在构造或销毁的对象,请参见[class.cdtor]。否则,此类glvalue指分配的存储,并且使用不依赖于其值的glvalue属性是明确定义的。该程序在以下情况下具有未定义的行为:
glvalue用于访问对象,或者
glvalue用于调用对象的非静态成员函数,或者
glvalue绑定到对虚拟基类的引用,或者
glvalue用作
dynamic_cast
的操作数或typeid
的操作数。
在parentRefToChildInstance
初始化期间,这四件事均未发生,特别是因为parent
不是child
的虚拟基类。因此,该代码属于定义明确的引用要求中提到的情况。