Come funzionano le eccezioni (dietro le quinte) in c ++
-
08-07-2019 - |
Domanda
Continuo a vedere la gente dire che le eccezioni sono lente, ma non vedo mai alcuna prova. Quindi, invece di chiedere se lo sono, chiederò come funzionano le eccezioni dietro la scena, così posso prendere una decisione su quando usarle e se sono lente.
Da quello che so, le eccezioni sono la stessa cosa di fare un sacco di ritorno, ma controlla anche quando deve smettere di fare il ritorno. Come controlla quando fermarsi? Sto indovinando e dicendo che c'è un secondo stack che contiene il tipo di eccezione e la posizione dello stack quindi ritorna fino a quando non arriva. Sto anche immaginando che l'unica volta in cui lo stack è touch è un lancio e ogni tentativo / cattura. AFAICT che implementa un comportamento simile con il codice di ritorno richiederebbe lo stesso tempo. Ma questa è tutta una supposizione, quindi voglio saperlo.
Come funzionano davvero le eccezioni?
Soluzione
Invece di indovinare, ho deciso di esaminare il codice generato con un piccolo pezzo di codice C ++ e un'installazione Linux un po 'vecchia.
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);
}
L'ho compilato con g ++ -m32 -W -Wall -O3 -save-temps -c
e ho guardato il file assembly generato.
.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
è MyException :: ~ MyException ()
, quindi il compilatore ha deciso che aveva bisogno di una copia non in linea del distruttore.
.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 .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 .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"
, 8(%ebp)
jne .L21
leave
ret
.L21:
movl $1, (%esp)
call __cxa_allocate_exception
movl Invece di indovinare, ho deciso di esaminare il codice generato con un piccolo pezzo di codice C ++ e un'installazione Linux un po 'vecchia.
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);
}
L'ho compilato con g ++ -m32 -W -Wall -O3 -save-temps -c
e ho guardato il file assembly generato.
.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
è MyException :: ~ MyException ()
, quindi il compilatore ha deciso che aveva bisogno di una copia non in linea del distruttore.
.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 .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
, (%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:
Sorpresa! Non ci sono istruzioni aggiuntive sul normale percorso del codice. Il compilatore ha invece generato blocchi di codice di correzione extra-line extra, a cui fa riferimento una tabella alla fine della funzione (che in realtà è inserita in una sezione separata dell'eseguibile). Tutto il lavoro è svolto dietro le quinte dalla libreria standard, basata su queste tabelle ( _ZTI11MyException
è typeinfo per MyException
).
OK, non è stata una sorpresa per me, sapevo già come ha fatto questo compilatore. Continuando con l'output dell'assembly:
<*>
Qui vediamo il codice per generare un'eccezione. Sebbene non ci sia stato un sovraccarico extra semplicemente perché potrebbe essere generata un'eccezione, c'è ovviamente un sacco di overhead nel lanciare e catturare un'eccezione. La maggior parte è nascosta in __cxa_throw
, che deve:
- Cammina nello stack con l'aiuto delle tabelle delle eccezioni fino a quando non trova un gestore per tale eccezione.
- Svolgi la pila fino a quando non arriva a quel gestore.
- In realtà chiama il gestore.
Confrontalo con il costo della semplice restituzione di un valore e capisci perché le eccezioni dovrebbero essere utilizzate solo per rendimenti eccezionali.
Per finire, il resto del file assembly:
<*>
I dati typeinfo.
<*>
Ancora più tabelle di gestione delle eccezioni e informazioni extra assortite.
Quindi, la conclusione, almeno per GCC su Linux: il costo è lo spazio extra (per i gestori e le tabelle) indipendentemente dal fatto che vengano generate o meno eccezioni, oltre al costo aggiuntivo dell'analisi delle tabelle e dell'esecuzione dei gestori quando un'eccezione è gettato. Se si utilizzano eccezioni anziché codici di errore e un errore è raro, può essere più veloce , poiché non si ha più il sovraccarico del test degli errori.
Se desideri maggiori informazioni, in particolare cosa fanno tutte le funzioni __cxa_
, vedi le specifiche originali da cui provengono:
ZN11MyExceptionD1Ev, 8(%esp)
movl Invece di indovinare, ho deciso di esaminare il codice generato con un piccolo pezzo di codice C ++ e un'installazione Linux un po 'vecchia.
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);
}
L'ho compilato con g ++ -m32 -W -Wall -O3 -save-temps -c
e ho guardato il file assembly generato.
.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
è MyException :: ~ MyException ()
, quindi il compilatore ha deciso che aveva bisogno di una copia non in linea del distruttore.
.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 <*>, (%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:
Sorpresa! Non ci sono istruzioni aggiuntive sul normale percorso del codice. Il compilatore ha invece generato blocchi di codice di correzione extra-line extra, a cui fa riferimento una tabella alla fine della funzione (che in realtà è inserita in una sezione separata dell'eseguibile). Tutto il lavoro è svolto dietro le quinte dalla libreria standard, basata su queste tabelle ( _ZTI11MyException
è typeinfo per MyException
).
OK, non è stata una sorpresa per me, sapevo già come ha fatto questo compilatore. Continuando con l'output dell'assembly:
<*>
Qui vediamo il codice per generare un'eccezione. Sebbene non ci sia stato un sovraccarico extra semplicemente perché potrebbe essere generata un'eccezione, c'è ovviamente un sacco di overhead nel lanciare e catturare un'eccezione. La maggior parte è nascosta in __cxa_throw
, che deve:
- Cammina nello stack con l'aiuto delle tabelle delle eccezioni fino a quando non trova un gestore per tale eccezione.
- Svolgi la pila fino a quando non arriva a quel gestore.
- In realtà chiama il gestore.
Confrontalo con il costo della semplice restituzione di un valore e capisci perché le eccezioni dovrebbero essere utilizzate solo per rendimenti eccezionali.
Per finire, il resto del file assembly:
<*>
I dati typeinfo.
<*>
Ancora più tabelle di gestione delle eccezioni e informazioni extra assortite.
Quindi, la conclusione, almeno per GCC su Linux: il costo è lo spazio extra (per i gestori e le tabelle) indipendentemente dal fatto che vengano generate o meno eccezioni, oltre al costo aggiuntivo dell'analisi delle tabelle e dell'esecuzione dei gestori quando un'eccezione è gettato. Se si utilizzano eccezioni anziché codici di errore e un errore è raro, può essere più veloce , poiché non si ha più il sovraccarico del test degli errori.
Se desideri maggiori informazioni, in particolare cosa fanno tutte le funzioni __cxa_
, vedi le specifiche originali da cui provengono:
ZTI11MyException, 4(%esp)
movl %eax, (%esp)
call __cxa_throw
.LFE8:
.size _Z20my_throwing_functionb, .-_Z20my_throwing_functionb
, (%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:
Sorpresa! Non ci sono istruzioni aggiuntive sul normale percorso del codice. Il compilatore ha invece generato blocchi di codice di correzione extra-line extra, a cui fa riferimento una tabella alla fine della funzione (che in realtà è inserita in una sezione separata dell'eseguibile). Tutto il lavoro è svolto dietro le quinte dalla libreria standard, basata su queste tabelle ( _ZTI11MyException
è typeinfo per MyException
).
OK, non è stata una sorpresa per me, sapevo già come ha fatto questo compilatore. Continuando con l'output dell'assembly:
<*> Qui vediamo il codice per generare un'eccezione. Sebbene non ci sia stato un sovraccarico extra semplicemente perché potrebbe essere generata un'eccezione, c'è ovviamente un sacco di overhead nel lanciare e catturare un'eccezione. La maggior parte è nascosta in __cxa_throw
, che deve:
- Cammina nello stack con l'aiuto delle tabelle delle eccezioni fino a quando non trova un gestore per tale eccezione.
- Svolgi la pila fino a quando non arriva a quel gestore.
- In realtà chiama il gestore.
Confrontalo con il costo della semplice restituzione di un valore e capisci perché le eccezioni dovrebbero essere utilizzate solo per rendimenti eccezionali.
Per finire, il resto del file assembly:
<*>I dati typeinfo.
<*>Ancora più tabelle di gestione delle eccezioni e informazioni extra assortite.
Quindi, la conclusione, almeno per GCC su Linux: il costo è lo spazio extra (per i gestori e le tabelle) indipendentemente dal fatto che vengano generate o meno eccezioni, oltre al costo aggiuntivo dell'analisi delle tabelle e dell'esecuzione dei gestori quando un'eccezione è gettato. Se si utilizzano eccezioni anziché codici di errore e un errore è raro, può essere più veloce , poiché non si ha più il sovraccarico del test degli errori.
Se desideri maggiori informazioni, in particolare cosa fanno tutte le funzioni __cxa_
, vedi le specifiche originali da cui provengono:
Altri suggerimenti
Le eccezioni sono lente era vero ai vecchi tempi.
Nella maggior parte dei compilatori moderni questo non è più vero.
Nota: solo perché abbiamo delle eccezioni non significa che non utilizziamo anche i codici di errore. Quando l'errore può essere gestito localmente, utilizzare i codici di errore. Quando gli errori richiedono più contesto per la correzione, utilizzare le eccezioni: l'ho scritto molto più eloquentemente qui: Quali sono i principi che guidano la tua politica di gestione delle eccezioni?
Il costo del codice di gestione delle eccezioni quando non vengono utilizzate eccezioni è praticamente zero.
Quando viene generata un'eccezione, viene svolto del lavoro.
Ma devi confrontarlo con il costo di restituzione dei codici di errore e controllandoli fino al punto in cui è possibile gestire l'errore. Entrambi richiedono più tempo per scrivere e mantenere.
Inoltre c'è un gotcha per i principianti:
Sebbene gli oggetti Exception debbano essere piccoli, alcune persone inseriscono molte cose al loro interno. Quindi hai il costo di copiare l'oggetto eccezione. La soluzione è duplice:
- Non mettere cose extra nella tua eccezione.
- Cattura per riferimento const.
A mio avviso, scommetterei che lo stesso codice con le eccezioni è più efficiente o almeno paragonabile al codice senza le eccezioni (ma ha tutto il codice aggiuntivo per controllare i risultati dell'errore di funzione). Ricorda che non stai ottenendo nulla gratuitamente il compilatore sta generando il codice che dovresti aver scritto in primo luogo per controllare i codici di errore (e di solito il compilatore è molto più efficiente di un essere umano).
Esistono diversi modi in cui è possibile implementare le eccezioni, ma in genere si baseranno su un supporto sottostante dal sistema operativo. Su Windows questo è il meccanismo strutturato di gestione delle eccezioni.
C'è una discreta discussione dei dettagli su Code Project: Come un compilatore C ++ implementa la gestione delle eccezioni
Il sovraccarico delle eccezioni si verifica perché il compilatore deve generare codice per tenere traccia di quali oggetti devono essere distrutti in ogni frame dello stack (o più precisamente nell'ambito) se un'eccezione si propaga al di fuori di tale ambito. Se una funzione non ha variabili locali nello stack che richiedono di chiamare i distruttori, non dovrebbe avere una gestione delle eccezioni rispetto alla penalità di prestazione.
L'uso di un codice di ritorno può svolgere solo un singolo livello dello stack alla volta, mentre un meccanismo di gestione delle eccezioni può saltare molto più indietro nello stack in un'unica operazione se non c'è nulla da fare nei frame intermedi dello stack.
Matt Pietrek ha scritto un eccellente articolo su Gestione delle eccezioni strutturata Win32 . Mentre questo articolo è stato originariamente scritto nel 1997, si applica ancora oggi (ma ovviamente si applica solo a Windows).
Questo articolo esamina il problema e fondamentalmente scopre che in pratica esiste una corsa- costo delle eccezioni, anche se il costo è piuttosto basso se l'eccezione non viene generata. Buon articolo, consigliato.
Un mio amico ha scritto un po 'come Visual C ++ gestisce le eccezioni alcuni anni fa.
Tutte le risposte valide.
Inoltre, pensa a quanto è più facile eseguire il debug del codice che fa "se verifica" come porte in cima ai metodi invece di consentire al codice di generare eccezioni.
Il mio motto è che è facile scrivere codice che funzioni. La cosa più importante è scrivere il codice per la prossima persona che lo guarda. In alcuni casi, sei tu tra 9 mesi e non vuoi maledire il tuo nome!