C ++异常处理添加了多少占用空间

时间:2009-03-27 19:23:52

标签: c++ exception exception-handling embedded footprint

此问题对于嵌入式开发尤为重要。异常处理为生成的二进制输出增加了一些空间。另一方面,没有例外,错误需要以其他方式处理,这需要额外的代码,这最终也会增加二进制大小。

我对你的经历感兴趣,特别是:

  1. 编译器为异常处理添加的平均占用空间是多少(如果有这样的测量值)?
  2. 就二进制输出大小而言,异常处理是否真的比其他错误处理策略更昂贵(很多人说)?
  3. 您对嵌入式开发建议采用什么错误处理策略?
  4. 请仅以我的问题为指导。欢迎任何意见。

    附录:是否有任何人拥有一个具体的方法/脚本/工具,对于特定的C ++对象/可执行文件,它将显示由编译器生成的代码和专用于异常处理的数据结构占用的已加载内存占用的百分比?

8 个答案:

答案 0 :(得分:35)

当发生异常时,会有时间开销,这取决于您实现异常处理的方式。但是,作为轶事,应该导致异常的事件的严重性将花费同样多的时间来处理使用任何其他方法。为什么不使用高度支持的基于语言的方法来处理这些问题?

GNU C ++编译器默认使用零成本模型,即不发生异常时没有时间开销。

  

由于有关异常处理代码和本地对象偏移的信息可以在编译时计算一次,因此这些信息可以保存在与每个函数关联的单个位置,但不能保存在每个ARI中。您基本上可以从每个ARI中删除异常开销,从而避免将额外时间推送到堆栈上。这种方法称为异常处理的零成本模型,前面提到的优化存储称为影子堆栈。 - 布鲁斯·埃克尔,思考C ++第2卷

尺寸复杂性开销不容易量化,但Eckel平均表示5%和15%。这取决于异常处理代码的大小与应用程序代码大小的比率。如果你的程序很小,那么异常将是二进制文件的很大一部分。如果您使用零成本模型而不是异常会占用更多空间来消除时间开销,因此如果您关心空间而不是时间而不是使用零成本编译。

我的意见是大多数嵌入式系统都有足够的内存,如果你的系统有一个C ++编译器,你有足够的空间来包含异常。我的项目使用的PC / 104计算机有几GB的二级存储器,512 MB的主存储器,因此异常没有空间问题 - 但是,我们的微控制器是用C编程的。我的启发式是“如果有一个主流的C ++编译器它,使用异常,否则使用C“。

答案 1 :(得分:20)

测量事物,第2部分。我现在有两个程序。第一个是在C中,用gcc -O2编译:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            return -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        if ( (z = f(i)) == -1 ) { 
            break;
        }
    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

第二个是C ++,带有异常处理,用g ++ -O2编译:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            throw -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        try {
         z += f(i); 
        }
        catch( ... ) {
            break;
        }

    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

我认为这些回答了我上一篇文章的所有批评。

结果:执行时间使C版本比C ++版本有0.5%的优势,但没有其他人谈过的10%(但没有演示)

如果其他人可以尝试编译并运行代码(应该只需要几分钟),我会非常感激,以便检查我在任何地方都没有犯过一个可怕而明显的错误。这被称为“科学方法”!

答案 2 :(得分:5)

我在低延迟环境中工作。 (对于我在“生产链”中的应用程序来说,超过300微秒)根据我的经验,异常处理会增加5-25%的执行时间,具体取决于您的工作量!

我们一般不关心二元膨胀,但是如果你得到太多臃肿然后你就像疯了一样捶打,所以你需要小心。

保持二进制合理(取决于您的设置)。

我对我的系统进行了大量的分析 其他令人讨厌的地区:

登录

坚持(我们不做这个,或者如果我们这样做的话)

答案 3 :(得分:4)

我想这取决于该特定平台的硬件和工具链端口。

我没有数据。然而,对于大多数嵌入式开发,我看到人们放弃了两件事(对于VxWorks / GCC工具链):

  • 模板
  • RTTI

在大多数情况下,异常处理都会使用这两种情况,因此也有将其抛弃的倾向。

在我们真正想接近金属的情况下,使用setjmp / longjmp请注意,这可能不是最好的解决方案(或非常强大),但那就是_we_使用的。

