本节将实现变量引用,所谓变量引用可以看作是另一个变量的影子,和所引用的变量使用相同的内存空间,只不过变量名或者作用域不同而已。对引用的操作就是对原变量的操作,通过引用赋值可以减少...

    本节将实现变量引用,所谓变量引用可以看作是另一个变量的影子,和所引用的变量使用相同的内存空间,只不过变量名或者作用域不同而已。对引用的操作就是对原变量的操作,通过引用赋值可以减少实际的内存拷贝的开销,在一些特殊的场合很有用。

    本节v0.0.15版本的源代码下载地址为:http://pan.baidu.com/share/link?shareid=189829&uk=940392313 (此为百度云盘的共享链接地址),访问该地址可以看到三个文件:zengl_lang_v0.0.15_forXP.rar (XP系统下的vs2008解决方案和源代码), zengl_language_v0.0.15_forLinux.tar.gz  (Linux系统下的源代码和makefile) ,v0.0.15-v0.0.14-diffs.txt  (v0.0.15和v0.0.14的代码变化情况)。

   
SourceForge.net上的仓库地址为:https://sourceforge.net/projects/zengl/files/   从里面可以看到各个版本的代码压缩包,比如本节的zengl_lang_v0.0.15_forXP.rar zengl_language_v0.0.15_forLinux.tar.gzv0.0.15-v0.0.14-diffs.txt

   
先来看下本版本的描述 (在linux代码包里的usage.txt里有这段描述,在目前几个带有git版本的....-diffs.txt和git log中也有这段描述):

    v0.0.15版本,该版本添加了&符号用于变量的引用。
   
    变量引用就像C语言的指针,不过操作更简单也只有一个符号,就和PHP里的&引用符号一样。在赋值或者函数传值时,可以将某变量的引用作为参数, 这样在函数里对该参数的操作就是对原变量的操作,引用可以看作是某变量的影子。引用可以被覆盖,也可以传递,例如:test = &test1 那么test是test1的引用,对test的操作其实就是对test1的操作,如果稍候,test = &test2,那么test就又变成test2的引用了,这就是引用的覆盖,在test为test2的引用的时候,如果test3 = &test,那么test3也会变为test2的引用,这就是引用的传递。
    在main.c中添加了引用的token,在parser.c中添加了引用的语法树生成,引用的语法树结构和reverse取反运算符的结构一样,都是用 在某变量前的。在run.c中为内存空间添加了引用的类型,当对某个类型为引用的内存进行操作时,会自动转向对所引用的目标内存进行操作。
 其他的修改请用git log -p来查看,或者使用gitk之类的图形界面来查看。
 
    作者:zenglong
    时间:2012年4月4日
    官网网站:www.zengl.com

    以前的git版本控制信息都是使用windows下的git bash工具生成的,但是使用windows下的git工具生成的版本信息,在linux下无法正常进行扩展,可能原因是windows下使用的是 fat32或者ntfs文件系统和linux下的ext4之类的文件系统不兼容,所以从这个版本开始git版本控制信息都将在linux下生成,以防止出现类似的问题,因为要重新在linux系统下生成版本信息,所以需要git init重新初始化git版本控制信息,该版本linux源代码里的git信息就只有v0.0.14和v0.0.15的版本数据,v0.0.14之前的版本信息就只有在以前发布的tar.gz中可以找到了。
    另外因为源代码统一使用的是DOS换行符,所以linux下生成的git版本数据在windows下使用git log -p查看时,会发现每行代码的末尾有个^M的符号,如果想隐藏掉这个讨厌的符号,可以使用
git config --global core.whitespace cr-at-eol 命令进行隐藏。

    具体的C文件代码部分,请结合源代码中的注释,加上git工具以及vs2008或者eclipse+CDT或者gcc,gdb等工具进行分析。

    
下面通过本节的test.zl测试脚本的例子来说明引用的用法和作用(下面的注释仅在此起说明作用,目前注释功能还没实现):

fun modifyGlobalValUseAddr(arg)  //先定义一个函数,到时候给该函数赋予一个变量引用作为参数,就可以对所引用的变量进行操作。函数名modifyGlobalValUseAddr的英文翻译过来就是:通过变量引用来修改全局变量,这里用Addr代表引用的意思。
  arg = 'welcome to zengl.com';  //将引用的变量的值修改为字符串'welcome to zengl.com'
