2

My problem is about the constructor/destructor execution optimization: there is a significant difference of the execution order between PC and AVR (8-bit) platforms.

I created a small source to demonstrate it. The important part is the class C which is instantiated within the operator<<().

Let's see the PC version first:

(The class F here is only to see the float operations in the output)

#include <iostream>
struct C
{
    C()
    {
        std::cout << "C created" << std::endl;
    }
    ~C()
    {
        std::cout << "C deleted" << std::endl;
    }
};
struct D
{
    D & operator<<(float f)
    {
        C _c;
        std::cout << "D << " << f << std::endl;
        value = f;
        return *this;
    }
    float value;
};
struct F
{
    F(float f):
        value(f)
    {
        std::cout << "F created: " << value << std::endl;
    }
    operator float()
    {
        return value;
    }
    F operator+(float f)
    {
        std::cout << value << "+" << f << "=" << value+f << std::endl;
        return F(value+f);
    }
    float value;
};

F f1(3.5f);
F f2(4.2f);
D d;

int main()
{
    std::cout << "main() started" << std::endl;
    d << (f1+f2);
}

I compiled it for my PC (this source is in main.cpp):

g++ -o main -O4 -Wall main.cpp

The execution result is here:

$ ./main 
F created: 3.5
F created: 4.2
main() started
3.5+4.2=7.7
F created: 7.7
C created
D << 7.7
C deleted

The execution order is correct here, everything is as expected.

To compile it for AVR (8-bit family) the std::cout calls removed, and the class C is slightly modified:

struct C
{
    C()
    {
        asm("cli");
    }
    ~C()
    {
        asm("sei");
    }
};
struct D
{
    D & operator<<(float f)
    {
        C _c;
        value = f;
        return *this;
    }
    float value;
};
float f1(3.5f);
float f2(4.2f);
D d;

int main()
{
    d << (f1+f2);
}

It is compiled for AVR (8-bit family):

avr-g++ -o main -O4 -Wall main.cpp

Let's see the result of the main function:

00000028 <main>:
  28:   f8 94           cli
  2a:   20 91 68 00     lds r18, 0x0068
  2e:   30 91 69 00     lds r19, 0x0069
  32:   40 91 6a 00     lds r20, 0x006A
  36:   50 91 6b 00     lds r21, 0x006B
  3a:   60 91 64 00     lds r22, 0x0064
  3e:   70 91 65 00     lds r23, 0x0065
  42:   80 91 66 00     lds r24, 0x0066
  46:   90 91 67 00     lds r25, 0x0067
  4a:   2e d0           rcall   .+92        ; 0xa8 <__addsf3>
  4c:   60 93 60 00     sts 0x0060, r22
  50:   70 93 61 00     sts 0x0061, r23
  54:   80 93 62 00     sts 0x0062, r24
  58:   90 93 63 00     sts 0x0063, r25
  5c:   78 94           sei
  5e:   80 e0           ldi r24, 0x00   ; 0
  60:   90 e0           ldi r25, 0x00   ; 0
  62:   08 95           ret

Every operations are executed between 'cli' and 'sei' here, which means the class C is instantiated earlier than expected. I expected the addition to be executed first, and only the assignment is placed between 'cli' and 'sei'. Somehow the whole expression is optimized into the operator<<() function.

I tried to create temporary variables and other tricks but cannot get the expected behavior. Unfortunately I do not know the C++ standard deep enough, so I cannot decide if it is a compiler bug or such an execution order change is allowed by the standard. At least I do not understand the difference between platforms.

Please help me how can I get the expected execution order in AVR platform!

I would expect something like this:

0000002a <main>:
  2a:   20 91 68 00     lds r18, 0x0068
  2e:   30 91 69 00     lds r19, 0x0069
  32:   40 91 6a 00     lds r20, 0x006A
  36:   50 91 6b 00     lds r21, 0x006B
  3a:   60 91 64 00     lds r22, 0x0064
  3e:   70 91 65 00     lds r23, 0x0065
  42:   80 91 66 00     lds r24, 0x0066
  46:   90 91 67 00     lds r25, 0x0067
  4a:   2e d0           rcall   .+92        ; 0xa8 <__addsf3>
  4c:   f8 94           cli
  4e:   60 93 60 00     sts 0x0060, r22
  52:   70 93 61 00     sts 0x0061, r23
  56:   80 93 62 00     sts 0x0062, r24
  5a:   90 93 63 00     sts 0x0063, r25
  5e:   78 94           sei
  60:   80 e0           ldi r24, 0x00   ; 0
  62:   90 e0           ldi r25, 0x00   ; 0
  64:   08 95           ret

I tried g++ versions 4.9.2 and 5.3.0 with the same result.

Thanx in advance,

Gyorgy Kovesdi

Additions to the first comments: The global objects' order is not important, only the class C, which is local to a function, and only its constructor against the operations like f1+f2 in this example. The asm instruction is also not relevant, the constructor can have any content, it is just an example.

user2849906
  • 131
  • 1
  • 1
  • 4
  • I don't know what compiler you are using but this answer seems to indicate that you might want "asm volatile" to prevent the compiler from reordering the asm statements. http://stackoverflow.com/questions/14449141/the-difference-between-asm-asm-volatile-and-clobbering-memory – jcoder Nov 02 '16 at 08:44
  • Are global constructors calling order defined by the language? I always assumed this was compiler dependant. What happens when we link multiple object files together? In what order should these be called? – PaulHK Nov 02 '16 at 10:00
  • @PaulHK what's relevant here is the constructor/destructor of C which is not instantiated globally only locally inside `operator<<`. Also, yes global static variables are initialized from top to bottom inside one translation unit. The order in which translations units are initialized is unspecified – PeterT Nov 02 '16 at 10:39
  • "if it is a compiler bug or such an execution order change is allowed by the standard." - Certainly not a bug. Why should the standard prohibit any re-ordering that is not visible to the program? In fact, in this case, none of the operations carried out have a result which is used later in the program; so the whole avr code could be optimized away. – JimmyB Nov 07 '16 at 11:28

0 Answers0