您可以使用两个版本的基准测试套件在桌面上运行简单测试,无需异常处理,并获取您最依赖的数据。

关于嵌入式开发的另一个问题是:模板可以像瘟疫一样被避免 - 它们会导致过多的膨胀。正如Johann Gerell在评论中解释的那样,模板和RTTI上的例外标记(我认为这很好理解)。

同样,这正是我们所做的。什么是所有的downvoting?

答案 4 :(得分:4)

要考虑的一件事:如果您在嵌入式环境中工作,则希望尽可能减少应用程序。 Microsoft C Runtime为程序增加了相当多的开销。通过删除C运行时作为一个要求,我能够得到一个简单的程序,成为一个2KB的exe文件,而不是一个70多字节的千字节文件,并且所有的大小优化都打开了。

C ++异常处理需要编译器支持,由C运行时提供。具体细节笼罩在神秘之中,根本没有记载。通过避免C ++异常,我可以删除整个C运行时库。

你可能会争论只是动态链接,但在我看来这是不切实际的。

另一个问题是C ++异常至少在MSVC上需要有限的RTTI(运行时类型信息),这意味着异常的类型名称存储在可执行文件中。在空间方面,这不是一个问题,但在文件中没有这些信息对我来说感觉更“干净”。

答案 5 :(得分:1)

在我看来,异常处理并不是嵌入式开发通常可以接受的。

GCC和Microsoft都没有“零开销”异常处理。两个编译器都将序言和结尾语句插入到跟踪执行范围的每个函数中。这导致性能和内存占用量的显着增加。

在我的经验中,性能差异大约为10%,对于我的工作领域(实时图形)来说,这是一个巨大的数额。内存开销远远少于但仍然很重要 - 我不记得这个数字,但是使用GCC / MSVC可以很容易地编译程序并测量差异。

我看到有些人谈论异常处理是“只有你使用它”的成本。根据我所观察到的情况,这是不正确的。当您启用异常处理时,它会影响所有代码,代码路径是否可以抛出异常(当您考虑编译器的工作方式时,这完全有意义。)

我也会远离RTTI进行嵌入式开发,尽管我们在调试版本中使用它来检查向下转换的结果。

答案 6 :(得分:1)

很容易看到对二进制大小的影响,只需关闭编译器中的RTTI和异常。如果您正在使用它,您会收到有关dynamic_cast&lt;&gt;的投诉...但我们通常会避免使用依赖于dynamic_cast&lt;&gt;的代码。在我们的环境中。

我们总是发现在二进制大小方面关闭异常处理和RTTI是一个胜利。在没有异常处理的情况下,我已经看到了许多不同的错误处理方法。最受欢迎的似乎是将失败代码传递给callstack。在我们当前的项目中,我们使用setjmp / longjmp,但我建议在C ++项目中反对这一点,因为在许多实现中退出作用域时它们不会运行析构函数。如果我是诚实的,我认为这是代码的原始架构师做出的糟糕选择,特别是考虑到我们的项目是C ++。

答案 7 :(得分:0)

定义'嵌入'。在8位处理器上,我当然不会使用异常(我肯定不会在8位处理器上使用C ++)。如果您正在使用功能强大的PC104型主板,那么几年前您就可以使用它了。但我不得不问 - 为什么有例外?通常在嵌入式应用程序中,任何异常发生都是不可想象的 - 为什么在测试中没有解决这个问题?

例如,这是在医疗设备中吗?医疗设备中的软件软件已经造成人员伤亡。任何计划外发生的时期都是不可接受的。必须考虑所有失败模式,正如Joel Spolsky所说,异常就像GOTO语句,除了你不知道它们从何处被调用。因此,当您处理异常时,您的设备失败了什么状态以及状态是什么?由于你的例外是你的放射治疗机停留在FULL并正在烹饪某人(这已经发生过IRL)?在10,000多行代码中,异常发生在什么时候。当然你可以将其减少到大约100行代码,但是你知道导致异常的每一行的重要性吗?

如果没有更多信息,我会说不要在嵌入式系统中规划异常。如果添加它们,那么请准备好计划可能导致异常的每一行代码的失败模式。如果您正在制作医疗设备,那么如果您不这样做,人们就会死亡。如果你正在制作一台便携式DVD播放器,那么你就制作了一台糟糕的便携式DVD播放器。这是什么?