endfun

glTest = "hello world"; //初始化全局变量glTest,接下来将对glTest进行引用操作。
print 'now global value glTest is ' + glTest; //将glTest的值打印显示出来。
glTest2 = &glTest; //通过&符号将glTest的引用赋值给glTest2,这样glTest2就成为glTest的替身,两个变量指向同一个虚拟内存空间。
print 'now global value glTest2 is addr of glTest , and glTest2 is ' + glTest2; //将glTest2的值打印出来。
glTest2 = 'go your own way';   //重新设置glTest2的值,通过下面的两条打印语句,可以看到,glTest2发生变化,glTest也跟着发生相同变化。
print 'now global value glTest2 is ' + glTest2; //打印显示glTest2的值。
print 'now global value glTest is ' + glTest;   //打印显示glTest的值。结果是glTest的值始终等于glTest2 。
modifyGlobalValUseAddr(&glTest);   //将glTest的引用作为参数,调用modifyGlobalValUseAddr函数,因为在该函数里对引用做了修改,所以glTest的值也跟着发生变化。
print 'now global value glTest is ' + glTest; //将经过modifyGlobalValUseAddr函数修改后的glTest值打印显示出来。

    test.zl对应的汇编代码文件test.zlc如下(下面的注释仅在此起说明作用):

0 JMP 5;
1 FUNARG 1; //modifyGlobalValUseAddr定义的开始位置
2 MOV AX "welcome to zengl.com";
3 MOV arg(0) AX;
4 RET;
5 MOV AX "hello world";
6 MOV (0) AX;
7 MOV AX "now global value glTest is ";
8 MOV BX (0);
9 PLUS;
10 print AX;
11 ADDR (0);  //通过ADDR汇编指令得到内存0(即glTest变量)的引用信息,并将引用信息(引用信息里包含该内存类型和内存偏移值)存放在AX寄存器中
12 MOV (1) AX;  //再将AX里的引用信息传递给内存1(即glTest2变量),这样glTest2就成为glTest的引用,对内存1(glTest2)的操作都会转化为对内存0(glTest)的操作。(即武学中所谓的“北斗星移”,O(∩_∩)O~)
13 MOV AX "now global value glTest2 is addr of glTest , and glTest2 is ";
14 MOV BX (1);
15 PLUS;
16 print AX;
17 MOV AX "go your own way";
18 MOV (1) AX;
19 MOV AX "now global value glTest2 is ";
20 MOV BX (1);
21 PLUS;
22 print AX;
23 MOV AX "now global value glTest is ";
24 MOV BX (0);
25 PLUS;
26 print AX;
27 PUSH ARG;
28 PUSH LOC;
29 RESET ARGTMP;
30 ADDR (0);  //和上面一样先得到内存0即glTest的引用,并将引用信息存放到AX寄存器。
31 PUSH AX; //将AX寄存器的引用信息作为参数压入栈中。这样在modifyGlobalValUseAddr函数中对参数arg(0)的操作其实就是对全局内存(0)即glTest的操作
32 PUSH 36; //返回地址压入栈
33 MOV ARG ARGTMP; //设置ARG参数寄存器
34 RESET LOC;  //设置LOC局部变量寄存器
35 JMP 1;  //跳到汇编代码1的位置,执行modifyGlobalValUseAddr函数
36 MOV AX "now global value glTest is ";
37 MOV BX (0);
38 PLUS;
39 print AX; //将经过函数修改后的glTest打印显示出来。
40 END;

    使用zenglrun虚拟机运行test.zlc得到的结果为:

    可以看出,将glTest2修改为go your own way后,因为glTest2是glTest的引用,所以glTest的值也跟着变为go your own way了。接着将glTest的引用作为参数调用modifyGlobalValUseAddr 函数,在该函数里,将参数修改为welcome to zengl.com ,因为参数是glTest的引用,所以glTest最终的值也变为welcome to zengl.com了。

    变量引用的核心原理主要集中在run.c的MemOps函数(操作全局内存的)和StackOps函数(操作栈空间的),下面是MemOps函数的片段:

