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-312erwartete Ausgabe:
Value: 2 Step: 0 Value: 2 Step: 0 Value: 2 Step: 0 Value: 2 Step: 0 Value: 2 Step: 0djohn
-
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
doubleinstanziieren. 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: 0Ich 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
-
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..
-
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 retUnd 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 retDebuginformationen 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?
-
-
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).