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