none
Ausnahmebehandlung in extern "C"-Funktionen RRS feed

  • Frage

  • Ich verstehe hier etwas nicht ganz an der Bedeutung von extern "C" … hoffentlich kann mich jemand erleuchten. Also:

    extern "C" teilt dem Compiler mit, dass man die darin deklarierten Symbole von C-Programmen aus aufrufen können soll. Dass das im Detail C-Namensdekoration usw bewirkt, ist hier unwichtig.

    /EHsc – die standardmäßige Compiler-Option zum setzen des Ausnahmebehandlungsmodells – bewirkt, dass der Compiler annimmt, extern "C"-deklarierte Funktionen könnten keine Ausnahmen werfen: If used with s (/EHsc), catches C++ exceptions only and tells the compiler to assume that extern C functions never throw a C++ exception.

    Das bedeutet u.a., dass in einer Funktion, die zwar aufzuräumende Objekte enthält, in deren Verlauf aber keine K’toren/D’toren/Funktionen aufgerufen werden die Ausnahmen werfen könnten – z.B., indem alle K’toren throw()-dekoriert sind und nur extern "C"-deklarierte Funktionen aufgerufen werden – kein Code für Ausnahmebehandlung erzeugt werden muss. Soweit klar.

    Nun kommen wir aber zum Verhalten innerhalb extern "C"-deklarierter Funktionen. Der Compiler darf die Annahme treffen, dass keine Funktion, die ich in einem extern "C"-Block schreibe, eine Ausnahme wirft. Der Knackpunkt ist nun: Ist das in dem Sinne gemeint, als dass diese Funktion generell keine Ausnahmebehandlung benutzen sollte, oder in dem Sinne, dass sie nur keine Ausnahme nach außen durchlassen („emittieren“) sollte?

    Ich wollte das testen, finde aber widersprüchliches Verhalten: Der Compiler ignoriert innerhalb extern "C"-deklarierter Funktionen Ausnahmebehandlung. Zeigen lässt es sich an diesem Beispiel:

    struct ThrowsOnConstruction {
    
    	ThrowsOnConstruction () {
    		throw 0;
    	}
    
    };
    
    extern "C" {
    
    	ThrowsOnConstruction * badCFunction() {
    		return new ThrowsOnConstruction;
    	}
    
    }
    
    int main() {
    	badCFunction();
    	return 0;
    }
    

    Dieser Code erzeugt ein Speicherleck – da der Compiler davon ausgeht, dass innerhalb der Funktion nichts geworfen wird, fügt er auch keinen Code ein, der die Freigabe des per new allokierten Speichers im Fall eines fehlschlagenden K’tors erzwingt.

    Ganz anders ist die Sache nun aber, wenn man in badCFunction() einen try-catch-Block einfügt. Innerhalb dieses Blocks erzeugt der Compiler Ausnahmebehandlungs-Code und das Speicherleck verschwindet.

    Die Frage ist nun: Wo ist das Verhalten „extern "C"-deklarierte Funktionen benutzen keine Ausnahmebehandlung, es sei denn, sie enthalten einen try-catch-Block“ dokumentiert? Will mir die Dokumentation von /EHsc sagen, dass extern "C"-Funktionen nur keine Ausnahmen zum Aufrufer durchlassen dürfen, dass sie auch intern keine benutzen dürfen oder dass sie sie intern nur benutzen dürfen, wenn sie in try-catch-Blöcken auftreten? Ich finde dazu nichts in der Dokumentation und möchte nicht, dass mein Code mit dem nächsten Compiler Release ungültig wird, weil das Verhalten geändert wurde.

     

    Gruß, Ky

    Montag, 10. Januar 2011 17:59

Antworten

  • Ja, gut. Für mich sieht es mittlerweile auch so aus, als dass man sich dort eh auf wackeligem Terrain bewegt – /EHsc ist genau so kontra dem Sprachstandard wie das Deaktivieren von Stack Unwinding oder Laufzeittypinformation, also muss ich mich auch nicht wundern, wenn an so einer Stelle ein Speicherleck entsteht.

    Die mit Abstand beste Lösung ist, /EHs statt /EHsc zu benutzen, den Compiler extern "C"-Funktionen also als potentielle Emitter für Ausnahmen ansehen zu lassen. Falls das möglich ist.

    Die zweit- und drittbesten Lösungen sind dann, es in eine eigene Funktion auszulagern oder einen try-catch-Block drum herumzuspannen.

    So werde ich es dann auch tun, danke für die Hilfe.

    • Als Antwort markiert Krishty Donnerstag, 13. Januar 2011 19:15
    Dienstag, 11. Januar 2011 16:43

