2
votes

I am compiling a C++ program for a NIOS II core with very limited memory. Since it is an embedded system we are not using a heap either. Since we added inheritance to our code we saw that malloc and free (and related functions) was being build into our binary thus increasing its size a number of kilobytes.

We have one pure virtual base class, StatisticsApi and one derived class Statistics. Statistics and they both have virtual destructors defined.

We are compiling with Alteras GCC with fno-rtti and -fno_expections and are also defined our own __cxa_pure_virtual().

Looking closer into it we see that delete is called from the destructors (and thus free). Why is that? What memory is it trying to free?

Here is the assembler for the destructors:

0000c6b8 <_ZN7Namespace12StatisticsD0Ev>:
    c6b8:   defffd04    addi    sp,sp,-12
    c6bc:   dfc00215    stw ra,8(sp)
    c6c0:   df000115    stw fp,4(sp)
    c6c4:   df000104    addi    fp,sp,4
    c6c8:   e13fff15    stw r4,-4(fp)
    c6cc:   00c000b4    movhi   r3,2
    c6d0:   18c74b04    addi    r3,r3,7468
    c6d4:   e0bfff17    ldw r2,-4(fp)
    c6d8:   10c00015    stw r3,0(r2)
    c6dc:   e13fff17    ldw r4,-4(fp)
    c6e0:   000c7f00    call    c7f0 <_ZN7Namespace15StatisticsApiD2Ev>
    c6e4:   00800044    movi    r2,1
    c6e8:   10803fcc    andi    r2,r2,255
    c6ec:   1005003a    cmpeq   r2,r2,zero
    c6f0:   1000021e    bne r2,zero,c6fc <_ZN7LinCtrl12TxStatisticsD0Ev+0x44>
    c6f4:   e13fff17    ldw r4,-4(fp)
    c6f8:   000c8e00    call    c8e0 <_ZdlPv>
    c6fc:   e037883a    mov sp,fp
    c700:   dfc00117    ldw ra,4(sp)
    c704:   df000017    ldw fp,0(sp)
    c708:   dec00204    addi    sp,sp,8
    c70c:   f800283a    ret


0000c710 <_ZN7Namespace12StatisticsD1Ev>:
    c710:   defffd04    addi    sp,sp,-12
    c714:   dfc00215    stw ra,8(sp)
    c718:   df000115    stw fp,4(sp)
    c71c:   df000104    addi    fp,sp,4
    c720:   e13fff15    stw r4,-4(fp)
    c724:   00c000b4    movhi   r3,2
    c728:   18c74b04    addi    r3,r3,7468
    c72c:   e0bfff17    ldw r2,-4(fp)
    c730:   10c00015    stw r3,0(r2)
    c734:   e13fff17    ldw r4,-4(fp)
    c738:   000c7f00    call    c7f0 <_ZN7Namespace15StatisticsApiD2Ev>
    c73c:   0005883a    mov r2,zero
    c740:   10803fcc    andi    r2,r2,255
    c744:   1005003a    cmpeq   r2,r2,zero
    c748:   1000021e    bne r2,zero,c754 <_ZN7Namespace12StatisticsD1Ev+0x44>
    c74c:   e13fff17    ldw r4,-4(fp)
    c750:   000c8e00    call    c8e0 <_ZdlPv>
    c754:   e037883a    mov sp,fp
    c758:   dfc00117    ldw ra,4(sp)
    c75c:   df000017    ldw fp,0(sp)
    c760:   dec00204    addi    sp,sp,8
    c764:   f800283a    ret


