3 回答
TA贡献1817条经验 获得超6个赞
不用猜测,我决定实际上是用一小段C ++代码和稍旧的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);
}
我使用进行了编译g++ -m32 -W -Wall -O3 -save-temps -c,然后查看了生成的程序集文件。
.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(),因此编译器决定需要析构函数的非内联副本。
.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:
惊喜!正常代码路径上根本没有多余的指令。相反,编译器生成了额外的离线修正代码块,这些代码块通过函数末尾的表引用(实际上放在可执行文件的单独部分中)。所有工作都是由标准库在后台基于这些表(_ZTI11MyExceptionis typeinfo for MyException)完成的。
好吧,这实际上对我来说并不令人惊讶,我已经知道该编译器是如何做到的。继续汇编输出:
.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
在这里,我们看到了引发异常的代码。尽管没有仅仅因为可能引发异常而产生了额外的开销,但是在实际引发和捕获异常方面显然存在很多开销。其中大多数隐藏在中__cxa_throw,该必须:
在异常表的帮助下遍历堆栈,直到找到该异常的处理程序为止。
展开堆栈,直到到达该处理程序为止。
实际调用处理程序。
将其与仅返回值的成本进行比较,您会看到为什么仅将异常用于特殊收益的原因。
最后,汇编文件的其余部分:
.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"
typeinfo数据。
.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
甚至更多的异常处理表,以及各种额外的信息。
因此,至少对于Linux上的GCC,得出的结论是:无论是否引发异常,开销都是额外的空间(用于处理程序和表),加上在引发异常时解析表并执行处理程序的额外开销。如果使用异常而不是错误代码,并且错误很少见,则错误速度会更快,因为您不再需要进行错误测试。
TA贡献1842条经验 获得超21个赞
例外是缓是在旧时代真的。
在大多数现代编译器中,这不再成立。
注意:仅仅因为我们有例外并不意味着我们也不会使用错误代码。如果可以在本地处理错误,请使用错误代码。当错误需要更多上下文来更正时,请使用异常:我在这里雄辩地写道:指导异常处理策略的原则是什么?
当不使用任何异常时,异常处理代码的成本实际上为零。
引发异常时,将完成一些工作。
但是您必须将其与返回错误代码并一路检查它们以指出可以处理错误的位置的开销进行比较。两者都花费更多的时间来编写和维护。
对于新手来说也有一个陷阱:
尽管Exception对象应该很小,但是有些人却在其中放了很多东西。然后,您需要复制异常对象。解决方案有两个方面:
不要把多余的东西放在例外中。
通过const引用捕获。
在我看来,我敢打赌,带有例外的同一代码将比没有例外的代码更有效率,或者至少具有可比性(但具有检查功能错误结果的所有额外代码)。请记住,您没有免费获得任何东西,编译器正在生成您应首先编写的用于检查错误代码的代码(通常,编译器比人类更有效)。
TA贡献1831条经验 获得超9个赞
有多种方法可以实现异常,但是通常它们将依赖于操作系统的某些基础支持。在Windows上,这是结构化异常处理机制。
有关代码项目的详细信息,进行了不错的讨论:C ++编译器如何实现异常处理
发生异常的开销是因为,如果异常传播到该范围之外,则编译器必须生成代码来跟踪必须在每个堆栈帧(或更确切地说是范围)中销毁哪些对象。如果函数在堆栈上没有需要调用析构函数的局部变量,则它不应因异常处理而降低性能。
使用返回码一次只能解开堆栈的单个级别,而如果在中间堆栈帧中无事可做,则异常处理机制可以在一次操作中进一步跳回堆栈。
添加回答
举报