[C/C++] throw in funktion?

posi90

Erfahrenes Mitglied
Hallo,

Habe mich etwas in die try catch throw Sache reingelesen. Und würde gerne wissen ob ein throw in einer Funktion den Stack verletzt.

Funktioniert das wie ein goto?

C++:
int f(void)
{
	throw "Error!";
	return 0;
}

int main(void)
{
	try
	{
		f();
	}
	catch(char *error)
	{
		printf("%s", error);
	}
	
	return 0;
}

In asm funktioniert eine Funktion doch so:
C++:
main:	
	call funktion;	// hier wird die Rücksprungaddresse in den Stack gespeichert	
end

funktion:
	INC R0;
					// was passiert wenn hier in C ein throw aufgerufen wird? 
					// wird die Rücksprungaddresse im Stack gelassen?
	RET;			// hier wird die Rücksprungaddresse vom Stack
					// geholt und nach oben zurückgesprungen (return in C)

Oder ist es besser alles mit IF abzufangen?
 
Hallo,

Nein throw beschädigt den stack nicht.

throw ist intern n bisl komplizierter. Ich versuch es mal etwas zu beschreiben, jedoch bin ich kein compiler developer, und habe dementsprechend selber nur groben einblick in das was da eigendlich vorgeht. Es ist also durchaus möglich, dass einige details hier etwas von der realität abweichen, aber es geht ja erstmal nur um das grundprinzip.

hier mal der Assemblercode der aus deinem beispiel resultiert:

Code:
        .file   "test.cpp"
        .section        .rodata
.LC0:
        .string "Error!"
        .text
.globl _Z1fv
        .type   _Z1fv, @function
_Z1fv:
.LFB0:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        movl    $8, %edi
        call    __cxa_allocate_exception
        movq    %rax, %rdx
        movq    $.LC0, (%rdx)
        movl    $0, %edx
        movl    $_ZTIPKc, %esi
        movq    %rax, %rdi
        call    __cxa_throw
        .cfi_endproc
.LFE0:
        .size   _Z1fv, .-_Z1fv
        .section        .rodata
.LC1:
        .string "%s"
.globl _Unwind_Resume
        .text
.globl main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        .cfi_lsda 0x3,.LLSDA1
        pushq   %rbp
        .cfi_def_cfa_offset 16
        movq    %rsp, %rbp
        .cfi_offset 6, -16
        .cfi_def_cfa_register 6
        pushq   %r12
        pushq   %rbx
        subq    $16, %rsp
.LEHB0:
        .cfi_offset 3, -32
        .cfi_offset 12, -24
        call    _Z1fv
.LEHE0:
        jmp     .L4
.L11:
        cmpq    $1, %rdx
        je      .L5
        movq    %rax, %rdi
.LEHB1:
        call    _Unwind_Resume
.LEHE1:
.L5:
        movq    %rax, %rdi
        call    __cxa_begin_catch
        movq    %rax, -24(%rbp)
        movq    -24(%rbp), %rax
        movq    %rax, %rsi
        movl    $.LC1, %edi
        movl    $0, %eax
.LEHB2:
        call    printf
.LEHE2:
        jmp     .L12
.L10:
.L7:
        movl    %edx, %ebx
        movq    %rax, %r12
        call    __cxa_end_catch
        movq    %r12, %rax
        movslq  %ebx,%rdx
        movq    %rax, %rdi
.LEHB3:
        call    _Unwind_Resume
.L12:
        call    __cxa_end_catch
.LEHE3:
.L4:
        movl    $0, %eax
        addq    $16, %rsp
        popq    %rbx
        popq    %r12
        leave
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
.globl __gxx_personality_v0
        .section        .gcc_except_table,"a",@progbits
        .align 4
.LLSDA1:
        .byte   0xff
        .byte   0x3
        .uleb128 .LLSDATT1-.LLSDATTD1
.LLSDATTD1:
        .byte   0x1
        .uleb128 .LLSDACSE1-.LLSDACSB1
.LLSDACSB1:
        .uleb128 .LEHB0-.LFB1
        .uleb128 .LEHE0-.LEHB0
        .uleb128 .L11-.LFB1
        .uleb128 0x1
        .uleb128 .LEHB1-.LFB1
        .uleb128 .LEHE1-.LEHB1
        .uleb128 0x0
        .uleb128 0x0
        .uleb128 .LEHB2-.LFB1
        .uleb128 .LEHE2-.LEHB2
        .uleb128 .L10-.LFB1
        .uleb128 0x0
        .uleb128 .LEHB3-.LFB1
        .uleb128 .LEHE3-.LEHB3
        .uleb128 0x0
        .uleb128 0x0
.LLSDACSE1:
        .byte   0x1
        .byte   0x0
        .align 4
        .long   _ZTIPc
.LLSDATT1:
        .text
        .ident  "GCC: (Gentoo 4.4.5 p1.2, pie-0.4.5) 4.4.5"
        .section        .note.GNU-stack,"",@progbits

Die magie von throw steckt grossteils in der funktion __cxa_throw.

Wie du sicher weisst ist ein funktionsaufruf nicht mir einem call erledigt.
Auch wenn man in assembler in vielen fällen sich das ganze drum herum spaart, der C++ Compiler macht dies auf jedenfall sauber.

ein funktionsaufruf aus sicht des C Compiler läuft im wesendlichen (stark vereinfacht) so ab:
1.) call
2.) call speichert automatisch die rücksprungaddresse auf den stack
3.) kopieren des stack-pointers in das base-pointer register
4.) pushen aller register die durch die funktion verändert werden.
5.) decrementieren des stack pointers um platz für lokale variablen zu schaffen.

dieser speicherbereich der sich nun zwischen %ESP und %EBP bildet wird als stackframe bezeichnet.

Um das ganze bei einem throw nun sauber aufzurollen, braucht man also nur basepointer in stackpointer verschieben, und den neuen basepointer vom stack lesen.
Das ganze wiederholt man solange, bis die rücksprungaddresse innerhalb eines vorher irgendwo definierten speicherbeichs liegt.

Das die daten selber weiter im speicherbereich des stacks stehen, ist irrelevant, da der speicherbereich unterhalb des stackpointers als "frei" betrachtet wird. Man muss also nicht zwangsläufig mit pop oder ret werte vom stack holen. Ein modifizieren des stackpointers reicht aus.

Nun wird überprüft ob die catch bedingung zutrifft. Wenn ja wird der code ausgeführt. Wenn nein wird _Unwind_Resume aufgerufen, und die exception fliegt weiter den (call)stack nach oben, bis sie auf einen passenden catch block trifft, oder der stack leer ist.

Nichts viel anders arbeiten übrigens debugger wenn sie einen callstack anzeigen.
 
Zuletzt bearbeitet:
Danke für die ausführliche und zeitaufwendige Antwort!

Hast mir sehr weitergeholfen.

Gruß posi90
 
Zurück