Speicherloch std::chrono::utc_clock?!



  • Hallo,

    ich bin gerade echt verwirrt. Durch Nutzung von der std::chrono::utc_clock entsteht laut dem VLD ein Speicherloch? Ich kann das gar nicht glauben!?

    #include <vld.h>
    
    #include <chrono>
    
    signed main()
    {
    	const auto& now = std::chrono::utc_clock::now();
    
    	return 0;
    }
    
    Visual Leak Detector read settings from: C:\Program Files (x86)\Visual Leak Detector\vld.ini
    Visual Leak Detector Version 2.5.1 installed.
    WARNING: Visual Leak Detector detected memory leaks!
    ---------- Block 279 at 0x01143770: 48 bytes ----------
      Leak Hash: 0x48253D63, Count: 1, Total 48 bytes
      Call Stack (TID 26232):
      Data:
        41 6D 65 72    69 63 61 2F    41 72 67 65    6E 74 69 6E     America/ Argentin
        61 2F 43 6F    6D 6F 64 52    69 76 61 64    61 76 69 61     a/ComodR ivadavia
        00 CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    
    
    ---------- Block 6 at 0x0114A530: 32 bytes ----------
      Leak Hash: 0x566895BC, Count: 1, Total 32 bytes
      Call Stack (TID 26232):
      Data:
        41 75 73 74    72 61 6C 69    61 2F 44 61    72 77 69 6E     Australi a/Darwin
        00 CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    
    
    ---------- Block 10 at 0x0114A5D0: 32 bytes ----------
      Leak Hash: 0x566895BC, Count: 1, Total 32 bytes
      Call Stack (TID 26232):
      Data:
        41 75 73 74    72 61 6C 69    61 2F 53 79    64 6E 65 79     Australi a/Sydney
        00 CD CD CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ........ ........
    
    
    ---------- Block 16 at 0x0114A620: 32 bytes ----------
      Leak Hash: 0x566895BC, Count: 1, Total 32 bytes
      Call Stack (TID 26232):
      Data:
        41 6D 65 72    69 63 61 2F    42 75 65 6E    6F 73 5F 41     America/ Buenos_A
        69 72 65 73    00 CD CD CD    CD CD CD CD    CD CD CD CD     ires.... ........
    
    
    ---------- Block 49 at 0x0114A670: 32 bytes ----------
      Leak Hash: 0x6B4D0BF9, Count: 1, Total 32 bytes
      Call Stack (TID 26232):
        ucrtbased.dll!malloc()
        D:\a\_work\1\s\src\vctools\crt\vcstartup\src\heap\new_scalar.cpp (35): leakTest.exe!operator new() + 0x9 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xmemory (79): leakTest.exe!std::_Default_allocate_traits::_Allocate() + 0x9 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xmemory (235): leakTest.exe!std::_Allocate<8,std::_Default_allocate_traits,0>() + 0x9 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xmemory (801): leakTest.exe!std::allocator<char>::allocate() + 0x12 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring (4481): leakTest.exe!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Reallocate_for<`std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign'::`2'::<lambda_1>,char const *>() + 0x18 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring (3227): leakTest.exe!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring (3198): leakTest.exe!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::assign<std::basic_string_view<char,std::char_traits<char> >,0>()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xstring (2716): leakTest.exe!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::basic_string<char,std::char_traits<char>,std::allocator<char> ><std::basic_string_view<char,std::char_traits<char> >,0>()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (1639): leakTest.exe!std::chrono::time_zone::time_zone() + 0x24 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xmemory (683): leakTest.exe!std::_Default_allocator_traits<std::allocator<std::chrono::time_zone> >::construct<std::chrono::time_zone,std::basic_string_view<char,std::char_traits<char> > const &>()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\vector (776): leakTest.exe!std::vector<std::chrono::time_zone,std::allocator<std::chrono::time_zone> >::_Emplace_reallocate<std::basic_string_view<char,std::char_traits<char> > const &>() + 0x42 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\vector (734): leakTest.exe!std::vector<std::chrono::time_zone,std::allocator<std::chrono::time_zone> >::emplace_back<std::basic_string_view<char,std::char_traits<char> > const &>() + 0x33 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (1991): leakTest.exe!std::chrono::_Tzdb_generate_time_zones()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (2089): leakTest.exe!std::chrono::tzdb_list::tzdb_list() + 0x9 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\xutility (137): leakTest.exe!std::construct_at<std::chrono::tzdb_list,void>()
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (2199): leakTest.exe!std::chrono::get_tzdb_list() + 0x9 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (2221): leakTest.exe!std::chrono::get_tzdb() + 0x5 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (2563): leakTest.exe!std::chrono::utc_clock::from_sys<std::chrono::duration<__int64,std::ratio<1,10000000> > >() + 0x5 bytes
        C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Tools\MSVC\14.29.30133\include\chrono (2538): leakTest.exe!std::chrono::utc_clock::now() + 0x19 bytes
        C:\Sources\leakTest\leakTest\main.cpp (7): leakTest.exe!main() + 0x9 bytes
        D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (78): leakTest.exe!invoke_main() + 0x2D bytes
        D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (288): leakTest.exe!__scrt_common_main_seh() + 0x5 bytes
        D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl (331): leakTest.exe!__scrt_common_main()
        D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_main.cpp (17): leakTest.exe!mainCRTStartup()
        KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes
        ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0x11E bytes
        ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xEE bytes
      Data:
        41 66 72 69    63 61 2F 41    64 64 69 73    5F 41 62 61     Africa/A ddis_Aba
        62 61 00 CD    CD CD CD CD    CD CD CD CD    CD CD CD CD     ba...... ........
    
    ...
    
    
    ---------- Block 3104 at 0x01191A38: 8 bytes ----------
      Leak Hash: 0xD63DB29A, Count: 1, Total 8 bytes
      Call Stack (TID 26232):
      Data:
        04 CF 17 01    00 00 00 00                                   ........ ........
    
    
    Visual Leak Detector detected 1065 memory leaks (85734 bytes).
    Largest number used: 112741 bytes.
    Total allocations: 251461 bytes.
    Visual Leak Detector is now exiting.
    

    Gibt es irgendeine Art shutDown(), was ich am Ende meines Programms aufrufen muss?

    VG Torsten



  • @TorDev sagte in Speicherloch std::chrono::utc_clock?!:

    const auto& now = std::chrono::utc_clock::now();

    Warum verwendest du hier eine Referenz auf den temporären Wert, den now() zurück gibt? Die Lebensdauer des Rückgabewertes beschränkt sich auf das Statement selber, d.h. deine Referenz ist dann direkt ungültig.

    now() gibt dir nicht ein Clock-Objekt zurück sondern einen Zeitwert ( irgendwas integerartiges ) mit kurzer Lebensdauer.



  • Hallo,

    spielt keine Rolle, auch ohne Referenz

    const auto now = std::chrono::utc_clock::now();
    

    entsteht das Speicherloch.

    VG Torsten



  • Ich vermute hier eher ein statisches Objekt dahinter, welches fälschlicherweise als Leak erkannt wird.



  • @It0101 sagte in Speicherloch std::chrono::utc_clock?!:

    Warum verwendest du hier eine Referenz auf den temporären Wert, den now() zurück gibt?

    Torsten verwendet eine konstante Referenz auf einen temporären Wert.

    Die Lebensdauer des Rückgabewertes beschränkt sich auf das Statement selber, d.h. deine Referenz ist dann direkt ungültig.

    Konstante Referenzen verlängern die Lebenszeit.
    Siehe https://en.cppreference.com/w/cpp/language/lifetime

    The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.



  • @TorDev

    signed main()
    

    Dir ist klar dass das kein Mensch so schreibt, oder?

    Aber OK, darum geht's ja nicht.

    VLD scheine wohl keine konfigurierbare "ignore" Liste zu haben. Doof. Was du aber machen kannst, ist bei Programmstart explizit std::chrono::utc_clock::now aufzurufen, eingeklammert in VLDDisable() und VLDEnable().



  • @wob sagte in Speicherloch std::chrono::utc_clock?!:

    @It0101 sagte in Speicherloch std::chrono::utc_clock?!:

    Warum verwendest du hier eine Referenz auf den temporären Wert, den now() zurück gibt?

    Torsten verwendet eine konstante Referenz auf einen temporären Wert.

    Die Lebensdauer des Rückgabewertes beschränkt sich auf das Statement selber, d.h. deine Referenz ist dann direkt ungültig.

    Konstante Referenzen verlängern die Lebenszeit.
    Siehe https://en.cppreference.com/w/cpp/language/lifetime

    The lifetime of a temporary object may be extended by binding to a const lvalue reference or to an rvalue reference (since C++11), see reference initialization for details.

    Okay, das war mir nicht bekannt. Ich halte das für falsch, weil es zu schlampigem Umgang mit Referenzen verführt, aber wenn der Standard es zulässt, dann ist das hinzunehmen 🙂
    Zumal es in dem Fall unnötig ist.



  • @It0101
    Ist aber ein ganz brauchbares Feature, finde ich, weil man sowas Problemlos machen kann:

    Something getSomething();
    void consumeSomething(const Something& something)
    
    //somewhere in the code
    
    consumeSomething(getSomething())
    
    

    Und der Standard garantiert das der Return Wert von getSomething() so lange lebt, wie es im Scope von consumeSomething ist.



  • @hustbaer sagte in Speicherloch std::chrono::utc_clock?!:

    @TorDev

    signed main()
    

    Dir ist klar dass das kein Mensch so schreibt, oder?

    Aber OK, darum geht's ja nicht.

    VLD scheine wohl keine konfigurierbare "ignore" Liste zu haben. Doof. Was du aber machen kannst, ist bei Programmstart explizit std::chrono::utc_clock::now aufzurufen, eingeklammert in VLDDisable() und VLDEnable().

    Hm... das wäre durchaus eine Option. Schön ist es dennoch nicht 😞 Vielleicht finde ich noch einen anderen Workaround, danke für eure Hilfe.

    VG Torsten



  • @Schlangenmensch sagte in Speicherloch std::chrono::utc_clock?!:

    @It0101
    Ist aber ein ganz brauchbares Feature, finde ich, weil man sowas Problemlos machen kann:

    Something getSomething();
    void consumeSomething(const Something& something)
    
    //somewhere in the code
    
    consumeSomething(getSomething())
    
    

    Und der Standard garantiert das der Return Wert von getSomething() so lange lebt, wie es im Scope von consumeSomething ist.

    Du hast recht. Dann habe ich das garantiert insgeheim auch schon verwendet. 🧐



  • @Schlangenmensch sagte in Speicherloch std::chrono::utc_clock?!:

    @It0101
    Ist aber ein ganz brauchbares Feature, finde ich, weil man sowas Problemlos machen kann:

    Something getSomething();
    void consumeSomething(const Something& something)
    
    //somewhere in the code
    
    consumeSomething(getSomething())
    
    

    Und der Standard garantiert das der Return Wert von getSomething() so lange lebt, wie es im Scope von consumeSomething ist.

    Die Garantie hast du in deinem Beispiel sowieso, das hat mit der "life extension" durch Binden einer const-ref mMn. nixe zu tun. Temporäre Objekte werden vereinfacht gesagt erst mit dem nächsten ; zerstört. Beispiel:

    #include <vector>
    #include <iostream>
    
    struct TempThing {
        ~TempThing() {
            std::cout << "~TempThing: " << s << std::endl;
        }
    
        std::string* getStringPtr() {
            return &s;
        }
    
        std::string s;
    };
    
    void setString(std::string* s, char const* cstr) {
        *s = cstr;
    }
    
    std::string makeString() {
        return "Look ma, no const-ref!";
    }
    
    TempThing makeTempThing() {
        return {};
    }
    
    int main() {
        setString(
            makeTempThing() /* <- 1 */ .getStringPtr(),
            makeString() /* <- 2 */ .c_str());
    }
    

    Ausgabe: ~TempThing: Look ma, no const-ref!

    Keine einzige cons-ref weit und breit (*), aber trotzdem garantiert dass hier alles wie gewünscht funktioniert. Die Returnwerte von (1) und (2) werden garantiert erst zerstört nachdem setString zurückgekehrt ist.



  • @hustbaer Aber:

    void useTempThing(TempThing& thing)
    {
      std::cout << thing.s << std::endl;
    }
    
    int main() {
      useTempThing(makeTempThing());
    }
    

    kompiliert nicht da man keine non const ref an einen L-Value binden kann, während

    void useTempThing(const TempThing& thing)
    {
      std::cout << thing.s << std::endl;
    }
    
    int main() {
      useTempThing(makeTempThing());
    }
    

    funktioniert.
    Ich hatte irgendwie im Kopf, dass das mit der "life extension" zusammen hängt.

    Was aber machen kann, ist über einen const ref parameter einen const ref member setzen. Das wäre vielleicht als Beispiel schöner gewesen. Damit wird dann die Life Time über das nächste Semikolon hinaus verlängert 😉 (siehe Erklärung von @hustbaer unter dem Post)



  • @Schlangenmensch sagte in Speicherloch std::chrono::utc_clock?!:

    Was aber machen kann, ist über einen const ref parameter einen const ref member setzen. Das wäre vielleicht als Beispiel schöner gewesen. Damit wird dann die Life Time über das nächste Semikolon hinaus verlängert 😉

    Nein, das geht nicht. Weil die Referenz um deren Lebenszeit es geht in dem Fall der Parameter ist, nicht irgendwas was man eventuell danach dann wiederrum mit dem Parameter initisalisiert.

    Und auch das Temporary direkt an die Membervariable dranzuknoten geht nicht:

    #include <vector>
    #include <iostream>
    
    struct TempThing {
        ~TempThing() {
            std::cout << "~TempThing" << std::endl;
        }
    };
    
    TempThing makeTempThing() {
        return {};
    }
    
    struct Foo {
        TempThing const& cr = makeTempThing();
        ~Foo() {
            std::cout << "~Foo" << std::endl;
        }
    };
    
    int main() {
        Foo f;
        std::cout << "Checkpoint" << std::endl;
    }
    

    GCC (akzeptiert den Code):

    ~TempThing
    Checkpoint
    ~Foo
    

    Clang (Compilerfehler):

    <source>:14:8: error: reference member 'cr' binds to a temporary object whose lifetime would be shorter than the lifetime of the constructed object
    struct Foo {
           ^~~
    <source>:22:9: note: in implicit default constructor for 'Foo' first required here
        Foo f;
            ^
    <source>:15:22: note: initializing field 'cr' with default member initializer
        TempThing const& cr = makeTempThing();
                         ^    ~~~~~~~~~~~~~~~
    


  • @hustbaer MSVC akzeptiert das auch... ^^ aber ich gebe mich geschlagen



  • @Schlangenmensch
    Ich will nur zur Sicherheit nachfragen: dir ist aufgefallen dass GCC, obwohl er den Code akzeptiert, hier eben genau nicht wie u.U. vom Programmierer gewünscht die Lebenszeit des temporären Objekts verlängert. Korrekt? Also trotz dem dass GCC den Code akzeptiert, kann man damit nix sinnvoll anfangen. MSVC hab ich nicht probiert, ich traue mich aber einen beliebigen 5-stelligen Betrag in EUR wetten dass auch MSVC hier die Lebenszeit nicht verlängert.



  • @hustbaer
    Ausgabe MSVC:

    Checkpoint
    ~Foo
    

    Was ich allerdings analaog dazu gemacht habe:

    #include <iostream>
    
    struct A
    {
      A(std::string c):
      content(c)
      {}
      std::string content;
    };
    
    A getA()
    {
      return A("temp");
    }
    
    struct B
    {
      void print() const
      {
        std::cout << a.content << std::endl;
      }
      const A& a = getA();
    };
    
    
    int main() {
      B b;
      b.print();
    }
    

    und das gibt mitr MSVC (16 aka 2019), tempaus. GCC und Clang kann ich gerade nur online testen, GCC scheint zu kompilieren, gibt aber nichts aus und Clang akzeptiert das nicht.



  • @Schlangenmensch
    OK, hab's mir angesehen. MSVC generiert hier total kaputten Code. Er scheint hier einen statischen Speicherbereich für das "temporäre" Objekt zu verwenden an das B::a gebunden wird. Dort wird dann jedes mal wenn man ein B Objekt erzeugt, ein A Objekt hineinkonstruiert. Ohne das alte A Objekt vorher zu zerstören:

    #define _CRT_SECURE_NO_WARNINGS
    
    #include <iostream>
    #include <string>
    
    struct A {
    	explicit A(std::string const& s) {
    		strcpy(content, s.c_str());
    	}
    
    	~A() {
    		std::cout << "~A: " << content << std::endl;
    	}
    
    	char content[100];
    };
    
    A getA() {
    	static int count = 0;
    	count++;
    	return A("temp" + std::to_string(count));
    }
    
    struct B {
    	void print() const {
    		std::cout << a.content << std::endl;
    	}
    
    	const A& a = getA();
    };
    
    int main() {
    	B b1;
    	B b2;
    	B b3;
    
    	b1.print();
    	b2.print();
    	b3.print();
    
    	std::cout << (&b1.a) << std::endl;
    	std::cout << (&b2.a) << std::endl;
    	std::cout << (&b3.a) << std::endl;
    }
    

    Output:

    temp3
    temp3
    temp3
    00007FF789984450
    00007FF789984450
    00007FF789984450
    

    Völlig unbrauchbar.

    Die einzig vernünftige "Implementierung" hat hier Clang.


Anmelden zum Antworten