简体   繁体   English

成员结构位域元素的初始化程序列表初始化导致IAR ARM中的错误

[英]Initializer list initialization of a member struct bitfield element causing bugs in IAR ARM

I have the following class structure in IAR: 我在IAR中具有以下类结构:

class A
{
public:
    A(){}
    virtual ~A() {};
    virtual void load() {};
};


class C
{
public:
    C()
    {
        //C does other stuff, not relevant
    }
};

class D;

class B : public A
{
public:
    B() : invert(false) {};
    virtual ~B() {};
    void load()
    {
        //Irrelevant stuff done here
    }
private:
    C member_c;
    std::vector<D*> vector_of_d;
    struct {
        bool var_1:1;
        bool var_2:1;
        bool var_3:1;
        bool var_4:1;
        bool invert:1;
    };
};

I am running into bugs with the assembly generated to initialize B, where it seems to be getting 'confused' about where the VTable pointer is vs where the anonymous struct bitfield is. 我在生成初始化B的程序集时遇到了bug,在这里似乎对VTable指针的位置与匿名struct位域的位置感到“困惑”。 When it goes to set the invert bit false, it goes to the first word of the object (which is the VTable pointer) and flips a bit in the address. 当将反相位设置为false时,它将转到对象的第一个字(即VTable指针),并在地址中翻转一位。 When I later call load() , it follows the invalid VTable pointer and ends up finding a null pointer, which it then blindly follows. 当我稍后调用load() ,它跟随无效的VTable指针并最终找到一个空指针,然后它被盲目跟随。 Things obviously fall apart from there. 事情显然从那里消失了。

Here is an example of the code that would invoke this problem: 这是将调用此问题的代码示例:

void load_A(A* to_be_loaded){
    if(to_be_loaded) to_be_loaded->load();
}

int main(){
   load_A(new B());
}

Now the big question, is have I accidentally introduced some undefined behavior somewhere? 现在最大的问题是,我是否在某个地方意外地引入了一些未定义的行为? This is code that is being ported from GCC-ARM, where it worked fine, but now suddenly it is causing hard-faults when compiled with IAR. 这是从GCC-ARM移植的代码,在这里工作正常,但现在突然用IAR编译时会导致硬故障。 My two theories are: 我的两个理论是:

  • It's a compiler bug (I know, it's never a compiler bug) 这是一个编译器错误(我知道,它从来不是编译器错误)
  • It's non-standard behavior that GCC handled as an extension GCC作为扩展程序处理的是非标准行为

As far as I can tell, there shouldn't be anything wrong with using an initializer list to initialize a field in an anonymous struct. 据我所知,使用初始化列表在匿名结构中初始化字段应该没有任何问题。 I do recognize that anonymous structs are a compiler extension, but they are documented in both IAR and GCC. 我确实认识到匿名结构编译器扩展,但是IAR和GCC中都记录了它们。 Either way, IAR is not giving me any warning or error, and is generating clearly broken assembly. 无论哪种方式,IAR都不会给我任何警告或错误,并且会生成明显损坏的程序集。

Here is the assembly that it made for the B constructor 这是它为B构造函数制作的程序集

