简体   繁体   中英

C vs C++ Static Initialization of Objects

I have a question regarding initialization of fairly large sets of static data.

See my three examples below of initializing sets of static data. I'd like to understand the program load time & memory footprint implications of the methods shown below. I really don't know how to evaluate that on my own at the moment. My build environment is still on the desktop using Visual Studio, however the embedded targets will be compiled for VxWorks using GCC.

Traditionally, I've used basic C-structs for this sort of thing, although there's good reason to move this data into C++ classes moving forward. Dynamic memory allocation is frowned upon in the embedded application and is avoided wherever possible.

As far as I know, the only way to initialize a C++ class is through its constructor, shown below in Method 2. I am wondering how that compares to Method 1. Is there any appreciable additional overhead in terms of ROM (Program footprint), RAM (Memory Footprint), or program load time? It seems to me that the compiler may be able to optimize away the rather trivial constructor, but I'm not sure if that's common behavior.

I listed Method 3, as I've considered it, although it just seems like a bad idea. Is there something else that I'm missing here? Anyone else out there initialize data in a similar manner?

/* C-Style Struct Storage */
typedef struct{
    int a;
    int b;
}DATA_C;

/* CPP Style Class Storage */
class DATA_CPP{
public:
    int a;
    int b;
    DATA_CPP(int,int);
};
DATA_CPP::DATA_CPP(int aIn, int bIn){
    a = aIn;
    b = bIn;
}

/* METHOD 1: Direct C-Style Static Initialization */
DATA_C MyCData[5] = { {1,2},
                      {3,4},
                      {5,6},
                      {7,8},
                      {9,10}
                    };

/* METHOD 2: Direct CPP-Style Initialization */ 
DATA_CPP MyCppData[5] = { DATA_CPP(1,2),
                          DATA_CPP(3,4),
                          DATA_CPP(5,6),
                          DATA_CPP(7,8),
                          DATA_CPP(9,10),
                         };

/* METHOD 3: Cast C-Struct to CPP class */
DATA_CPP* pMyCppData2 = (DATA_CPP*) MyCData;

In C++11 , you can write this:

DATA_CPP obj = {1,2}; //Or simply : DATA_CPP obj {1,2}; i.e omit '='

instead of

DATA_CPP obj(1,2);

Extending this, you can write:

DATA_CPP MyCppData[5] = { {1,2},
                          {3,4},
                          {5,6},
                          {7,8},
                          {9,10},
                         };

instead of this:

DATA_CPP MyCppData[5] = { DATA_CPP(1,2),
                          DATA_CPP(3,4),
                          DATA_CPP(5,6),
                          DATA_CPP(7,8),
                          DATA_CPP(9,10),
                         };

Read about uniform initialization.

I've done a bit of research in this, thought I'd post the results. I used Visual Studio 2008 in all cases here.

Here's the disassembly view of the code from Visual Studio in Debug Mode:

/* METHOD 1: Direct C-Style Static Initialization */
DATA_C MyCData[5] = { {1,2},
                      {3,4},
                      {5,6},
                      {7,8},
                      {9,10},
                    };

/* METHOD 2: Direct CPP-Style Initialization */ 
DATA_CPP MyCppData[5] = { DATA_CPP(1,2),
010345C0  push        ebp  
010345C1  mov         ebp,esp 
010345C3  sub         esp,0C0h 
010345C9  push        ebx  
010345CA  push        esi  
010345CB  push        edi  
010345CC  lea         edi,[ebp-0C0h] 
010345D2  mov         ecx,30h 
010345D7  mov         eax,0CCCCCCCCh 
010345DC  rep stos    dword ptr es:[edi] 
010345DE  push        2    
010345E0  push        1    
010345E2  mov         ecx,offset MyCppData (1038184h) 
010345E7  call        DATA_CPP::DATA_CPP (103119Ah) 
                          DATA_CPP(3,4),
010345EC  push        4    
010345EE  push        3    
010345F0  mov         ecx,offset MyCppData+8 (103818Ch) 
010345F5  call        DATA_CPP::DATA_CPP (103119Ah) 
                          DATA_CPP(5,6),
010345FA  push        6    
010345FC  push        5    
010345FE  mov         ecx,offset MyCppData+10h (1038194h) 
01034603  call        DATA_CPP::DATA_CPP (103119Ah) 
                          DATA_CPP(7,8),
01034608  push        8    
0103460A  push        7    
0103460C  mov         ecx,offset MyCppData+18h (103819Ch) 
01034611  call        DATA_CPP::DATA_CPP (103119Ah) 
                          DATA_CPP(9,10),
01034616  push        0Ah  
01034618  push        9    
0103461A  mov         ecx,offset MyCppData+20h (10381A4h) 
0103461F  call        DATA_CPP::DATA_CPP (103119Ah) 
                         };
