Undefined behaviour in der libc++?

Kamikaze

Warrior of Sunlight
Teammitglied
Hallo, für mich liest sich das so als stecke in der libc++ undefined Behaviour.

Wie man am Valgrind output sieht tritt das Problem in einem istream auf. Wenn ich in meinem Programm eine Exception schmeiße gibt es sogar ein Memory-Leak. Für mich liest sich das als wäre da ein Fehler in der libc++.

Könnte das eine Ursache in meinem Code haben?
Code:
==7103== Conditional jump or move depends on uninitialised value(s)
==7103==    at 0x4C23853: free (in /usr/local/lib/valgrind/vgpreload_memcheck-amd64-freebsd.so)
==7103==    by 0x50C975E: std::__1::ios_base::~ios_base() (in /usr/lib/libc++.so.1)
==7103==    by 0x407403: std::__1::basic_ifstream<char, std::__1::char_traits<char> >::~basic_ifstream() (iosfwd:131)
==7103==    by 0x407428: std::__1::basic_ifstream<char, std::__1::char_traits<char> >::~basic_ifstream() (iosfwd:131)
==7103==    by 0x405F75: operator() (memory:2431)
==7103==    by 0x405F75: reset (memory:2630)
==7103==    by 0x405F75: ~unique_ptr (memory:2598)
==7103==    by 0x405F75: main (main.cpp:116)
==7103== 
==7103== Conditional jump or move depends on uninitialised value(s)
==7103==    at 0x4C23853: free (in /usr/local/lib/valgrind/vgpreload_memcheck-amd64-freebsd.so)
==7103==    by 0x50C9770: std::__1::ios_base::~ios_base() (in /usr/lib/libc++.so.1)
==7103==    by 0x407403: std::__1::basic_ifstream<char, std::__1::char_traits<char> >::~basic_ifstream() (iosfwd:131)
==7103==    by 0x407428: std::__1::basic_ifstream<char, std::__1::char_traits<char> >::~basic_ifstream() (iosfwd:131)
==7103==    by 0x405F75: operator() (memory:2431)
==7103==    by 0x405F75: reset (memory:2630)
==7103==    by 0x405F75: ~unique_ptr (memory:2598)
==7103==    by 0x405F75: main (main.cpp:116)
==7103== 
==7103== 
==7103== HEAP SUMMARY:
==7103==     in use at exit: 5,328 bytes in 6 blocks
==7103==   total heap usage: 1,408,546 allocs, 1,408,540 frees, 13,482,532,159 bytes allocated
==7103== 
==7103== LEAK SUMMARY:
==7103==    definitely lost: 0 bytes in 0 blocks
==7103==    indirectly lost: 0 bytes in 0 blocks
==7103==      possibly lost: 0 bytes in 0 blocks
==7103==    still reachable: 5,328 bytes in 6 blocks
==7103==         suppressed: 0 bytes in 0 blocks
==7103== Rerun with --leak-check=full to see details of leaked memory
==7103== 
==7103== For counts of detected and suppressed errors, rerun with: -v
==7103== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
 
Hängt es vielleicht damit zusammen?

http://en.cppreference.com/w/cpp/io/ios_base/ios_base schrieb:
The internal state is undefined after the construction. The derived class must call basic_ios::init() to complete initialization before first use or before destructor, otherwise the behavior is undefined.
 
Für mich sieht das so aus als wäre das eine ordentliche Initialisierung:
Code:
in = new ifstream{argv[1], ifstream::in | ifstream::binary};

Wenn ich mit g++49 baue tritt das Problem auch nicht auf.
 
Für mich sieht das so aus als wäre das eine ordentliche Initialisierung:
Code:
in = new ifstream{argv[1], ifstream::in | ifstream::binary};

Da ist auch kein std::unique_ptr involviert, kannst du vielleicht mal den kompletten (in Bezug auf den trace von valgrind) Code posten?
Vielleicht ist es ein Problem mit Wechselwirkungen zwischen std::unique_ptr und std::ifstream.

Wenn ich mit g++49 baue tritt das Problem auch nicht auf.

Aber g++49 nutzt die GNU libstdc++, oder irre ich mich da?
 
Aber g++49 nutzt die GNU libstdc++, oder irre ich mich da?
Das ist der Punkt daran.

Hier ist einmal der Code.

Die unique_ptr sind dazu da zu garantieren, dass das später ordentlich aufgeräumt wird.
Code:
/** \file main.cpp
 * File for the main() function.
 */

#include "io/Parse.hpp"
#include "io/Exception.hpp"

#include <iostream>
#include <fstream>

using io::Parse;
using io::Exception;

using namespace std;

