Template-Methode mit Default-Parameter



  • Hallo,

    Ich habe eine template-Methode erstellt, die einen Default-Parameter verwendet. Durch den Aufruf der Methode zerstöre ich mir den Stack! Allerdings ist mit nicht klar, was hier falsch ist.

    Minimalbeispiel:

    class ForceBug
    {
    public:
    template<typename NumericType>
    void bar(NumericType value, NumericType step = NumericType());
    };
    
    template<typename NumericType>
    void ForceBug::bar(NumericType value, NumericType step)
    {
    	NumericType dummy = step; // Beim 5. Aufruf von foo.bar ist step != 0.0 
    }
    int main()
    {
    	ForceBug foo;
    	foo.bar(2);
    	foo.bar(2.0);
    	foo.bar(2.0);
    	foo.bar(2.0);
    	foo.bar(2.0); 
    	return 0;   // hier kommt vom Debugger die Fehlermeldung:
    }
    

    Fehler:

    Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
    

    Noch ein paar Anmerkungen:
    Wenn man den Defaultparamter mit "step=0" definiert, reicht schon ein Aufruf mit einem double-Parameter, um den Fehler zu produzieren. Mit "step=NumericType()" konnte ich es nur reproduzieren, wenn zuerst ein Aufruf mit einem int-Parameter und danach mit einem double-Parameter erfolgt. Der Fehler lässt sich auch nicht bei freien Funktion reproduzieren oder wenn die Methode direkt in der Klasse definiert wird. Ich verwende Visual Studio 2005.

    Was mache ich falsch? Falls das ein Compiler-Problem ist, gibt es dazu irgenwo schon einen Fehlerreport? Kann jemand nachvollziehen, ob das Problem in Visual Studio 2008 auch noch besteht?

    Danke
    djohn



  • Das scheint ein Compiler Bug zu sein, denn du machst rein gar nichts, womit du eine Calling Convention missachten könntest. 😉

    Unter VS 2010 läuft das ohne Probleme.
    VS 2005 hat afaik Probleme mit templates. Kann gut sein, dass es daher kommt, aber mit normalem Code kann man so einen Fehler nicht erzeugen (müsstest z.B inline assembler benutzen oder vlt. mit einem merkwürdigen Pointer hack).



  • drakon schrieb:

    Unter VS 2010 läuft das ohne Probleme.

    Bei mir krachts in der 10er EE im Debug. Release läuft.



  • Tatsächlich. Auf Debug krieg ich den Fehler auch. Hatte es aus versehen noch auf Release.

    Scheint, dass es Probleme gibt sobald man verschiedene Typen übergibt. Da werden aber korrekt 2 verschiedene Funktionen aufgerufen. Der Fehler scheint von den verschiedenen Grössen der Parameter zu kommen. Aber ich sehe nicht, was an dem Code falsch sein sollte.

    Wenn ich x64 Code generieren lasse, dann läufts auch im Debug.



  • Ja, das Minimalbeispiel macht im Release nichts sichtbares, deshalb scheint alles in Ordnung. Wenn man aber einfach eine Ausgabe einfügt, sieht man, dass da was nicht stimmen kann:

    #include <iostream>
    class ForceBug
    {
    public:
    template<typename NumericType>
    void bar(NumericType value, NumericType step = NumericType());
    };
    
    template<typename NumericType>
    void ForceBug::bar(NumericType value, NumericType step)
    {
    	NumericType dummy = step;
    	std::cout << "Value: " << value << " Step: " << step << std::endl;
    }
    int main()
    {
    	ForceBug foo;
    	foo.bar(2);
    	foo.bar(2.0);
    	foo.bar(2.0);
    	foo.bar(2.0);
    	foo.bar(2.0);
    	return 0;
    }
    

    Ausgabe:

    Value: 2 Step: 0
    Value: 0 Step: 1.80244e-307
    Value: 0 Step: 1.79435e-307
    Value: 0 Step: 5.30499e-315
    Value: 0 Step: 1.23606e-312
    

    erwartete Ausgabe:

    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    

    djohn



  • foo.bar(2.0);
    

    Kann es sein, dass du

    foo.bar(2,0);
    

    schreiben wolltest? Denn du erwartest ja 2 Parameter...



  • @djohn
    Das kommt wahrscheinlich daher, dass bei double irgendwas schief läuft und er irgendwie nicht 2 Wörter liest, sondern so nur ein Teil und dann den Rest von irgend einem anderen Wort liest.

    @vermutung
    Spielt eigentlich keine Rolle, weil das template 2 Funktionen machen kann. Man kann natürlich beide Parameter angeben, aber das tut ja nichts zur Sache, dass der gezeigte Code einen Fehler generiert.

    Ich würde mal vermute, dass hier tatsächlich ein Bug vorliegt. Jemand hier, der GCC installiert hat?



  • 2.0 ist schon richtig, damit soll eine Template-Instance für doube erzwungen werden. (Kann auch jeder andere double-Wert sein.) Als zweiter Parameter soll automatisch der Default-Parameter genommen werden.

    djohn



  • Wirklich merkwürdig, bei mir (VS 2010) ist der Fehler ebenfalls reproduzierbar. Ich würde im Microsoft-spezifischen Unterforum nachfragen (oder diesen Thread verschieben), eventuell wissen die dort mehr.

    @ vermutung: Kaum, er will halt mit double instanziieren. Der zweite Parameter ist eh ein Default-Parameter.



  • drakon schrieb:

    Ich würde mal vermute, dass hier tatsächlich ein Bug vorliegt. Jemand hier, der GCC installiert hat?

    gcc 4.5.2 verhält sich wie erwartet:

    $ ./a.out
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    

    Ich sehe im Code auch keinen Fehler. Scheint ein VS-Bug zu sein.



  • drakon schrieb:

    Jemand hier, der GCC installiert hat?

    Mit g++ 4.5.0 kommt die erwartete Ausgabe (kein Laufzeitfehler):

    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    

  • Mod

    drakon schrieb:

    Ich würde mal vermute, dass hier tatsächlich ein Bug vorliegt. Jemand hier, der GCC installiert hat?

    Ja. Läuft hervorrragend mit GNU und Intel-Compiler (beide ziemlich neu). Debugger meldet auch nichts verdächtiges. Und ich sehe auch keinen Fehler im Code.



  • gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)

    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0
    Value: 2 Step: 0



  • Kannst du mir mal den generierten Assembler Code für die beiden Funktionen und die main geben? (EDIT: Ohne die Ausgaben, wens geht. :))

    ForceBug foo; 
        foo.bar(2);
        foo.bar(2.0);
    

    in der main reicht schon, um den Fehler zu erzeugen.

    Notiz an mich:
    Ich muss mal meine Ubuntu Installation brauchbar machen.. 😃


  • Mod

    drakon schrieb:

    Kannst du mir mal den generierten Assembler Code für die beiden Funktionen und die main geben? (EDIT: Ohne die Ausgaben, wens geht. :))

    Meinst du mich? Ok, hier der Code der unoptimierten Version, weil die optimierte Version total unleserlich ist (GCC 4.4.3, 64 Bit):
    main:

    leaq	-1(%rbp), %rax
    	movl	$0, %edx
    	movl	$2, %esi
    	movq	%rax, %rdi
    	call	_ZN8ForceBug3barIiEEvT_S1_
    
    	movsd	.LC0(%rip), %xmm0
    	leaq	-1(%rbp), %rax
    	xorpd	%xmm1, %xmm1
    	movq	%rax, %rdi
    	call	_ZN8ForceBug3barIdEEvT_S1_
    

    _ZN8ForceBug3barIiEEvT_S1_:

    pushq	%rbp
    	movq	%rsp, %rbp
    	subq	$32, %rsp
    	movq	%rdi, -24(%rbp)
    	movl	%esi, -28(%rbp)
    	movl	%edx, -32(%rbp)
    	movl	-32(%rbp), %eax
    	movl	%eax, -4(%rbp)
    	movl	$.LC2, %esi
    	movl	$_ZSt4cout, %edi
    	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movl	-28(%rbp), %edx
    	movl	%edx, %esi
    	movq	%rax, %rdi
    	call	_ZNSolsEi
    	movl	$.LC3, %esi
    	movq	%rax, %rdi
    	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movl	-32(%rbp), %edx
    	movl	%edx, %esi
    	movq	%rax, %rdi
    	call	_ZNSolsEi
    	movl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esi
    	movq	%rax, %rdi
    	call	_ZNSolsEPFRSoS_E
    	leave
    	ret
    

    Und die andere für double:

    pushq	%rbp
    	movq	%rsp, %rbp
    	subq	$48, %rsp
    	movq	%rdi, -24(%rbp)
    	movsd	%xmm0, -32(%rbp)
    	movsd	%xmm1, -40(%rbp)
    	movq	-40(%rbp), %rax
    	movq	%rax, -8(%rbp)
    	movl	$.LC2, %esi
    	movl	$_ZSt4cout, %edi
    	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movsd	-32(%rbp), %xmm0
    	movq	%rax, %rdi
    	call	_ZNSolsEd
    	movl	$.LC3, %esi
    	movq	%rax, %rdi
    	call	_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
    	movsd	-40(%rbp), %xmm0
    	movq	%rax, %rdi
    	call	_ZNSolsEd
    	movl	$_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_, %esi
    	movq	%rax, %rdi
    	call	_ZNSolsEPFRSoS_E
    	leave
    	ret
    

    Debuginformationen habe ich mal rausgekürzt um es übersichtlicher zu halten. Ich weiß jetzt aber nicht, was das bringen soll, beide Funktionen arbeiten korrekt und sind im Prinzip identisch, außer dass bei der einen alle Befehle für int sind, bei der anderen für doubles. Wie sehen die Funktionen denn bei VS 2010 aus?

    edit: Oh, die Ausgabefunktionen habe ich entgegen deinem Wunsch noch drin. Naja, so lang ist's ja nun auch nicht.



  • Hmm. Mir scheint als hätte ich den Fehler gefunden, den VC da macht.
    Die Funktionen selbst sind denke ich korrekt implementiert, aber der Aufruf der Funktionen scheint nicht so ganz zu klappen.

    foo.bar(2);
    00EA10AB  push        0  % zweiter parameter: ok
    00EA10AD  push        2  % erster parameter: ok
    00EA10AF  lea         ecx,[foo]  % this: ok
    00EA10B2  call        ForceBug::bar<int> (0EA1046h)  
        foo.bar(2.0);
    00EA10B7  push        0  % (1)
    00EA10B9  sub         esp,8  
    00EA10BC  fld         qword ptr [__real@4000000000000000 (0EA6830h)]  % parameter 1
    00EA10C2  fstp        qword ptr [esp]  % parameter 1 speichern
    00EA10C5  lea         ecx,[foo]  % this
    00EA10C8  call        ForceBug::bar<double> (0EA103Ch)
    

    Da sieht man, dass bar(2) korrekt aufgerufen wird. Jedoch bin ich bei (1) stutzig geworden. Warum wird da lediglich ein Wort gepusht und nicht 2, wie es bei einem double ja sein müsste?

    Also habe ich das ganze mal umgedreht:

    foo.bar(2.0);
    00EE10AB  sub         esp,8  
    00EE10AE  fldz  % schon eher wenn man floats ausnullen will
    00EE10B0  fstp        qword ptr [esp]  
    00EE10B3  sub         esp,8  
    00EE10B6  fld         qword ptr [__real@4000000000000000 (0EE6830h)]  
    00EE10BC  fstp        qword ptr [esp]  
    00EE10BF  lea         ecx,[foo]  
    00EE10C2  call        ForceBug::bar<double> (0EE103Ch)  
        foo.bar(2);
    00EE10C7  sub         esp,8  
    00EE10CA  fldz  % (2)
    00EE10CC  fstp        qword ptr [esp]  
    00EE10CF  push        2  % erster parameter
    00EE10D1  lea         ecx,[foo]  
    00EE10D4  call        ForceBug::bar<int> (0EE1046h)
    

    Bei (2) dachte ich dann nur WTF? - Warum wird da eine floating Point Operation gemacht, wenn doch nur int's im Spiel sind?

    Da scheint irgendwie Code vorschnell erzeugt zu werden.. Ok, also mal schauen was passiert, wenn wir die bösen templates weglassen. :
    (ich habe das template mit identischen Funktionen mit expliziten Parametern ausgetauscht)

    foo.bar(2.0);
    00C6168B  sub         esp,8  
    00C6168E  fldz  
    00C61690  fstp        qword ptr [esp]  
    00C61693  sub         esp,8  
    00C61696  fld         qword ptr [__real@4000000000000000 (0C66830h)]  
    00C6169C  fstp        qword ptr [esp]  
    00C6169F  lea         ecx,[foo]  
    00C616A2  call        ForceBug::bar (0C61050h)  
        foo.bar(2);
    00C616A7  push        0  % kein floating Zeugs mehr. 
    00C616A9  push        2  
    00C616AB  lea         ecx,[foo]  
    00C616AE  call        ForceBug::bar (0C6104Bh)
    

    ahh.. schon eher..

    und andersrum:

    foo.bar(2);
    008A168B  push        0  
    008A168D  push        2  
    008A168F  lea         ecx,[foo]  
    008A1692  call        ForceBug::bar (8A104Bh)  
        foo.bar(2.0);
    008A1697  sub         esp,8  
    008A169A  fldz  % jetzt passt auch das hier
    008A169C  fstp        qword ptr [esp]  
    008A169F  sub         esp,8  
    008A16A2  fld         qword ptr [__real@4000000000000000 (8A6830h)]  
    008A16A8  fstp        qword ptr [esp]  
    008A16AB  lea         ecx,[foo]  
    008A16AE  call        ForceBug::bar (8A1050h)
    

    Jetzt stimmts auch in der Richtung.

    Fazit: Selbst VC++ 2010 hat gewisse Probleme mit templates.. 😛

    @SeppJ:
    Danke, aber ich hab den Fehler, den VC da macht bereits. 🙂

    Hat jemand eine Ahnung, ob der Bug bereits gemeldet wurde? Respektive wo es da eine gute DB dazu gibt? Ich werde das bei Gelegenheit mal noch in meinem Blog posten. Wird Zeit, dass da wiedermal was passiert. 🙂

    //EDIT:
    Habe anscheinend den korrekten Anlaufpunkt gefunden und der Fehler scheint noch nicht bekannt zu sein:
    https://connect.microsoft.com/VisualStudio/Feedback



  • scary...

    @drakon: Hast du den Fehler reported?



  • Hallo,

    vielen Dank für die vielen Rückmeldungen. Ich hab jetzt hier
    den Fehler gemeldet.

    djohn



  • hustbaer schrieb:

    scary...

    @drakon: Hast du den Fehler reported?

    Nein. Bin gester nicht dazu gekommen. Wollte eigentlich einen exakten Beschreib (auch für meinen Blog) von dem Fehler machen.

    Ich habe mich gestern dort extra noch angemeldet. 😛

    @djohn:
    Da ich ja bereits den Fehler analysiert habe wäre es vielleicht gut das auch noch zu melden. Dann können die sich ein wenig Arbeit sparen.. ^^
    Ich werde mal in einem Kommentar den Fehlerbeschreib ergänzen.



  • Habe gerade noch ein paar Sachen ausprobiert. Mit freien template Funktionen funktioniert es und auch wenn man die Definition inline macht (was denke ich der Hauptgrund ist, dass den Fehler noch niemand bemerkt hat).


Anmelden zum Antworten