0000c768 <_ZN7Namespace12StatisticsD2Ev>:
    c768:   defffd04    addi    sp,sp,-12
    c76c:   dfc00215    stw ra,8(sp)
    c770:   df000115    stw fp,4(sp)
    c774:   df000104    addi    fp,sp,4
    c778:   e13fff15    stw r4,-4(fp)
    c77c:   00c000b4    movhi   r3,2
    c780:   18c74b04    addi    r3,r3,7468
    c784:   e0bfff17    ldw r2,-4(fp)
    c788:   10c00015    stw r3,0(r2)
    c78c:   e13fff17    ldw r4,-4(fp)
    c790:   000c7f00    call    c7f0 <_ZN7Namespace15StatisticsApiD2Ev>
    c794:   0005883a    mov r2,zero
    c798:   10803fcc    andi    r2,r2,255
    c79c:   1005003a    cmpeq   r2,r2,zero
    c7a0:   1000021e    bne r2,zero,c7ac <_ZN7Namespace12StatisticsD2Ev+0x44>
    c7a4:   e13fff17    ldw r4,-4(fp)
    c7a8:   000c8e00    call    c8e0 <_ZdlPv>
    c7ac:   e037883a    mov sp,fp
    c7b0:   dfc00117    ldw ra,4(sp)
    c7b4:   df000017    ldw fp,0(sp)
    c7b8:   dec00204    addi    sp,sp,8
    c7bc:   f800283a    ret

And here are the destructors for the base class:

0000c7f0 <_ZN7Namespace15StatisticsApiD2Ev>:
    c7f0:   defffd04    addi    sp,sp,-12
    c7f4:   dfc00215    stw ra,8(sp)
    c7f8:   df000115    stw fp,4(sp)
    c7fc:   df000104    addi    fp,sp,4
    c800:   e13fff15    stw r4,-4(fp)
    c804:   00c000b4    movhi   r3,2 
    c808:   18c76204    addi    r3,r3,7560 
    c80c:   e0bfff17    ldw r2,-4(fp)
    c810:   10c00015    stw r3,0(r2)
    c814:   0005883a    mov r2,zero
    c818:   10803fcc    andi    r2,r2,255
    c81c:   1005003a    cmpeq   r2,r2,zero
    c820:   1000021e    bne r2,zero,c82c <_ZN7Namespace15StatisticsApiD2Ev+0x3c>
    c824:   e13fff17    ldw r4,-4(fp)
    c828:   000c8e00    call    c8e0 <_ZdlPv>
    c82c:   e037883a    mov sp,fp
    c830:   dfc00117    ldw ra,4(sp)
    c834:   df000017    ldw fp,0(sp)
    c838:   dec00204    addi    sp,sp,8
    c83c:   f800283a    ret


0000c840 <_ZN7LinCtrl15TxStatisticsApiD0Ev>:
    c840:   defffd04    addi    sp,sp,-12
    c844:   dfc00215    stw ra,8(sp)
    c848:   df000115    stw fp,4(sp)
    c84c:   df000104    addi    fp,sp,4
    c850:   e13fff15    stw r4,-4(fp)
    c854:   00c000b4    movhi   r3,2
    c858:   18c76204    addi    r3,r3,7560
    c85c:   e0bfff17    ldw r2,-4(fp)
    c860:   10c00015    stw r3,0(r2)
    c864:   00800044    movi    r2,1
    c868:   10803fcc    andi    r2,r2,255
    c86c:   1005003a    cmpeq   r2,r2,zero
    c870:   1000021e    bne r2,zero,c87c <_ZN7Namespace15StatisticsApiD0Ev+0x3c>
    c874:   e13fff17    ldw r4,-4(fp)
    c878:   000c8e00    call    c8e0 <_ZdlPv>
    c87c:   e037883a    mov sp,fp
    c880:   dfc00117    ldw ra,4(sp)
    c884:   df000017    ldw fp,0(sp)
    c888:   dec00204    addi    sp,sp,8
    c88c:   f800283a    ret


