【问题标题】:alloca() in caller's space调用者空间中的 alloca()
【发布时间】:2015-12-22 17:09:38
【问题描述】:

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

返回数组的常用技术是:A)被调用者在堆上分配并返回,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 可能会导致不必要的 allocate-free 循环,而情况 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 的 ret 指针来实现。我们已经有了现代 C 语言中的 VLA,这看起来非常相似,但稍微有点复杂。

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

有谁知道这种技术在哪里实现/堆栈帧加入? (这里C只是一个例子)


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

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

【问题讨论】:

  • 哇,我们使用动态内存分配已有 50 多年了,怎么可能所有的编程语言理论家都错过了这个显而易见的解决方案? :)
  • 嗯,我不确定。你说“这种技术”,好像这个想法是有道理的;我质疑这种感觉。
  • int a[SIZE]; callee(a, SIZE);
  • 基本问题是,一旦一个函数像这样分配一个数组,你就不能在程序的整个其余部分从那里展开堆栈。它本质上违反了将堆栈用于临时的整个想法,因为现在堆栈框架成为永久的。
  • 另一种看待它的方式是将堆栈变成堆。

标签: c theory callstack


【解决方案1】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-23
    • 2011-02-26
    • 1970-01-01
    • 2012-02-27
    • 2021-01-05
    • 2014-02-27
    • 2010-10-17
    相关资源
    最近更新 更多