Alle Antworten

  • Das stimmt nicht, was Du schreibst.

    Du hast mit /EHsc gesagt Deine C Funktionen werfen keine Exception.
    Das tun diese jedoch. Also gibt es eine unhandled Excpetion error und Deine Anwendung crashed! Bzw. die CRT führt einen abort aus.

    Grundsätzlich: Für eine Exception für die kein Handler existiert gibt es einen Crash.

    /EHsc sagt lediglich, dass wenn innerhalb der C Funktion eine Exception geworfen wird diese auch dort behandelt werden muss. Sonst gibt es einen unhandled Exception error. Die CRT wird einen abort auslösen.
    Es heißt eben, dass ein Exception Handler im aufrufenden Modul von der innen geworfenen Exception nicht gefunden werden kann.


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Montag, 10. Januar 2011 19:07
    Moderator
  • Ups – die unbehandelte Ausnahme entsteht, weil in meinem Beispiel main() eine Exception wirft, was es nicht darf … bei mir war die Funktion nicht direkt in main() und ich habe sie für das Beispiel schnell reingekleistert, sorry; ich korrigiere zu
    struct ThrowsOnConstruction {
    
    	ThrowsOnConstruction () {
    		throw 0;
    	}
    
    };
    
    extern "C" {
    
    	ThrowsOnConstruction * badCFunction() {
    		return new ThrowsOnConstruction;
    	}
    
    }
    
    int main() {
    	try {
    		badCFunction();
    	} catch(...) { }
    	return 0;
    }
    
    /EHsc bewirkt keine CRT-Fehler; der Compiler lässt Exceptions auch von extern "C"-Funktionen durch. try-catch funktioniert ja auch, wenn Exception Handling abgeschaltet ist – nur die Unwind Semantics bleiben dann unberücksichtigt. Stimmt also schon, was ich schreibe, nur das Beispiel war falsch zusammenimprovisiert.
    Montag, 10. Januar 2011 19:34
  • Eigentlich stimmt es auch wieder nicht.

    Der Compiler ist intelligent genug um hier Exceptions innerhalb des Modules korrekt zu behandeln. ;)

    Verlagere das ganze (die struct mit der Excpetion) in eine DLL und Du wirst eine unhandled Exception erhalten... genau wie ich schreibe...


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Montag, 10. Januar 2011 21:58
    Moderator
  • Habe ich bereits. Nix Exception. Nur durch das scheinbar unerklärliche Speicherleck, was dadurch entstanden ist, habe ich mir diese Fragen erst gestellt ;-)
    Montag, 10. Januar 2011 22:11
  • Ich habe eben mit dem Wizard eine Anwendung zusammengeklickt.

    EXE und DLL. In der DLL wird eine ThrowsOnConstruction struct verwendet.
    Wie erwartet ein Crash!

    Sowohl in Debug als auch Release Version VS-2010.

    Grundsätzlich: Eine Exception die nicht gefangen wird (egal in welchem Mode) führt zu einem Crash...


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Dienstag, 11. Januar 2011 07:56
    Moderator
  • Ich habe eben mit dem Wizard eine Anwendung zusammengeklickt.

    EXE und DLL. In der DLL wird eine ThrowsOnConstruction struct verwendet.
    Wie erwartet ein Crash!

    Sowohl in Debug als auch Release Version VS-2010.

    Grundsätzlich: Eine Exception die nicht gefangen wird (egal in welchem Mode) führt zu einem Crash...

    In der main() soll ja auch ein catch-all sein; das habe ich ja oben korrigiert.

    Hier, damit wir auf demselben Stand sind:

    // cl.exe exe.cpp /EHsc
    #include <iostream>
    #include <Windows.h>
    #pragma comment(lib, "User32.lib")
    
    typedef void(*ImportedFunction)(size_t * toMemoryPointer);
    
    size_t allocatedMemoryInBytes = 0;
    
    int main() {
    
    	if(::HMODULE DLL = ::LoadLibrary("dll.dll")) {
    		::std::cout << "dll.dll geladen" << ::std::endl;
    
    		if(ImportedFunction CExportWithTry = (ImportedFunction)::GetProcAddress(DLL, "CExportWithTry")) {
    			::std::cout << "Funktion mit try-catch-Block geladen; mal ausführen:" << ::std::endl;
    
    			CExportWithTry(&allocatedMemoryInBytes);
    			::std::cout << "Die Welt ist nicht untergegangen und es sind " << allocatedMemoryInBytes << " B allokiert." << ::std::endl;
    
    			if(ImportedFunction CExportWithoutTry = (ImportedFunction)::GetProcAddress(DLL, "CExportWithoutTry")) {
    				::std::cout << "Funktion ohne try-catch-Block geladen; mal ausführen:" << ::std::endl;
    
    				try {
    					CExportWithoutTry(&allocatedMemoryInBytes);
    				} catch(...) {
    					::std::cout << "Schon wieder keine Apokalypse, dafür " << allocatedMemoryInBytes << " B allokiert." << ::std::endl;
    					return 0;
    				}
    
    			} // if CExportWithoutTry
    
    		} // if CExportWithTry
    
    	} // if DLL
    
    	return -1;
    }
    

    und

    // cl.exe dll.cpp /LD /EHsc
    #include <new>
    #include <Windows.h>
    
    size_t * toAllocatedMemoryInBytes = nullptr;
    
    void * operator new(size_t size) {
    	void * toNewMemory = ::HeapAlloc(::GetProcessHeap(), 0, size);
    	(*toAllocatedMemoryInBytes) += ::HeapSize(::GetProcessHeap(), 0, toNewMemory);
    	return toNewMemory;
    }
    
    void operator delete(void * toOldMemory) {
    	(*toAllocatedMemoryInBytes) -= ::HeapSize(::GetProcessHeap(), 0, toOldMemory);
    	::HeapFree(::GetProcessHeap(), 0, toOldMemory);
    }
    
    struct ThrowsOnConstruction {
    
    	ThrowsOnConstruction() {
    		throw 0;
    	}
    
    };
    
    extern "C" __declspec(dllexport) void CExportWithTry(size_t * toMemoryCounter) {
    	toAllocatedMemoryInBytes = toMemoryCounter;
    	try {
    		new ThrowsOnConstruction;
    	} catch(...) { }
    }
    
    extern "C" __declspec(dllexport) void CExportWithoutTry(size_t * toMemoryCounter) {
    	toAllocatedMemoryInBytes = toMemoryCounter;
    	new ThrowsOnConstruction;
    }
    

    Das erzeugt die Ausgabe:

    dll.dll geladen
    Funktion mit try-catch-Block geladen; mal ausf³hren:
    Die Welt ist nicht untergegangen und es sind 0 B allokiert.
    Funktion ohne try-catch-Block geladen; mal ausf³hren:
    Schon wieder keine Apokalypse, daf³r 1 B allokiert.

     

    Wo das nun geklärt wäre, zur ursprünglichen Frage zurück: Wo ist dieses Verhalten dokumentiert?

    Dienstag, 11. Januar 2011 11:56
  • OK! Eben habe ich das Problem erst richtig verstanden. Sorry für meine Begriffstutzigkeit.

    Es geht um die unvollständige Konstruktion des Objektes und die Entsorgung des allokierten Blocks.

    Das Problem ist, dass Du mit /EHsc eben sagt, dass die Funktion keine Excpetion wirft. D.h. es werden in diesem Funktionsblock auch keine try/catch Blöcke für unvollständige Konstruktionen eingebaut.

    Pack den Code in eine eigene Funktion und siehe da: Das unvollständig konstruierte Item wird weggeräumt.

    ThrowsOnConstruction *WrapIt()
    {
        return new ThrowsOnConstruction;
    }
    
    extern "C" ThrowsOnConstruction * badCFunction()
    {
        return *WrapIt();
    }

    In der main() soll ja auch ein catch-all sein; das habe ich ja oben korrigiert.

    PS: Ein Handler in main garantiert Dir nicht, dass die Excpetion gefangen wird. Das ist rein abhängig vom verwendeten Compiler...


    Martin Richter -- MVP for VC++ [Germany] -- http://blog.m-ri.de
    Dienstag, 11. Januar 2011 13:08
    Moderator
  • Ja, gut. Für mich sieht es mittlerweile auch so aus, als dass man sich dort eh auf wackeligem Terrain bewegt – /EHsc ist genau so kontra dem Sprachstandard wie das Deaktivieren von Stack Unwinding oder Laufzeittypinformation, also muss ich mich auch nicht wundern, wenn an so einer Stelle ein Speicherleck entsteht.

    Die mit Abstand beste Lösung ist, /EHs statt /EHsc zu benutzen, den Compiler extern "C"-Funktionen also als potentielle Emitter für Ausnahmen ansehen zu lassen. Falls das möglich ist.

    Die zweit- und drittbesten Lösungen sind dann, es in eine eigene Funktion auszulagern oder einen try-catch-Block drum herumzuspannen.

    So werde ich es dann auch tun, danke für die Hilfe.

    • Als Antwort markiert Krishty Donnerstag, 13. Januar 2011 19:15
    Dienstag, 11. Januar 2011 16:43
  • > Die mit Abstand beste Lösung ist, /EHs

    Ne... die mit Abstand beste Lösung ist /EHa
    Das ist die einzige Variante die alles unterstützt!

    http://msdn.microsoft.com/de-de/library/1deeycx5

    [quote]
    /EHa: Das Ausnahmebehandlungsmodell, das asynchrone (strukturierte) und synchrone (C++) Ausnahmen abfängt.
    [/quote]

    Alles mit /EHs kümmert sich nur um C++ Exceptions!


    Jochen Kalmbach (MVP VC++)
    Dienstag, 11. Januar 2011 17:01
  • Das kommt darauf an, wie nah man am Betriebssystem arbeitet … ich würde sagen, dass ein Großteil der guten C++-Anwendungen SEH-Bedarf entweder garnicht oder nur in engen, logisch isolierten Bereichen hat, die man eher in C schreibt. Und alles, was portabel oder standardkonform ist, kennt die sowieso nicht.
    Dienstag, 11. Januar 2011 17:23