1

So I have the following code:

int main(int, char **) {
    auto wptr = new Fl_Double_Window{640,480};
    wptr->show();
    Fl::run();
    return 0;
}
> valgrind --leak-check=full -s ./run
==13685== Memcheck, a memory error detector
==13685== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13685== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==13685== Command: ./run
==13685==
==13685==
==13685== HEAP SUMMARY:
==13685==     in use at exit: 741,252 bytes in 1,107 blocks
==13685==   total heap usage: 14,853 allocs, 13,746 frees, 2,829,612 bytes allocated
==13685==
==13685== LEAK SUMMARY:
==13685==    definitely lost: 0 bytes in 0 blocks
==13685==    indirectly lost: 0 bytes in 0 blocks
==13685==      possibly lost: 0 bytes in 0 blocks
==13685==    still reachable: 741,252 bytes in 1,107 blocks
==13685==         suppressed: 0 bytes in 0 blocks
==13685== Reachable blocks (those to which a pointer was found) are not shown.
==13685== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==13685==
==13685== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)
--13685--
--13685-- used_suppression:      1 X on SUSE11 writev uninit padding /usr/lib/valgrind/default.supp:377
==13685==
==13685== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

Notice I did NOT delete the pointer to the window and valgrind has not detected any leaks.
I have searched the docs (https://www.fltk.org/doc-1.3/basics.html), as well as the FAQ pages (https://www.fltk.org/doc-1.3/FAQ.html, https://www.fltk.org/articles.php), and I've found that the most recent grouping widget automatically attaches newly created widgets, but I've found nothing about how memory is managed by fltk, as in, whether there is some "garbage collector", or if I have to delete the pointer manually.
Normally this output would imply that there is some hidden mechanism that is responsible for freeing memory as I have not manually freed the memory allocated for the window and there seems to be no leak, but take a look at the following modification to the previous code:

int main(int, char **) {
    auto wptr = new Fl_Double_Window{640,480};
    auto box = new Fl_Box{0,0,100,100,"Hello!"}; // Adding widget
    wptr->show();
    Fl::run();
    return 0;
}
valgrind --leak-check=full -s ./run
==14005== Memcheck, a memory error detector
==14005== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14005== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==14005== Command: ./run
==14005==
==14005==
==14005== HEAP SUMMARY:
==14005==     in use at exit: 1,101,954 bytes in 11,185 blocks
==14005==   total heap usage: 31,631 allocs, 20,446 frees, 7,013,742 bytes allocated
==14005==
==14005== 384 (256 direct, 128 indirect) bytes in 1 blocks are definitely lost in loss record 368 of 540
==14005==    at 0x483E7C5: malloc (vg_replace_malloc.c:380)
==14005==    by 0x4EC8255: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4ECC200: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4D433BF: ??? (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4D438F5: ??? (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4D43C96: ??? (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4D44080: XftDefaultHasRender (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4D44558: XftDefaultSubstitute (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4D46FD3: XftFontMatch (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4B30739: Fl_Font_Descriptor::Fl_Font_Descriptor(char const*, int, int) (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4B309F2: ??? (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4B337BB: fl_normal_label(Fl_Label const*, int, int, int, int, unsigned int) (in /usr/lib/libfltk.so.1.3.7)
==14005==
==14005== 2,565 (768 direct, 1,797 indirect) bytes in 1 blocks are definitely lost in loss record 482 of 540
==14005==    at 0x484383F: realloc (vg_replace_malloc.c:1192)
==14005==    by 0x4EC830E: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4ED94DA: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4ED156C: FcFontRenderPrepare (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4ED1B9D: FcFontMatch (in /usr/lib/libfontconfig.so.1.12.0)
==14005==    by 0x4D46FEF: XftFontMatch (in /usr/lib/libXft.so.2.3.4)
==14005==    by 0x4B30739: Fl_Font_Descriptor::Fl_Font_Descriptor(char const*, int, int) (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4B309F2: ??? (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4B337BB: fl_normal_label(Fl_Label const*, int, int, int, int, unsigned int) (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4B3390A: Fl_Widget::draw_label(int, int, int, int, unsigned int) const (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4AD8D70: Fl_Group::draw_child(Fl_Widget&) const (in /usr/lib/libfltk.so.1.3.7)
==14005==    by 0x4AD8F92: Fl_Group::draw_children() (in /usr/lib/libfltk.so.1.3.7)
==14005==
==14005== LEAK SUMMARY:
==14005==    definitely lost: 1,024 bytes in 2 blocks
==14005==    indirectly lost: 1,925 bytes in 69 blocks
==14005==      possibly lost: 0 bytes in 0 blocks
==14005==    still reachable: 1,099,005 bytes in 11,114 blocks
==14005==         suppressed: 0 bytes in 0 blocks
==14005== Reachable blocks (those to which a pointer was found) are not shown.
==14005== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==14005==
==14005== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 1 from 1)
--14005--
--14005-- used_suppression:      1 X on SUSE11 writev uninit padding /usr/lib/valgrind/default.supp:377
==14005==
==14005== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 1 from 1)

Now it seems that there is a memory leak. It appears that the font library is causing this.
I will again modify the code to delete the pointers to the widgets:

int main(int, char **) {
    auto wptr = new Fl_Double_Window{640,480};
    auto box = new Fl_Box{0,0,100,100,"Hello!"};
    wptr->show();
    Fl::run();
    delete wptr;
    delete box;
    return 0;
}
> valgrind --leak-check=full ./run
==14238== Memcheck, a memory error detector
==14238== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14238== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==14238== Command: ./run
==14238==
==14238== Invalid read of size 8
==14238==    at 0x1091CA: main (main.cpp:64)
==14238==  Address 0x58a8730 is 0 bytes inside a block of size 120 free'd
==14238==    at 0x48419AB: operator delete(void*, unsigned long) (vg_replace_malloc.c:814)
==14238==    by 0x4AD862D: Fl_Group::clear() (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4AD86E7: Fl_Group::~Fl_Group() (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4AD1889: Fl_Double_Window::~Fl_Double_Window() (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x1091C0: main (main.cpp:63)
==14238==  Block was alloc'd at
==14238==    at 0x483EF3F: operator new(unsigned long) (vg_replace_malloc.c:417)
==14238==    by 0x10915F: main (main.cpp:60)
==14238==
==14238== Jump to the invalid address stated on the next line
==14238==    at 0x0: ???
==14238==    by 0x51D4B24: (below main) (in /usr/lib/libc-2.33.so)
==14238==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==14238==
==14238==
==14238== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==14238==  Bad permissions for mapped region at address 0x0
==14238==    at 0x0: ???
==14238==    by 0x51D4B24: (below main) (in /usr/lib/libc-2.33.so)
==14238==
==14238== HEAP SUMMARY:
==14238==     in use at exit: 1,156,045 bytes in 11,490 blocks
==14238==   total heap usage: 31,586 allocs, 20,096 frees, 7,058,062 bytes allocated
==14238==
==14238== 80 bytes in 1 blocks are possibly lost in loss record 277 of 558
==14238==    at 0x483E7C5: malloc (vg_replace_malloc.c:380)
==14238==    by 0x53E6D37: ??? (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x53E73EE: __glDispatchInit (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x54600AA: ??? (in /usr/lib/libGLX.so.0.0.0)
==14238==    by 0x400FE2D: call_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x400FF1B: _dl_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x40010C9: ??? (in /usr/lib/ld-2.33.so)
==14238==
==14238== 80 bytes in 1 blocks are possibly lost in loss record 278 of 558
==14238==    at 0x483E7C5: malloc (vg_replace_malloc.c:380)
==14238==    by 0x53E6D37: ??? (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x53E75D3: __glDispatchRegisterStubCallbacks (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x538E076: ??? (in /usr/lib/libOpenGL.so.0.0.0)
==14238==    by 0x400FE2D: call_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x400FF1B: _dl_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x40010C9: ??? (in /usr/lib/ld-2.33.so)
==14238==
==14238== 80 bytes in 1 blocks are possibly lost in loss record 279 of 558
==14238==    at 0x483E7C5: malloc (vg_replace_malloc.c:380)
==14238==    by 0x53E6D37: ??? (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x53E75D3: __glDispatchRegisterStubCallbacks (in /usr/lib/libGLdispatch.so.0.0.0)
==14238==    by 0x4A22076: ??? (in /usr/lib/libGL.so.1.7.0)
==14238==    by 0x400FE2D: call_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x400FF1B: _dl_init (in /usr/lib/ld-2.33.so)
==14238==    by 0x40010C9: ??? (in /usr/lib/ld-2.33.so)
==14238==
==14238== 384 (256 direct, 128 indirect) bytes in 1 blocks are definitely lost in loss record 380 of 558
==14238==    at 0x483E7C5: malloc (vg_replace_malloc.c:380)
==14238==    by 0x4EC8255: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4ECC200: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4D433BF: ??? (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4D438F5: ??? (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4D43C96: ??? (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4D44080: XftDefaultHasRender (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4D44558: XftDefaultSubstitute (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4D46FD3: XftFontMatch (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4B30739: Fl_Font_Descriptor::Fl_Font_Descriptor(char const*, int, int) (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4B309F2: ??? (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4B337BB: fl_normal_label(Fl_Label const*, int, int, int, int, unsigned int) (in /usr/lib/libfltk.so.1.3.7)
==14238==
==14238== 2,565 (768 direct, 1,797 indirect) bytes in 1 blocks are definitely lost in loss record 498 of 558
==14238==    at 0x484383F: realloc (vg_replace_malloc.c:1192)
==14238==    by 0x4EC830E: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4ED94DA: ??? (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4ED156C: FcFontRenderPrepare (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4ED1B9D: FcFontMatch (in /usr/lib/libfontconfig.so.1.12.0)
==14238==    by 0x4D46FEF: XftFontMatch (in /usr/lib/libXft.so.2.3.4)
==14238==    by 0x4B30739: Fl_Font_Descriptor::Fl_Font_Descriptor(char const*, int, int) (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4B309F2: ??? (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4B337BB: fl_normal_label(Fl_Label const*, int, int, int, int, unsigned int) (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4B3390A: Fl_Widget::draw_label(int, int, int, int, unsigned int) const (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4AD8D70: Fl_Group::draw_child(Fl_Widget&) const (in /usr/lib/libfltk.so.1.3.7)
==14238==    by 0x4AD8F92: Fl_Group::draw_children() (in /usr/lib/libfltk.so.1.3.7)
==14238==
==14238== LEAK SUMMARY:
==14238==    definitely lost: 1,024 bytes in 2 blocks
==14238==    indirectly lost: 1,925 bytes in 69 blocks
==14238==      possibly lost: 240 bytes in 3 blocks
==14238==    still reachable: 1,152,856 bytes in 11,416 blocks
==14238==         suppressed: 0 bytes in 0 blocks
==14238== Reachable blocks (those to which a pointer was found) are not shown.
==14238== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==14238==
==14238== For lists of detected and suppressed errors, rerun with: -s
==14238== ERROR SUMMARY: 7 errors from 7 contexts (suppressed: 1 from 1)
Segmentation fault (core dumped)

To say the least this modification only worsened the condition XD.
In fact, (If I'm not mistaken) the "bad permissions for mapped region..." hints that an attempt has been made to free memory twice, which would suggest that there is indeed some memory management mechanism in fltk.
But then I found this: How to fix memory leaks in simplest FLTK programm? Which implies that I should delete the pointers so right now I feel somewhat confused :)
My best guess is that:
(1) the font library is malfunctioning and
(2) FLTK has some internal garbage collector (so I shouldn't delete widget pointers manually so as not to cause any errors), can anyone confirm?

Sam
  • 19
  • 3

4 Answers4

2

This is 2/3 of the answer. I'll have to try out FLTK on a Linux environment to work out the other 1/3.

FLTK will delete the child widgets when a window is deleted. If you look in the destructor of FL_DoubleWindow, it reads

/**
  The destructor <I>also deletes all the children</I>. This allows a
  whole tree to be deleted at once, without having to keep a pointer to
  all the children in the user code.
*/
Fl_Double_Window::~Fl_Double_Window() {
  hide();
}

I can't explain the second case but in the third case, the box is a child of the window and gets deleted when the window gets deleted. So when the code deletes box, it is deleting a deleted item, hence the error.

cup
  • 7,589
  • 4
  • 19
  • 42
  • Yes, I've managed to get to the part where the Fl_Group's destructor deletes the subwidget pointers if the Fl_Group is indeed their parent, if for some reason this is not the case then in the remove(int) function located in the clear() function located in the destructor of the Fl_Group class, the array_ data member is simply reallocated. – Sam Aug 16 '21 at 20:05
  • But have you managed to find out where the outermost widgets get deleted? In this case the Fl_Double_Window. I'm really curious as to how this is implemented cause regardless of whether the window is allocated on the stack, or heap (not deleting the pointer), there's no memory leak. What I've manged to track down is that the Fl::run function has a ```while (Fl_X::first) wait(FOREVER)``` loop. And I'm trying to find the "Window Manager" (https://www.fltk.org/doc-1.3/classFl__Window.html#details) cause I'm guessing that's where the magic happens. Something has to store these Fl_X objects, right? – Sam Aug 16 '21 at 20:27
  • I just tried recompiling all three situations (with the -O0 -g ops) and running valgrind: creating an Fl_Double_Window on the stack (leak free), creating an Fl_Double_Window on the heap and not deleting it (leak free), and finally, creating an Fl_Double_Window on the heap and deleting it (leak free). In all three cases I also invoked the show() method on the window. Is it possible to check the type of allocation at runtime of an object whose address is stored in a pointer??? – Sam Aug 16 '21 at 21:14
1

Valgrind logs clearly show that pointers allocated by Fl_Double_Window and new Fl_Box are not leaked, so it would not be reasonable to try and delete them manually.

The font library has a "leak"; is designed to work this way. Whether it is a good design or not is debatable, but this is the design. When it loads a font, it leaves it there until the application finishes. The application can use any font at any moment. There is no good place to free fonts, other than at application exit, and application exit clears everything up anyway. An application is unlikely to use a billion different fonts (more likely 3 to 10) so there is no risk of filling the memory with unused fonts. If your application does use a billion different fonts, you better find a way to cache them reasonably according to your application needs; a simple font library is unlikely to do a good job of it.

Exhibit A.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • "Unlikely" is a lame excuse for allowing a library to leak memory ;D I understand that when you say "...and application exit clears everything up anyway." you mean frees the allocated memory. Well clearly it doesn't, or at least not according to valgrind. – Sam Aug 16 '21 at 20:02
  • @Sam Application exit always clears everything up. This is how operating systems generally work. Valgrind shows you what is not cleared right before application exit. If you think this is a bug, you can try and come up with a way to fix it. – n. m. could be an AI Aug 17 '21 at 03:47
1

I just tried recompiling all three situations (with the -O0 -g ops) and running valgrind: creating an Fl_Double_Window on the stack (leak free), creating an Fl_Double_Window on the heap and not deleting it (leak free), and finally, creating an Fl_Double_Window on the heap and deleting it (leak free).

As someone else noted valgrind shows you the status right before the program exits. valgrind says among others "still reachable: 741,252 bytes in 1,107 blocks" in the OP's message and "Reachable blocks (those to which a pointer was found) are not shown" which means that in case 2 (on the heap) valgrind doesn't report a "leak" because there's still a pointer that points at the window.

Deleting the window (case 3) doesn't change much because it is done right before the program ends and it would release allocated memory anyway. The difference is only that using delete runs the destructors which means that other resources can be freed (buffered data can be written, files can be closed or deleted, etc.).

Whether something should be considered a leak depends on the behavior during runtime. If your program often allocates windows and widgets on the heap and doesn't delete them, then this constitutes a memory leak (but that's not FLTK's fault then). FLTK guarantees that all child widgets of e.g. a window are deleted when the parent window is deleted. That's all. There is no garbage collector running in the background.

0

But have you managed to find out where the outermost widgets get deleted? In this case the Fl_Double_Window.

FLTK does not delete such outermost widgets - usually windows - by itself. Such widgets are deleted when they go out of scope (if allocated on the stack) or by using operator delete (which is the programmer's responsibility).