0000c890 <_ZN7Namespace15StatisticsApiD1Ev>:
    c890:   defffd04    addi    sp,sp,-12
    c894:   dfc00215    stw ra,8(sp)
    c898:   df000115    stw fp,4(sp)
    c89c:   df000104    addi    fp,sp,4
    c8a0:   e13fff15    stw r4,-4(fp)
    c8a4:   00c000b4    movhi   r3,2
    c8a8:   18c76204    addi    r3,r3,7560
    c8ac:   e0bfff17    ldw r2,-4(fp)
    c8b0:   10c00015    stw r3,0(r2)
    c8b4:   0005883a    mov r2,zero
    c8b8:   10803fcc    andi    r2,r2,255
    c8bc:   1005003a    cmpeq   r2,r2,zero
    c8c0:   1000021e    bne r2,zero,c8cc <_ZN7Namespace15StatisticsApiD1Ev+0x3c>
    c8c4:   e13fff17    ldw r4,-4(fp)
    c8c8:   000c8e00    call    c8e0 <_ZdlPv>
    c8cc:   e037883a    mov sp,fp
    c8d0:   dfc00117    ldw ra,4(sp)
    c8d4:   df000017    ldw fp,0(sp)
    c8d8:   dec00204    addi    sp,sp,8
    c8dc:   f800283a    ret

And finally delete:

0000c8e0 <_ZdlPv>:
    c8e0:   20000126    beq r4,zero,c8e8 <_ZdlPv+0x8>
    c8e4:   000ebf01    jmpi    ebf0 <free>
    c8e8:   f800283a    ret

And here is a link to the instruction set for NIOS II for reference: http://www.altera.com/literature/hb/nios2/n2cpu_nii51017.pdf

The two solutions/workarounds we found were:

  1. Redefining new/delete as empty functions (no heap so they shouldn't be called anyway!) thus avoiding the call to free.

  2. Leaving the destructors undefined in C++ which makes them not get instantiated in assembler either. Though the compiler will complain that there is no virtual destructor.

Workarounds or not, why are the destructors calling free? What memory is it trying to free!?! We have other destructors that are neither for base classes nor derived classes and they don't call free. This is how such a destructor looks like:

00005194 <_ZN7Namespace16OtherClassD2Ev>:
    5194:   defffe04    addi    sp,sp,-8
    5198:   df000115    stw fp,4(sp)
    519c:   df000104    addi    fp,sp,4
    51a0:   e13fff15    stw r4,-4(fp)
    51a4:   e037883a    mov sp,fp
    51a8:   df000017    ldw fp,0(sp)
    51ac:   dec00104    addi    sp,sp,4
    51b0:   f800283a    ret

000051b4 <_ZN7Namespace16OtherClassD1Ev>:
    51b4:   defffe04    addi    sp,sp,-8
    51b8:   df000115    stw fp,4(sp)
    51bc:   df000104    addi    fp,sp,4
    51c0:   e13fff15    stw r4,-4(fp)
    51c4:   e037883a    mov sp,fp
    51c8:   df000017    ldw fp,0(sp)
    51cc:   dec00104    addi    sp,sp,4
    51d0:   f800283a    ret

And also, in general, why are there multiple functions for each destructor (D0, D1 and D2)?

1
Do you optimize (gcc -O2)? Did you consider upgrading your GCC compiler (to 4.9)? And please show some of your classes!Basile Starynkevitch
You could also look at the intermediate gimple representation (gcc -O -fdump-tree-gimple or -fdump-tree-all) which produces many dump files.Basile Starynkevitch
BTW, once you understand the offending gimple, you could use MELT to detect them, with its findgimple mode.Basile Starynkevitch
I can't explain why it's calling the deleting destructor, but I can give you a link explaining the different destructors: stackoverflow.com/questions/6613870/… ...presumably the compiler somehow thinks it got allocated on the heap and needs to be freed.Mark B

1 Answers

1
votes

When you call delete on an object with a virtual destructor, it looks up the function to call in the virtual table. That function calls the destructor as well as freeing the memory, since only at the derived level does it know the proper pointer to pass to the free function.

The problem is that the compiler doesn't know that you are never calling delete. It still has to create the code even though it may never be used, just like any other virtual function.

Even though, as a general rule, destructors should be virtual in classes that have other virtual functions, this may be a valid exception. Making the destructor be protected in the base class will help avoid mistakes.