是否可以在C中编写零成本异常处理?

时间:2013-03-17 18:59:55

标签: c optimization exception-handling elf gas

g ++编译器具有零成本异常处理功能。根据我的理解,try什么都不做,但是当抛出异常时,会执行异常处理程序的子例程。像这样:

void foo() {
    try {
        bar(); // throws.
    } catch (Type exc) {
        baz();
    }
}

在伪代码(c-stylish)中看起来像这样:

void foo() {
    bar();
    return;
catch1_Type:
    baz();
}

bar()抛出。例外程序执行以下操作:

啊,返回地址是函数foo()!并且返回地址在第一个try-catch块中,我们抛出类型Type,因此异常处理程序例程位于地址foo + catch1_Type。所以清理堆栈让我们最终到达那里!

现在我的问题:有没有办法在C中实现它? (可以是C99或更新,虽然我对gcc支持的C语言感兴趣)。我知道我可以使用例如libunwind进行堆栈检查和遍历,虽然我不知道如何获取catch1_Type标签的地址。这可能是不可能的。

异常处理程序可能是一个不同的函数,同样可以,但是如何在另一个函数中获取stackframe foo的局部变量的地址?这似乎也是不可能的。

所以...有什么办法吗?我不想用这个去装配,但如果其他一切都失败也是可以接受的(虽然局部变量 - 伙计,如果使用不同的优化级别,你永远不会知道它们在哪里)。

要明确 - 这个问题的目的是避免 setjmp / longjmp方法。

编辑:我发现了一个很酷的主意,但并不完全有效:

gcc中的嵌套函数。他们能做什么?

  • 可以访问本地变量
  • 有可能在父函数中转到本地标签!
  • 只要我们传递一个指向嵌套函数的指针,
  • 就可以被我们函数的被调用者调用,所以它可以被被调用者中的指针使用。

下行阻止我做任何零成本的事情:

  • 如果它们未被使用,它们甚至会在-O0级别进行优化。我可以对此做些什么吗?如果可以的话,我可以在抛出异常时通过符号名称获取地址,并且它只是执行实现异常的工作,这些异常在没有抛出时会花费成本...

2 个答案:

答案 0 :(得分:3)

我已经找到了一些时间来实现这个想法,而且我非常接近为自己的问题找到解决方案。以下是详细信息:

  • gcc允许嵌套函数,它们可以访问父函数的局部变量,并且可以在父函数中转到标签!
  • 如果内部函数仅定义但未引用,则它不会发出内部函数的代码。您可以定义内联无操作函数,该函数获取指向本地函数的指针,并在父函数中调用它。这将强制生成内部函数的代码,并且它将是零成本(在更高的优化级别,将删除内联无操作调用)。有可能在最近的gcc中优化内联调用会阻止生成内部函数的代码。
  • 坏事是,我认为无法强制嵌套(内部)函数成为全局符号。它们总是本地的,所以不可能使用dlsym来获取地址。
  • 另一个坏消息,如果程序使用这样的嵌套函数,valgrind会崩溃;)我能够验证我的简单测试程序,但我无法使用valgrind来验证没有内存违规。

我用于检查的代码有明显的缺陷:它不是'零成本',因为必须在函数执行期间设置指向异常处理例程的全局指针。如果我们只能编译这个编译时间!

好吧,毕竟如果一个人想要正确使用内部函数,比如将一个指针传递给callees,那么他们可以在抛出异常的情况下调用它,我们可能会有非常快速的异常处理,很多,更快比setjmp / longjmp ...

我会继续黑客攻击,也许我会找到一种方法(一些汇编代码块强制GAS将该函数注册为父级的个性例程?)。

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

typedef void (*catch_routine)(void*);

catch_routine g_r = NULL;

void tostr_internal(char* str, int a)
{
    int result = a + 'a';
    if (result < 'a' || result > 'z')
    {
        // handle exception
        if(g_r)
        {
            g_r(&a);
        }
        else
        {
            fprintf(stderr, "Exception not caught!");
            abort();
        }
    }
    else
    {
        str[0] = result;
        str[1] = '\0';
    }
}

char* tostring(int a)
{
    __label__ exhandler;
    char* string = (char*)malloc(2*sizeof(char));

    void personality(void* exid) {
        fprintf(stderr, "Number %d is not a character!\n", *(int*)(exid));
        free(string);
        goto exhandler;
    }
    g_r = personality;

    tostr_internal(string, a);
    return string;

exhandler:
    return NULL;
}

int main(int a, char** b)
{
    int i = 0;

    for(i = 0; i < 10000; i++)
    {
        int trythisbastard = i % 95;
        char* result = tostring(trythisbastard);
        if (result)
        {
            fprintf(stderr, "Number %d is %s\n", trythisbastard, result);
            free(result);
        }
    }

    return 0;
}

答案 1 :(得分:1)

当我在C中需要尝试/捕获类似函数时,我使用了这篇文章。

http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

正如一些人已经发现的那样,它是围绕setjmp longjmp

的粘合剂