Const member mit Referenz initialisieren - seg fault



  • Hallo zusammen,

    es sei eine Klasse, die im Contructor eine const ref auf std::string übernimmt und damit ein const string member initialisiert:
    (Include <string> und using namespace std; vorausgesetzt)

    struct Logger
    {
        Logger( const string& name_ ) : name( name_ ) {}
        void print() { cout << "Name: " << name << endl; }
        const string name;
    };
    

    Dieser Constructor wird im Projekt mehrmals mit einem C-string als Argument aufgerufen:

    int main() {
    	Logger myLogger( "test" );
    	myLogger.print();
        return 0;
    }
    

    Interessanterweise funktioniert das ohne Probleme seit Jahren auf zwei unterschiedlichen Systemen. Nun wurde es auf einem dritten compiliert und führt beim Aufruf des Ctors zu einem segmentation fault. Große Überraschung!

    Ungeachtet irgendwelcher Optimisierungen vermute ich prinzipiell folgenden Ablauf: Beim Erzeugen des Objekts myLogger wird ein temporäres std::string-Objekt erzeugt, das mit dem C-String initialisiert wird. Dieses wird als Referenz an den Ctor übergeben, der damit das const-Member name initialisiert.

    Frage:
    Kann man eine Aussage darüber treffen, ob eine Kopie stattfindet, d.h. name nachher sein eigenes std::string-Objekts hält? Oder ist dies offen gelassen, und der Compiler könnte auch, da der Ctor nur eine Referenz nimmt, name mit der Referenz des temporären std::string-Objekts verknüpfen? Die Folge wäre im letzten Fall natürlich Zugriff auf nicht mehr allokierten Speicher.

    Man könnte die Sache eindeutig machen:
    Bei einem Constructor Logger( string name_ ) hätten wir auf jeden Fall eine Kopie und wären sicher.

    Wie ist das in meinem Fall? Grauzone?



  • minastaros schrieb:

    ein const string member

    Dumme Idee. Mach das const weg.

    Interessanterweise funktioniert das ohne Probleme seit Jahren auf zwei unterschiedlichen Systemen. Nun wurde es auf einem dritten compiliert und führt beim Aufruf des Ctors zu einem segmentation fault. Große Überraschung!

    Ich bin ziemlich sicher, dass es nicht am Ctor liegt. Du hast anderswo UB.

    Frage:
    Kann man eine Aussage darüber treffen, ob eine Kopie stattfindet, d.h. name nachher sein eigenes std::string-Objekts hält? Oder ist dies offen gelassen, und der Compiler könnte auch, da der Ctor nur eine Referenz nimmt, name mit der Referenz des temporären std::string-Objekts verknüpfen? Die Folge wäre im letzten Fall natürlich Zugriff auf nicht mehr allokierten Speicher.

    Der Compiler darf alles, solange sich das Verhalten nicht ändert. Zugriff auf nicht mehr allokierten Speicher ändert das Verhalten aber, deshalb ist das verboten.

    Wie ist das in meinem Fall? Grauzone?

    Der Code ist völlig korrekt. Du hast vermutlich anderswo im Programm UB und das Programm crasht halt zufällig dort.



  • blaurath schrieb:

    minastaros schrieb:

    ein const string member

    Dumme Idee. Mach das const weg.

    Damit ist ausgedrückt, dass der String 'Name' einmal vergeben wird und dann - auch in der Klasse selbst - nicht mehr geändert werden darf. Dafür sind consts da. Soweit ist weiß ist kann das Const auch zur Laufzeit entstehen.
    Wenn nicht für den Compiler, dann für den Leser. Ich sehe keine grundsätzliche Dummheit darin.

    blaurath schrieb:

    ]Der Code ist völlig korrekt. Du hast vermutlich anderswo im Programm UB und das Programm crasht halt zufällig dort.

    Mag sein, das wird die weitere Untersuchung zeigen. Im Moment ist das die Erkenntnis, dass es am Widerspruch const bzw. Referenz liegen könnte.
    Const-Objekte verhalten sich ja hier und da anders als nicht const, bezogen auf den Speicherbereich. Wollte nur sichergehen, dass hier wirklich immer eine Kopie stattfindet (das ist bis jetzt auch mein Wissen).



  • minastaros schrieb:

    blaurath schrieb:

    minastaros schrieb:

    ein const string member

    Dumme Idee. Mach das const weg.

    Damit ist ausgedrückt, dass der String 'Name' einmal vergeben wird und dann - auch in der Klasse selbst - nicht mehr geändert werden darf. Dafür sind consts da. Soweit ist weiß ist kann das Const auch zur Laufzeit entstehen.
    Wenn nicht für den Compiler, dann für den Leser. Ich sehe keine grundsätzliche Dummheit darin.

    Ja, aber du kannst die Klasse dann nur nicht mehr zuweisen.



  • minastaros schrieb:

    blaurath schrieb:

    minastaros schrieb:

    ein const string member

    Dumme Idee. Mach das const weg.

    Damit ist ausgedrückt, dass der String 'Name' einmal vergeben wird und dann - auch in der Klasse selbst - nicht mehr geändert werden darf. Dafür sind consts da. Soweit ist weiß ist kann das Const auch zur Laufzeit entstehen.
    Wenn nicht für den Compiler, dann für den Leser. Ich sehe keine grundsätzliche Dummheit darin.

    swap() das mal ...

    minastaros schrieb:

    Soweit ist weiß ist kann das Const auch zur Laufzeit entstehen.

    😕

    blaurath schrieb:

    Const-Objekte verhalten sich ja hier und da anders als nicht const, bezogen auf den Speicherbereich.

    😕



  • Swordfish schrieb:

    swap() das mal ...

    OK, du meinst, zwei Logger-Objekte zu swap()en? Verstehe, das geht nicht. War im bisherigen Anwendungsfall auch nicht vorgesehen.
    Also ist es allgemein eine Empfehlung, keine const member zu haben, die im Ctor initialisiert werden, weil swap() nicht geht? Speichern im Standard-Container sollte ebenfalls nicht gehen (Default-ctor fehlt). Das wäre sicher ein Grund, OK.
    Kopieren geht ja wenigstens, wenn man den Copy-ctor mitgibt.

    minastaros schrieb:

    Soweit ist weiß ist kann das Const auch zur Laufzeit entstehen.

    😕

    Im Allgemeinen werden consts verwendet für Werte, die zur Compilezeit bekannt sind wie

    const float PI = 3.1415926;
    

    In meinem Beispiel habe ich ein const member Objekt, das jedoch erst zur Laufzeit entsteht, dann aber konstant bleiben soll.
    Was ich damit sagen wollte: Ich betrachte "const" nicht zwingend als "zur Compilezeit bekannt".

    minastaros schrieb:

    Const-Objekte verhalten sich ja hier und da anders als nicht const, bezogen auf den Speicherbereich.

    😕

    Ich meinte den CONST DATA speicherbereich (s. hier: http://www.gotw.ca/gotw/009.htm), in dem zur-Compilezeit-const-Daten abgelegt werden, im Gegensatz zum Stack. Aber ersteres gilt ja eh nicht für Objekte, und eben auch nicht in meinem Fall für das const member. Also, Denkfehler von mir. 😉



  • minastaros schrieb:

    Aber ersteres gilt ja eh nicht für Objekte, und eben auch nicht in meinem Fall für das const member. Also, Denkfehler von mir. 😉

    Genau. Das const könntest du weglassen, in deinem Fall macht das keinen Unterschied.

    Wieso ein Programm segfaulted kann ich nicht aus diesem Grund nicht sagen, weil der gezeigte Code korrekt ist.

    Wenn du andeuten willst, dass name nicht verändert wird, dann zeige das an der Funktion an:

    void print() const // <- hier ein const
    

    Das dokumentiert viel stärker als bei Membern (weil öffentlich).



  • minastaros schrieb:

    Swordfish schrieb:

    swap() das mal ...

    OK, du meinst, zwei Logger-Objekte zu swap()en? Verstehe, das geht nicht. War im bisherigen Anwendungsfall auch nicht vorgesehen.
    Also ist es allgemein eine Empfehlung, keine const member zu haben, die im Ctor initialisiert werden, weil swap() nicht geht? Speichern im Standard-Container sollte ebenfalls nicht gehen (Default-ctor fehlt).

    Nein, const Member sind keine dumme Idee wenn man weiss warum man es tut und was es für Konsequenzen hat (die oben genannten). Das Problem dieser Dogmen (immer dies, nie das) ist, dass sie eigentlich immer erst durch den Kontext zu einer schlechten Idee werden, der bei deinem Code gar nicht vorgegeben ist - allerdings ist es meist nicht verkehrt, diese Glaubenssätze zu beherzigen (man spart sich viel Ärger, wenn man nicht weiss was man tut und keinen guten Grund dafür hat) :D.

    Zu deinem eigentlichen Problem, das zwischen diesen ganzen Belehrungen leider etwas zu kurz gekommen ist:

    Der Code sieht korrekt aus, daran liegt es wahrscheinlich nicht (bezieht sich natürlich nur auf das, was du gepostet hast).
    Wo du schon mehrere Systeme erwähnst: Ist es eventuell möglich, dass Teile des Codes mit einer anderen Version oder Debug/Release-Variante der Standardbibliothek kompiliert wurden (verschiedene DLLs/Shared Objects/Statische Bibliotheken, auch indirekt über dritte Bibliotheken), oder gar mit einem anderen Compiler?
    Wenn nicht, liegt es wahrscheinlich an anderem Code, der irgendwo dazwischenfunkt. Vielleicht probierst du mal nur den Logger-Code aus, den du hier gepostet hast (ohne den ganzen anderen Kram).
    Ansonsten mal im Debugger das Objekt und den Call Stack des Konstruktors unter die Lupe nehmen. Besonders an dem Punkt wo er vor die Wand läuft, und was davor passiert ist (Call Stack).

    Gruss,
    Finnegan



  • OK, vielen Dank zusammen. Ursachensuche geht weiter.
    Ggf. liegt es auch an der Aufteilung in verschiedene Bibliotheken, dass da jetzt was falsch konfiguriert wurde.

    Wollte mich hier nur versichern, ob es mit const-Membern ggf. irgendwelche Fallstricke gibt, die mir bislang entgangen waren. Das mit dem swap() wäre schonmal einer davon, wenn es auch eine Erkenntnis unabhängig von dem seg fault ist. Trotzdem bedenkenswert, auf jeden Fall.


Anmelden zum Antworten