/**
 * Opens input/output streams and starts the simulation.
 *
 * The program has three optional command line arguments:
 * | position | default | function
 * |----------|---------|--------------------------------------
 * |        1 |   stdin | Input stream for the parser
 * |        2 |  stdout | Output stream for simulation results
 * |        3 |  stderr | Debugging output
 *
 * If only the first one is given the output will be written to
 * a file named "$1.out". The '-' character can be given to
 * indicate default even when other parameters are set.
 *
 * @param argc
 *	The number of command line arguments
 * @param argv
 *	Command line arguments
 * @retval 0
 *	The simulation terminated without error
 * @retval 1
 *	Opening the input file failed
 * @retval 2
 *	Opening the output file failed
 * @retval 3
 *	Opening the diagnosis output file failed
 * @retval 4
 *	Opening the output file derived from the input file name failed
 * @retval -1
 *	The simulation terminated due to an Exception
 */
int main(int argc, char * argv[]) {
	int state = 0;

	/* Containers for custom I/O streams. */
	unique_ptr<istream> arg_in;
	unique_ptr<ostream> arg_out;
	unique_ptr<ostream> arg_err;

	/* Pointers to the streams to pass on to the parser. */
	istream * in = &cin;
	ostream * out = &cout;
	ostream * err = &cerr;

	/* Parse input stream to perform simulation. */
	try {
		/* Open error output first. */
		if (argc >= 4 && string(argv[3]) != "-") {
			err = new ofstream{argv[3], ofstream::out | ofstream::binary};
			arg_err = unique_ptr<ostream>{err};
			if (!err->good()) {
				cerr << "The file \"" << argv[3] << "\" could "
				     << "not be opened or created for writing!\n";
				throw 3;
			}
		}

		/* Open input next. */
		if (argc >= 2 && string(argv[1]) != "-") {
			in = new ifstream{argv[1], ifstream::in | ifstream::binary};
			arg_in = unique_ptr<istream>{in};
			if (!in->good()) {
				*err << "The file \"" << argv[1] << "\" could "
				     << "not be opened for reading!\n";
				throw 1;
			}
			/* If there is an input file, but no output file,
			 * create one. */
			if (argc < 3) {
				out = new ofstream{(string{argv[1]} + ".out").c_str(), ofstream::out | ofstream::binary};
				arg_out = unique_ptr<ostream>{out};
				if (!out->good()) {
					*err << "The file \"" << argv[1] << ".out\" could "
					     << "not be opened or created for writing!\n";
					throw 4;
				}
			}
		}

		/* And finally open the output. */
		if (argc >= 3 && string(argv[2]) != "-") {
			out = new ofstream{argv[2], ofstream::out | ofstream::binary};
			arg_out = unique_ptr<ostream>{out};
			if (!out->good()) {
				*err << "The file \"" << argv[2] << "\" could "
				     << "not be opened or created for writing!\n";
				throw 2;
			}
		}

		/* Finally parse input. */
		Parse parse(*in, *out, *err);
	} catch (Exception & e) {
		*err << '\n' << e.what() << '\n';
		state = -1;
	} catch (int e) {
		state = e;
	}

	return state;
}
 
Ich hab das aber eben mal auf Ubuntu 14.04 mit clang++ -std=c++14 -stdlib=libc++ ausprobiert, dort tritt das Problem nicht auf.
Sobald ich mein Handy mit Musik versorgt habe, werde ich das mal mit meinem FreeBSD 10.1 probieren.
 
So unter FreeBSD 10.01 bekomme ich weder mit clang++36 -std=c++14 noch mit clang++ -std=c++11 deine Meldungen von Valgrind.
Ich habe in allen Fällen --leak-check=full --track-origins=yes verwendet.
 
Ich bin halt geschwätzig und verwende DoxyGen. Und ich habe in der C++ Welt noch keine Code-Doku gesehen die mir gefällt.
 
Die selbstlöschenden Pointer machen Exceptions halt erst sinnvoll nutzbar.

Ich verwende Exceptions übrigens sehr sparsam im Code. Ich habe sehr viele Interfaces an denen noexcept dran steht. Wenn ich stdin verwende bekomme ich übrigens kein Leak.

Ich denke der nächste Schritt wird das über ein Minimalbeispiel zu reproduzieren.
 
Ich verwende Exceptions übrigens sehr sparsam im Code. Ich habe sehr viele Interfaces an denen noexcept dran steht.
Aber in dem Code da oben kann man doch alles mit Rückgabewerten machen. Und es wäre auch viel lesbarer :o Aber ich wollte auch eigentlich nicht rummeckern, jeder codet anders und findet wahrscheinlich unterschiedliche Dinge "lesbar" ;)
 
Nicht alles, aber einiges das stimmt.

Im ursprünglichen Code hatte ich keine C++11 Features, in dem Fall unique_ptr, so dass ich Da kein return genommen habe damit ich noch ordentlich aufräumen kann.

Das ist in der Praxis zwar überflüssig, weil der Prozess dann ja terminiert. Aber solche Schlampereien wollte ich dann doch nicht machen.
 
Zurück
Oben