最近,被一个函数调用参数传递的问题困惑了一阵。自己写的解释程序,一直用的好好的。在暗自得意的过程中,突然出现了bug,被泼了一头冷水。当然,bug是在无意中被发现的,确定以后则可以编制专用的代码来揭示它:
func showargs(a, b, c)
{
print a, b, c;
return c+1;
}
showargs(1,2,3);
1 2 3
showargs(showargs(1,2,3),showargs(4,5,6),100);
4 5 6
1 2 3
4 2 3
参数是自右向左传递的。但最后一个print打印的结果不对。最后一个print打印当然应该是[4 7 100]。不是自定义函数代码写错,而是解释程序暴露了bug。
struct exprval e_fcall(int fi, struct node *paras, int *exception)
{
struct funxat *f;
struct node *p;
struct exprval e;
int i;
struct exprval ret;
f = &userfunc[fi];
p = paras;
i=0;
while(p) {
p = p->left;
++i;
}
if (i != f->paras) {
printf("function %s(...) need %d paras, but given %d\n",
f->name, f->paras, i);
*exception =1; goto exception_out;
}
p = paras;
for(i=f->paras-1; i>=0; --i) {
e = e_eval(p->right, exception); EXCEPTION_CHK;
symtab_compile[DM_FOR_LOCAL].var[i]= e;
p= p->left;
}
if (f->loop == f->warning) {
printf("waring: func %s(...) nest called in %d levels\n",
f->name, f->loop);
f->warning = terminate_loop -(terminate_loop -f->warning)/4;
}
if (f->loop >terminate_loop) {
printf("exception: func %s(", f->name);
for(i=0; i<f->paras; i++) {
if(i>0) printf(",");
e =symtab_compile[DM_FOR_LOCAL].var[i];
if (e.type==0) {
printf("~%d", e.u.value);
}
else {
printf("~%lf", e.u.d_value);
}
}
printf(") is terminated!;\n");
*exception =1;
goto exception_out;
}
f->loop++;
alloc_context(f);
for(i=0; i<f->paras; i++) {
stk->e[top].bp[i] =symtab_compile[DM_FOR_LOCAL].var[i];
}
execute_e_ls(f->syntree, 0, exception); //.. ..............
ret = stk->e[top].ret;
free_context();
f->loop--;
if (f->loop==0) {
f->warning= terminate_loop - (terminate_loop/4);
}
exception_out:
if (*exception==1) {
}
return ret;
}
这是解释程序相关部分的代码。问题就在
for(i=f->paras-1; i>=0; --i) {
e = e_eval(p->right, exception); EXCEPTION_CHK;
symtab_compile[DM_FOR_LOCAL].var[i]= e;
p= p->left;
}
这个语句。symtab_compile[DM_FOR_LOCAL].var[i]= e; 这个用法是导致出现bug的根本原因。由于固定位置重入,传入的参数,被后来嵌套的函数的参数传递覆盖了。当时这样用是因为,程序在执行期间, symtab_compile[DM_FOR_LOCAL].var[]作为为编译配置的内存资源,被闲置着。记得当时隐约觉得这样写有些不妥,但稍微细想一下又没发现问题。终于抵制不住诱惑,挪用了配置给编译期的内存。正解当然是动态分配一个f->paras个数的mixtype数组来存放参数,最后在函数返回时候释放它。遇到嵌套的函数时因为会重新一个这样的分配,就不会出现重入的问题,不会发生参数覆盖,这样就解决问题了。
修复的代码就不展示了。因为,个人还不死心,仍然想利用这个闲置的编译期的内存。当然,改动会变得复杂,还会有更多的坑需要去踩… …