alloca()在调用者的空间中

时间:2015-09-24 23:24:53

标签: c theory callstack

考虑返回动态或自动数组。与C无关。

返回数组的常用技巧是:A)callee在堆上分配并返回,B)调用者在堆栈上分配并传递给被调用者。

// A
void caller(void) {
    int *a = callee();
    free(a);
}
int *callee(void) {
    int *a = malloc(10 * sizeof(*a));
    return a;
}

// B
void caller(void) {
    int a[10]; callee(a, sizeof(a) / sizeof(a[0]));
}
void callee(int *a, size_t n) {
    //
}

案例A可能导致不必要的无分配周期,而案例B需要调用者的语法垃圾。在B中,我们也无法在被调用者中计算n,因为它是预定义的。我们也无法返回自动存储,因为它会在返回时被销毁(一般来说它是UB)。

但是如果我们引入将从被调用者返回的新return_auto运算符,但保持堆栈帧完好无损,如同调用者在其自己的堆栈上完成所有作业一样呢?

// C
void caller(void) {
    int *a = callee();
}
int *callee() {
    int a[compute_n()];
    return_auto a;
}

我的意思是,调用者可以继承被调用者的堆栈帧,所有问题都会消失。在return_auto:

之后,它的堆栈框架看起来像这样
[caller frame]
  arguments
  ret-pointer
  locals
    int *a = callee.a

  [callee frame] (defunct)
    arguments
    ret-pointer
    locals
      int a[n] (still alive)
  [end-of-callee-frame]

[end-of-caller-frame]

在机器代码(至少x86)中,可以通过跳转到ss:ebp而不是mov esp, ebp / ret n的快速指针来实现。我们已经在现代C中使用了VLA,这看起来非常相似但稍微复杂。

当然应该谨慎使用,因为return_auto系列会在堆栈上留下非常大的转储,只有当最外面的调用者返回(通常)时才会“收集”。但是堆栈分配非常便宜,理论上一些算法可以从不调用malloc / free中受益。这在代码结构化角度中也很有趣,而不仅仅是性能。

有谁知道这项技术的实施位置/堆叠帧加入了吗? (这只是一个例子)

好的,它需要一个简单的例子。

void caller(Context *ct) {
    char *s = make_s(ct);
    printf("%s\n", s);
}

void make_s(Context *ct) {
    const char *tag = "?", *name = "*";

    if (ct->use_tag) tag = ct->tag;
    else if (ct->app) tag = ct->app->tag;
    if (ct->app) name = ct->app->name;

    char s[strlen(tag)+strlen(name)+10];
    snprintf(s, len, "%s.object(%s)", name, tag);
    return_auto s;
}

显然,现在我们需要在调用者的身体中爆炸(可能通过宏来感受所有警告)或者在被调用者中asprintf/malloc和调用者中free

1 个答案:

答案 0 :(得分:2)

对于任何非平凡的场景来说,这似乎都是一个非常糟糕的主意。只需记住堆栈帧包含所有局部变量以及返回地址,保存的基指针等。在您的模型中,调用者需要“继承”整个帧作为其自己帧的一部分。然后考虑一下你可以将这个返回值传递给某个OTHER函数。那么如果这个函数想要返回的不仅仅是一个整数值呢?你很容易得到main()巨大的堆栈框架。任何堆实现都可能更节省空间。