1 |    B() : invert(false) {};
2 |B::B():
3 |_ZN6BC1Ev:
4 |    0x80645e8: 0xb510         PUSH      {R4, LR}
5 |    0x80645ea: 0x4604         MOV       R4, R0
6 |    B() : invert(false) {};
7 |    0x80645ec: 0xf007 0xfb20  BL        A::subobject A() ; 0x806bc30
8 |    0x80645f0: 0x4807         LDR.N     R0, [PC, #0x1c]         ; 0x8088808 (134776840)
9 |    0x80645f2: 0x6020         STR       R0, [R4]
10|    0x80645f4: 0xf104 0x0018  ADD.W     R0, R4, #24             ; 0x18
11|    0x80645f8: 0xf00a 0xfadd  BL        C::C()              ; 0x806ebb6
12|    0x80645fc: 0xf104 0x001c  ADD.W     R0, R4, #28             ; 0x1c
13|    0x8064600: 0xf00e 0xff2e  BL        std::vector<D *>::vector() ; 0x8073460
14|    0x8064604: 0x7820         LDRB      R0, [R4]
15|    0x8064606: 0xf000 0x00ef  AND.W     R0, R0, #239            ; 0xef
16|    0x806460a: 0x7020         STRB      R0, [R4]
17|    B() : invert(false) {};
18|    0x806460c: 0x4620         MOV       R0, R4
19|    0x806460e: 0xbd10         POP       {R4, PC}
20|    0x8064610: 0x08088808     DC32      0x8088808 (134776840)

On line 14, we load the value that R4 points to, which is the base address of our object. 在第14行,我们加载R4指向的值,这是对象的基地址。 It does not apply any offset to it, which means it points to the first thing in the object which is the VTable pointer. 它不对其施加任何偏移,这意味着它指向对象中的第一件事,即VTable指针。 It then continues with the assumption that it has the bitfield and unsets one bit on line 15 before putting it back into the object where it got it from on line 16. 然后,继续假设它具有位域,并在将其放回第16行中得到它的对象之前,在第15行上取消设置一位。

For reference, if we change the constructor of B to not use initializer lists (shown below) it will work as expected: 作为参考,如果我们将B的构造函数更改为使用初始化列表(如下所示),它将按预期工作:

class B : public A
{
public:
    B(){ invert = false; };
    virtual ~B() {};
    void load()
    {
        //Irrelevant stuff done here
    }
private:
    C member_c;
    std::vector<D*> vector_of_d;
    struct {
        bool var_1:1;
        bool var_2:1;
        bool var_3:1;
        bool var_4:1;
        bool invert:1;
    }
};

The generated assembly is as follows, take note of the offset used in the LDRB and STRB instructions on lines 14 and 16. This is the proper offset to access the bitfield in the object. 生成的程序集如下,请注意第14和16行上LDRBSTRB指令中使用的偏移量。这是访问对象中位域的适当偏移量。

1 |    B(){ invert = false; };
2 |B::B():
3 |_ZN6BC1Ev:
4 |    0x80645e8: 0xb510         PUSH      {R4, LR}
5 |    0x80645ea: 0x4604         MOV       R4, R0
6 |    B(){ invert = false; };
7 |    0x80645ec: 0xf007 0xfb20  BL        A::subobject A() ; 0x806bc30
8 |    0x80645f0: 0x4807         LDR.N     R0, [PC, #0x20]         ; 0x8088808 (134776840)
9 |    0x80645f2: 0x6020         STR       R0, [R4]
10|    0x80645f4: 0xf104 0x0018  ADD.W     R0, R4, #24             ; 0x18
11|    0x80645f8: 0xf00a 0xfadd  BL        C::C()              ; 0x806ebb6
12|    0x80645fc: 0xf104 0x001c  ADD.W     R0, R4, #28             ; 0x1c
13|    0x8064600: 0xf00e 0xff2e  BL        std::vector<D *>::vector() ; 0x8073460
14|    0x8064604: 0x7820         LDRB      R0, [R4, #0x2c]
15|    0x8064606: 0xf000 0x00ef  AND.W     R0, R0, #239            ; 0xef
16|    0x806460a: 0x7020         STRB      R0, [R4, #0x2c]
17|    B(){ invert = false; };
18|    0x806460c: 0x4620         MOV       R0, R4
19|    0x806460e: 0xbd10         POP       {R4, PC}
20|    0x8064610: 0x08088808     DC32      0x8088808 (134776840)

Side note, there is a slight change on line 8, but that's probably due to some offsets changes. 旁注,第8行略有变化,但这可能是由于一些偏移量的变化。

Does anyone have any insight as to what could be causing this? 是否有人对导致这种情况的原因有任何见解?

This is a compiler bug and according to my investigations it triggers in at least EWARM 7.80.1 and 8.11.2. 这是一个编译器错误,根据我的调查,它至少在EWARM 7.80.1和8.11.2中触发。 It does not trigger in EWARM 8.20.1. 它不会在EWARM 8.20.1中触发。 The bug triggers on all optimization levels and I can't think of another work-around than the one mentioned in the question. 该错误会在所有优化级别上触发,除了问题中提到的解决方法,我无法想到其他解决方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM