131 votes

Comment fonctionnent les exceptions (en coulisse) en c ++

Je continue à voir des gens dire que les exceptions sont lents, mais je ne vois jamais aucune preuve. Donc, au lieu de se demander s'ils sont je vais demander comment faire des exceptions travail derrière la scène pour que je puisse faire une des décisions quand les utiliser et s'ils sont lents.

De ce que je sais exceptions sont la même chose que de faire un tas de retour, mais il vérifie aussi quand il doit arrêter de faire le retour. Comment est-il vérifier quand le faire arrêter? Je prends une supposition et disant qu'il y a une deuxième pile, qui détient le type de l'exception et de l'emplacement de pile alors ne retourne jusqu'à ce qu'il y arrive. Je suis également deviner la seule fois que la pile est touch est sur un lancer et à chaque try/catch. AFAICT mise en œuvre d'un comportement similaire avec le code de retour prendrait la même quantité de temps. Mais ce n'est qu'une supposition donc, je veux savoir.

Comment faire des exceptions fonctionnent vraiment?

119voto

CesarB Points 18048

Au lieu de deviner, j'ai décidé de vraiment regarder le code généré avec un petit morceau de code C++ et un peu ancien installation de Linux.

class MyException
{
public:
    MyException() { }
    ~MyException() { }
};

void my_throwing_function(bool throwit)
{
    if (throwit)
    	throw MyException();
}

void another_function();
void log(unsigned count);

void my_catching_function()
{
    log(0);
    try
    {
    	log(1);
    	another_function();
    	log(2);
    }
    catch (const MyException& e)
    {
    	log(3);
    }
    log(4);
}

J'ai compilé avec g++ -m32 -W -Wall -O3 -save-temps -c, et regarda l'assembly généré fichier.

    .file	"foo.cpp"
    .section	.text._ZN11MyExceptionD1Ev,"axG",@progbits,_ZN11MyExceptionD1Ev,comdat
    .align 2
    .p2align 4,,15
    .weak	_ZN11MyExceptionD1Ev
    .type	_ZN11MyExceptionD1Ev, @function
_ZN11MyExceptionD1Ev:
.LFB7:
    pushl	%ebp
.LCFI0:
    movl	%esp, %ebp
.LCFI1:
    popl	%ebp
    ret
.LFE7:
    .size	_ZN11MyExceptionD1Ev, .-_ZN11MyExceptionD1Ev

_ZN11MyExceptionD1Ev est MyException::~MyException(), de sorte que le compilateur a décidé qu'il avait besoin d'un non-inline copie du destructeur.

.globl __gxx_personality_v0
.globl _Unwind_Resume
    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_catching_functionv
    .type	_Z20my_catching_functionv, @function
_Z20my_catching_functionv:
.LFB9:
    pushl	%ebp
.LCFI2:
    movl	%esp, %ebp
.LCFI3:
    pushl	%ebx
.LCFI4:
    subl	$20, %esp
.LCFI5:
    movl	$0, (%esp)
.LEHB0:
    call	_Z3logj
.LEHE0:
    movl	$1, (%esp)
.LEHB1:
    call	_Z3logj
    call	_Z16another_functionv
    movl	$2, (%esp)
    call	_Z3logj
.LEHE1:
.L5:
    movl	$4, (%esp)
.LEHB2:
    call	_Z3logj
    addl	$20, %esp
    popl	%ebx
    popl	%ebp
    ret
.L12:
    subl	$1, %edx
    movl	%eax, %ebx
    je	.L16
.L14:
    movl	%ebx, (%esp)
    call	_Unwind_Resume
.LEHE2:
.L16:
.L6:
    movl	%eax, (%esp)
    call	__cxa_begin_catch
    movl	$3, (%esp)
.LEHB3:
    call	_Z3logj
.LEHE3:
    call	__cxa_end_catch
    .p2align 4,,3
    jmp	.L5
.L11:
.L8:
    movl	%eax, %ebx
    .p2align 4,,6
    call	__cxa_end_catch
    .p2align 4,,6
    jmp	.L14
.LFE9:
    .size	_Z20my_catching_functionv, .-_Z20my_catching_functionv
    .section	.gcc_except_table,"a",@progbits
    .align 4
.LLSDA9:
    .byte	0xff
    .byte	0x0
    .uleb128 .LLSDATT9-.LLSDATTD9
.LLSDATTD9:
    .byte	0x1
    .uleb128 .LLSDACSE9-.LLSDACSB9
.LLSDACSB9:
    .uleb128 .LEHB0-.LFB9
    .uleb128 .LEHE0-.LEHB0
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB1-.LFB9
    .uleb128 .LEHE1-.LEHB1
    .uleb128 .L12-.LFB9
    .uleb128 0x1
    .uleb128 .LEHB2-.LFB9
    .uleb128 .LEHE2-.LEHB2
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB3-.LFB9
    .uleb128 .LEHE3-.LEHB3
    .uleb128 .L11-.LFB9
    .uleb128 0x0
.LLSDACSE9:
    .byte	0x1
    .byte	0x0
    .align 4
    .long	_ZTI11MyException
.LLSDATT9:

Surprise! Il n'y a pas les instructions supplémentaires sur la normale chemin d'accès du code. Le compilateur plutôt générés supplémentaire hors de la ligne de correction de blocs de code, référencé par l'intermédiaire d'un tableau à la fin de la fonction (ce qui est effectivement mis dans une section séparée de l'exécutable). Tout le travail est fait dans les coulisses par la bibliothèque standard, sur la base de ces tableaux (_ZTI11MyException est typeinfo for MyException).

OK, ce n'était pas vraiment une surprise pour moi, je savais déjà comment ce compilateur n'. En continuant avec l'assemblée de sortie:

    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_throwing_functionb
    .type	_Z20my_throwing_functionb, @function
_Z20my_throwing_functionb:
.LFB8:
    pushl	%ebp
.LCFI6:
    movl	%esp, %ebp
.LCFI7:
    subl	$24, %esp
.LCFI8:
    cmpb	$0, 8(%ebp)
    jne	.L21
    leave
    ret
.L21:
    movl	$1, (%esp)
    call	__cxa_allocate_exception
    movl	$_ZN11MyExceptionD1Ev, 8(%esp)
    movl	$_ZTI11MyException, 4(%esp)
    movl	%eax, (%esp)
    call	__cxa_throw
.LFE8:
    .size	_Z20my_throwing_functionb, .-_Z20my_throwing_functionb

Ici, nous voyons le code pour lancer une exception. Il n'y a aucune surcharge simplement parce que une exception peut être levée, il y a évidemment beaucoup de frais généraux de réellement lancer et attraper une exception. La plupart des il est caché dans __cxa_throwqui doit:

  • À pied de la pile avec l'aide de l'exception des tables jusqu'à ce qu'il trouve un gestionnaire pour que l'exception.
  • Détendez-vous de la pile jusqu'à ce qu'il arrive à ce gestionnaire.
  • En fait l'appel du gestionnaire.

Comparer le coût de simplement retourner une valeur, et vous voyez pourquoi des exceptions doit être utilisé uniquement pour un rendement exceptionnel.

Pour finir, le reste de l'assemblée fichier:

    .weak	_ZTI11MyException
    .section	.rodata._ZTI11MyException,"aG",@progbits,_ZTI11MyException,comdat
    .align 4
    .type	_ZTI11MyException, @object
    .size	_ZTI11MyException, 8
_ZTI11MyException:
    .long	_ZTVN10__cxxabiv117__class_type_infoE+8
    .long	_ZTS11MyException
    .weak	_ZTS11MyException
    .section	.rodata._ZTS11MyException,"aG",@progbits,_ZTS11MyException,comdat
    .type	_ZTS11MyException, @object
    .size	_ZTS11MyException, 14
_ZTS11MyException:
    .string	"11MyException"

Le typeinfo de données.

    .section	.eh_frame,"a",@progbits
.Lframe1:
    .long	.LECIE1-.LSCIE1
.LSCIE1:
    .long	0x0
    .byte	0x1
    .string	"zPL"
    .uleb128 0x1
    .sleb128 -4
    .byte	0x8
    .uleb128 0x6
    .byte	0x0
    .long	__gxx_personality_v0
    .byte	0x0
    .byte	0xc
    .uleb128 0x4
    .uleb128 0x4
    .byte	0x88
    .uleb128 0x1
    .align 4
.LECIE1:
.LSFDE3:
    .long	.LEFDE3-.LASFDE3
.LASFDE3:
    .long	.LASFDE3-.Lframe1
    .long	.LFB9
    .long	.LFE9-.LFB9
    .uleb128 0x4
    .long	.LLSDA9
    .byte	0x4
    .long	.LCFI2-.LFB9
    .byte	0xe
    .uleb128 0x8
    .byte	0x85
    .uleb128 0x2
    .byte	0x4
    .long	.LCFI3-.LCFI2
    .byte	0xd
    .uleb128 0x5
    .byte	0x4
    .long	.LCFI5-.LCFI3
    .byte	0x83
    .uleb128 0x3
    .align 4
.LEFDE3:
.LSFDE5:
    .long	.LEFDE5-.LASFDE5
.LASFDE5:
    .long	.LASFDE5-.Lframe1
    .long	.LFB8
    .long	.LFE8-.LFB8
    .uleb128 0x4
    .long	0x0
    .byte	0x4
    .long	.LCFI6-.LFB8
    .byte	0xe
    .uleb128 0x8
    .byte	0x85
    .uleb128 0x2
    .byte	0x4
    .long	.LCFI7-.LCFI6
    .byte	0xd
    .uleb128 0x5
    .align 4
.LEFDE5:
    .ident	"GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
    .section	.note.GNU-stack,"",@progbits

Encore plus la gestion des exceptions tables, et un assortiment d'informations supplémentaires.

Donc, la conclusion, au moins pour GCC sous Linux: le coût est de l'espace supplémentaire (pour les gestionnaires et les tables) si oui ou non les exceptions sont jetés, plus le coût supplémentaire de l'analyse des tableaux et de l'exécution des gestionnaires d'lorsqu'une exception est levée. Si vous utilisez des exceptions au lieu de codes d'erreur, et une erreur est rare, il peut être plus rapide, car vous n'avez pas la surcharge de tests pour les erreurs plus.

Dans le cas où vous souhaitez plus d'informations, en particulier, que tous les __cxa_ fonctions, voir la spécification d'origine ils sont venus à partir de:

14voto

Loki Astari Points 116129

Les Exceptions de la lenteur était vrai dans les vieux jours.
Dans la plupart des modernes compilateur ce n'est plus vrai.

Note: Juste parce que nous avons des exceptions ne signifie pas que nous ne pas utiliser des codes d'erreur. Quand l'erreur peut être traité localement utiliser des codes d'erreur. Lorsque des erreurs nécessitent plus de contexte pour la correction de l'utilisation des exceptions: je l'ai écrit beaucoup plus d'éloquence ici: http://stackoverflow.com/questions/106586/what-are-the-principles-guiding-your-exception-handling-policy#106749

Le coût de code de gestion des exceptions lorsque, sans exception, sont en cours d'utilisation est pratiquement nulle.

Lorsqu'une exception est lancée il y a un peu de travail.
Mais il faut comparer ce contre les frais de retour pour les codes d'erreur et la vérification de tous le chemin du retour vers le point où l'erreur peut être gérée. Fois plus de temps à écrire et à maintenir.

Il y a aussi une chasse aux sorcières pour les novices:
Si les objets d'Exception sont censés être des petits certaines personnes mettent des tas de trucs à l'intérieur. Ensuite, vous avez le coût de la copie de l'objet de l'exception. La seule solution est de deux ordres:

  • Ne mettez pas de trucs supplémentaires dans votre exception.
  • Attraper par référence const.

À mon avis, je serais prêt à parier que le même code avec des exceptions soit plus efficace, ou tout au moins comparable comme le code sans les exceptions (mais a tout le code de la fonction de contrôle d'erreur des résultats). Rappelez-vous que vous ne pas obtenir quelque chose gratuitement le compilateur génère le code, vous devriez avoir écrit à la première place pour vérifier les codes d'erreur (et normalement le compilateur est beaucoup plus efficace qu'un humain).

12voto

Rob Walker Points 25840

Il y a un certain nombre de façons dont vous pourriez mettre en œuvre des exceptions, mais en général, ils vont s'appuyer sur un support sous-jacent de l'OS. Sur Windows c'est de la gestion structurée des exceptions mécanisme.

Il est décent de discussion des détails sur le Code du Projet: Comment un compilateur C++ implémente la gestion des exceptions

La surcharge des exceptions se produit parce que le compilateur doit générer du code pour suivre les objets doivent être détruits à chaque frame de pile (ou plus précisément le champ d'application) si une exception se propage hors du champ d'application. Si une fonction n'a pas de variables locales dans la pile qui nécessitent des destructeurs d'être appelé alors il ne devrait pas avoir une perte de performance wrt gestion des exceptions.

À l'aide d'un code de retour ne peut se détendre un seul niveau de la pile à l'heure, alors que l'exception d'un mécanisme de gestion peut sauter beaucoup plus en arrière le bas de la pile en une seule opération, si il n'y a rien à faire dans l'intermédiaire pile d'images.

6voto

Greg Hewgill Points 356191

Matt Pietrek a écrit un excellent article sur Win32 Structured Exception Handling. Bien que cet article a été écrit en 1997, il s’applique encore aujourd'hui (mais bien sûr s’applique uniquement à Windows).

5voto

Alastair Points 2491

Cet article examine la question et conclut essentiellement que dans la pratique il y a un coût d’exécution sur des exceptions, même si le coût est relativement faible si l’exception n’est pas levée. Bon article, recommandé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X