MEM_STRUCT MemOps(MEMOPTYPE op,int memloc,MEM_STRUCT argval)
{
...................  //省略N行代码

    if(op != ADDMEM_ADDR) //如果不是设置某内存为引用的话,就进行下面的处理
    {
        if(gl_mems.mem_array[memloc].idtype == IDADDR_LOC) //如果memloc所在虚拟内存的运行时类型是IDADDR_LOC即局部变量的引用的话,就调用StackOps将操作转为对虚拟堆栈中引用的局部变量的操作。然后直接返回。
            return StackOps(op,argval,gl_mems.mem_array[memloc].val.dword); //dword里存放的是引用对象在堆栈中的索引
        else if(gl_mems.mem_array[memloc].idtype == IDADDR) //如果memloc所在内存的运行时类型是IDADDR即全局变量的引用,则调用MemOps将操作转为对引用的全局变量的操作。
            return MemOps(op,gl_mems.mem_array[memloc].val.dword,argval);  //dword存放的是引用对象在全局虚拟内存动态数组中的索引。
    }


.....................  //省略N行代码
}

    通过idtype判断是哪种类型的内存引用,然后选择调用MemOps或StackOps将操作转为对所引用内存的操作。在StackOps栈操作函数中同样有上面这段类似的代码。

   
其他更多的细节部分,请结合源代码,git log -p 和前面提到过的开发调试工具进行分析。

    最后还是老生常谈的话题:
    windowsXP压缩包中的代码包括test.zl测试脚本都是采用GBK的编码,Linux压缩包中的代码包括测试文件以及git里的信息都是UTF8的编码,所以如果哪些地方出现了乱码,请自行调整。

    对于windows用户,请确保在项目属性的配置里,命令行参数配置的是test.zl(对于zengl_lang_v0.0.15的项目)或test.zlc(对于zenglrun的项目),好像每一节都提到过。
    另外对于vs2008的用户,我在项目属性里:[配置属性>>>>C/C++ >>>> 高级] 部分设置了禁用特定警告:4013,4715,4996 ,这几个警告会显示一些某某函数是非安全的函数,或者函数没有返回值等,这里禁用掉,防止出现过多的警告。另外还有个警告是显示某某变量没被使用过的, 这个警告我没禁用,可以不用管它。我最开始是使用Linux系统开发的zengl ,在我的GCC下面并没有显示过这些讨厌的警告,所以就没处理,不过还好这些警告都无关痛痒,无需理会。
    还有一个地方:VS2008项目中,在[配置属性>>>> C/C++ >>>> 预处理器] 部分都设置了预处理器定义的宏:OS_IN_WINDOWS ,因为源代码既要在WINDOWS下编译,又要在LINUX下编译,所以需要通过这个宏来告诉程序当前的环境是windows还是linux,在 windows下面,在程序结束时会执行system ("pause");这条语句(vs2008下为了能看到结果,需要暂停,否则就一闪而过,什么都看不到咯。) 而linux系统主要在bash终端下执行,不需要这条语句。

    linux系统下的用户请结合usage.txt的说明,先运行make clean 将原来生成的zengl zenglrun 和 main.o parser.o assemble.o ld.o func.o run.o  symbol.o builtin.o文件
删除。

    再运行make all (单纯的make只能生成zengl,所以需要make all来生成所有的目标)

    生成zengl zenglrun 和 main.o parser.o assemble.o ld.o func.o run.o  symbol.o 
builtin.o。(在生成过程中如果出现一些警告,暂不管他)

    最后运行 ./zengl test.zl 查看printASTnodes函数打印抽象语法树节点的结果,以及符号表输出的变量信息以及函数信息等。(例如变量的内存地址,以及在源文件的行列号,函数的唯一标识ID等)。
    接着运行./zenglrun test.zlc (注意是.zlc结尾的文件名,因为zenglrun虚拟机只能运行.zlc里的汇编代码)。

    zengl语言涉及到的很多高级的编译原理都可以在《龙书》中找到。

    最后的最后,如果转载请注明来源 http://www.zengl.com   , OK , 先到这里,休息,休息一下 O(∩_∩)O~

上下篇

下一篇: zengl编程语言v0.0.16数组,21点扑克小游戏

上一篇: zengl编程语言v0.0.14加载模块函数

相关文章

zengl编程语言v0.0.5构建符号表汇编代码和虚拟机

zengl编程语言v0.0.7

zengl v1.2.1 修复函数调用BUG

zengl编程语言 中序

zengl编程语言v0.0.16数组,21点扑克小游戏

zengl编程语言 v1.0.3 完善文件出错信息