01034624  pop         edi  
01034625  pop         esi  
01034626  pop         ebx  
01034627  add         esp,0C0h 
0103462D  cmp         ebp,esp 
0103462F  call        @ILT+325(__RTC_CheckEsp) (103114Ah) 
01034634  mov         esp,ebp 
01034636  pop         ebp  

Interesting thing to note here is that there is definitely some overhead in program memory usage and load time, at least in non-optimized debug mode. Notice that Method 1 has zero assembly instructions, while method two has about 44 instructions.

I also ran compiled the program in Release mode with optimization enabled, here is the abridged assembly output:

?MyCData@@3PAUDATA_C@@A DD 01H              ; MyCData
    DD  02H
    DD  03H
    DD  04H
    DD  05H
    DD  06H
    DD  07H
    DD  08H
    DD  09H
    DD  0aH

?MyCppData@@3PAVDATA_CPP@@A DD 01H          ; MyCppData
    DD  02H
    DD  03H
    DD  04H
    DD  05H
    DD  06H
    DD  07H
    DD  08H
    DD  09H
    DD  0aH
END

Seems like the compiler indeed optimized away the calls to the C++ constructor. I could find no evidence of the constructor ever being called anywhere in the assembly code.

I thought I'd try something a bit more. I changed the constructor to:

DATA_CPP::DATA_CPP(int aIn, int bIn){
    a = aIn + bIn;
    b = bIn;
}

Again, the compiler optimized this away, resulting in a static dataset:

?MyCppData@@3PAVDATA_CPP@@A DD 03H          ; MyCppData
    DD  02H
    DD  07H
    DD  04H
    DD  0bH
    DD  06H
    DD  0fH
    DD  08H
    DD  013H
    DD  0aH
END

Interesting, the compiler was able to evaluate the constructor code on all the static data during compilation and create a static dataset, still not calling the constructor.

I thought I'd try something still a bit more, operate on a global variable in the constructor:

int globalvar;
DATA_CPP::DATA_CPP(int aIn, int bIn){
    a = aIn + globalvar;
    globalvar += a;
    b = bIn;
}

And in this case, the compiler now generated assembly code to call the constructor during initialization:

    ??__EMyCppData@@YAXXZ PROC              ; `dynamic initializer for 'MyCppData'', COMDAT

; 35   : DATA_CPP MyCppData[5] = { DATA_CPP(1,2),

  00000 a1 00 00 00 00   mov     eax, DWORD PTR ?globalvar@@3HA ; globalvar
  00005 8d 48 01     lea     ecx, DWORD PTR [eax+1]
  00008 03 c1        add     eax, ecx
  0000a 89 0d 00 00 00
    00       mov     DWORD PTR ?MyCppData@@3PAVDATA_CPP@@A, ecx

; 36   :                           DATA_CPP(3,4),

  00010 8d 48 03     lea     ecx, DWORD PTR [eax+3]
  00013 03 c1        add     eax, ecx
  00015 89 0d 08 00 00
    00       mov     DWORD PTR ?MyCppData@@3PAVDATA_CPP@@A+8, ecx

; 37   :                           DATA_CPP(5,6),

  0001b 8d 48 05     lea     ecx, DWORD PTR [eax+5]
  0001e 03 c1        add     eax, ecx
  00020 89 0d 10 00 00
    00       mov     DWORD PTR ?MyCppData@@3PAVDATA_CPP@@A+16, ecx

; 38   :                           DATA_CPP(7,8),

  00026 8d 48 07     lea     ecx, DWORD PTR [eax+7]
  00029 03 c1        add     eax, ecx
  0002b 89 0d 18 00 00
    00       mov     DWORD PTR ?MyCppData@@3PAVDATA_CPP@@A+24, ecx

; 39   :                           DATA_CPP(9,10),

  00031 8d 48 09     lea     ecx, DWORD PTR [eax+9]
  00034 03 c1        add     eax, ecx
  00036 89 0d 20 00 00
    00       mov     DWORD PTR ?MyCppData@@3PAVDATA_CPP@@A+32, ecx
  0003c a3 00 00 00 00   mov     DWORD PTR ?globalvar@@3HA, eax ; globalvar

; 40   :                          };

  00041 c3       ret     0
??__EMyCppData@@YAXXZ ENDP              ; `dynamic initializer for 'MyCppData''

FYI, I found this page helpful in setting up visual studio to output assembly: How do I get the assembler output from a C file in VS2005

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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