最近,被一个函数调用参数传递的问题困惑了一阵。自己写的解释程序,一直用的好好的。在暗自得意的过程中,突然出现了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数组来存放参数,最后在函数返回时候释放它。遇到嵌套的函数时因为会重新一个这样的分配,就不会出现重入的问题,不会发生参数覆盖,这样就解决问题了。
修复的代码就不展示了。因为,个人还不死心,仍然想利用这个闲置的编译期的内存。当然,改动会变得复杂,还会有更多的坑需要去踩… …