本节,我们为解释器添加动态内存的分配和读写机制,完成本节后,解释器能准确地执行下面的代码:
void main() { char p; p = malloc(2); printf("addr of p is : %d\n", p); p[0] = 1; p[1] = 2; printf("p[0] is : %d, p[1] is : %d", p[0], p[1]);}
上面代码中,通过库函数调用malloc,先分配两个字节,接下来分别对分配的内存进行读写和赋值,然后再把赋值内容打印出来。我们先看看在解释器上实现动态内存分配的机制。
malloc调用后,会形成一个整形值,这个数值的内容无关紧要,只要知道这个数值的开始地址,连续若干个字节的内存就是可以提供给程序任意读写的就可以了,也就是说,这几个数值相当于一把钥匙,通过这把钥匙,我们就能打开用于存储同喜的抽屉,我们看看如何在解释器中模拟这个机制,该机制的实现在MemoryHeap.java中:

package backend;import java.util.HashMap;import java.util.Map;public class MemoryHeap { private static int initAddr = 10000; private static MemoryHeap instance = null; private static HashMap<Integer, byte[]> memMap = new HashMap<Integer, byte[]>(); public static MemoryHeap getInstance() { if (instance == null) { instance = new MemoryHeap(); } return instance; } public static int allocMem(int size) { byte[] mem = new byte[size]; memMap.put(initAddr, mem); int allocAddr = initAddr; initAddr += size; return allocAddr; } public static Map.Entry<Integer, byte[]> getMem(int addr) { int initAddr = 0; for (Map.Entry<Integer, byte[]> entry : memMap.entrySet()) { if (entry.getKey() <= addr && entry.getKey() > initAddr) { initAddr = entry.getKey(); byte[] mems = entry.getValue(); if (initAddr + mems.length > addr) { return entry; } } } return null; } private MemoryHeap() { }}
allocMem用来生成动态内存,调用该函数时,传入的参数就是要申请的内存大小。该类用一个HashMap来表示动态内存,map的key用来模拟动态内存的地址,value则是byte[] 数据类型,用来模拟分配的动态内存。当这个函数调用时,它使用一个整形数值来表示内存的虚拟起始地址,然后构造一个给定长度的字节数组,把整形数组和分配的字节数组结合起来,放入到map 中,以后程序可以通过对应的整形数来获得字节数组。
有了虚拟起始地址后,通过这个地址,调用getMem,就可以获得对应的字节数组,程序对该数组的读取,就相当于对动态内存的读取,getMem返回的是一个Entry对象,这个对象包含了虚拟起始地址和byte类型数组。
p[0] 表示读取分配的动态内存的第一个字节,它相当于把一组连续的内存当做数组来访问,我们以前讲解过,读取数组元素是由UnaryNodeExecutor来实现的,因此对应的内存读取机制其实现代码如下
public class UnaryNodeExecutor extends BaseExecutor{ @Override public Object Execute(ICodeNode root) { executeChildren(root); .... switch (production) { .... case CGrammarInitializer.Unary_LB_Expr_RB_TO_Unary: child = root.getChildren().get(0); symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL); child = root.getChildren().get(1); int index = (Integer)child.getAttribute(ICodeKey.VALUE); try { Declarator declarator = symbol.getDeclarator(Declarator.ARRAY); if (declarator != null) { Object val = declarator.getElement(index); root.setAttribute(ICodeKey.VALUE, val); ArrayValueSetter setter = new ArrayValueSetter(symbol, index); root.setAttribute(ICodeKey.SYMBOL, setter); root.setAttribute(ICodeKey.TEXT, symbol.getName()); } Declarator pointer = symbol.getDeclarator(Declarator.POINTER); if (pointer != null) { setPointerValue(root, symbol, index); //create a PointerSetter PointerValueSetter pv = new PointerValueSetter(symbol, index); root.setAttribute(ICodeKey.SYMBOL, pv); root.setAttribute(ICodeKey.TEXT, symbol.getName()); } }catch (Exception e) { System.err.println(e.getMessage()); System.exit(1); } break; } }}private void setPointerValue(ICodeNode root, Symbol symbol, int index) { MemoryHeap memHeap = MemoryHeap.getInstance(); int addr = (Integer)symbol.getValue(); Map.Entry<Integer, byte[]> entry = memHeap.getMem(addr); byte[] content = entry.getValue(); if (symbol.getByteSize() == 1) { root.setAttribute(ICodeKey.VALUE, content[index]); } else { ByteBuffer buffer = ByteBuffer.allocate(4); buffer.put(content, index, 4); buffer.flip(); root.setAttribute(ICodeKey.VALUE, buffer.getLong()); } }
当解释器解析到语句P[0]时,p[0]可能代表数组下表为0的元素,也可以表示读取动态内存从起始地址开始,偏移量为0的内存数据,怎么判断到底是哪一种情况呢,我们在以前实现类型系统时,在解析过程中,如果变量定义成数组或者指针,那我们会在她的Symbol对象中添加一个成员,称之为Declarator,用这个类来对变量进行描述,如果变量P是数组,那么Declarator的类型是ARRAY,如果是指针,那么类型为Pointer。
如果p是指针的话,那么if(pointer!=null)里面的代码就会执行,首先通过setPonterValue把指定内存的内容读取出来,对应指针p[0]就是把p指向的内存读取偏移量为0的内存数据。
setPointerValue的逻辑是先得到内存地址,这个地址的数值就是allocMem返回的,通过这个地址,MemoryHeap的哈希表中找到对应的字节数值,这个字节数组就是用来模拟动态内存的,他的输入参数index对应于地址偏移,symbol.getBytes()用来获得指针变量的数据类型, 如果变量类型是char,那么我们一次读取一字节shuju7,若不然我们一次读取4字节的数据。
当解析器解析到语句p[0]=1时,表明程序想对分配的内存进行写入,我们会用一个pointerValueSetter,把对内存的写入逻辑封装起来。
public class PointerValueSetter implements IValueSetter { private Symbol symbol; private int index = 0; @Override public void setValue(Object obj) throws Exception { int addr = (Integer)symbol.getValue(); MemoryHeap memHeap = MemoryHeap.getInstance(); Map.Entry<Integer, byte[]> entry = memHeap.getMem(addr); byte[] content = entry.getValue(); Integer i = (Integer)obj; try { if (symbol.getByteSize() == 4) { content[index] = (byte)((i>>24) & 0xFF); content[index + 1] = (byte)((i>>16) & 0xFF); content[index + 2] = (byte)((i>>8) & 0xFF); content[index + 3] = (byte)(i & 0xFF); } else { content[index] = (byte)(i & 0xFF); } } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(); System.exit(1); } } public PointerValueSetter(Symbol symbol, int index) { this.symbol = symbol; this.index = index; }}
先从变量对应的symbol对下中,获得变量的值,在指针变量的情况下,这个值代表的就是内存的起始地址,根据这个地址,通过MemoryHeap获得对应的字节数组对下,然后根据偏移,把数据写入到字节数值中,再次我们暂时默认写入的数据要不是4字节的int要不就是byte,以后要读写更复杂的数据内容时,我们再做相应的修改。
对应变量赋值语句p[0]=1他的实现是在NoCommaExprExecutor这个类中,我们看看对于的代码实现。
public class NoCommaExprExecutor extends BaseExecutor{ ExecutorFactory factory = ExecutorFactory.getExecutorFactory(); @Override public Object Execute(ICodeNode root) { executeChildren(root); .... switch (production) { .... case CGrammarInitializer.NoCommaExpr_Equal_NoCommaExpr_TO_NoCommaExpr: child = root.getChildren().get(0); String t = (String)child.getAttribute(ICodeKey.TEXT); IValueSetter setter; setter = (IValueSetter)child.getAttribute(ICodeKey.SYMBOL); child = root.getChildren().get(1); value = child.getAttribute(ICodeKey.VALUE); try { setter.setValue(value); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.err.println("Runtime Error: Assign Value Error"); } child = root.getChildren().get(0); child.setAttribute(ICodeKey.VALUE, value); copyChild(root, root.getChildren().get(0)); break; }
这段代码跟我们以前讲解对数组元素赋值时所实现的一模一样,这主要得益于我们一开始就把赋值的机制通过接口IValueSetter封装起来,在这里,setter所对应的类就是前面提到的PointerValueSetter,解释器此处不需要知道到底是对数组赋值,还是对内存赋值,我们只需要调用接口就可以了,具体的赋值逻辑由具体的接口实现。
我们再看看库函数malloc的实现,代码如下:
public class ClibCall { .... private Object handleMallocCall() { ArrayList<Object> argsList = FunctionArgumentList.getFunctionArgumentList().getFuncArgList(false); int size = (Integer)argsList.get(0); int addr = 0; if (size > 0) { MemoryHeap memHeap = MemoryHeap.getInstance(); addr = memHeap.allocMem(size); } return addr; } ....}
它的逻辑比较简单,就是通过MemoryHeap的allocMem 接口,得到一个虚拟的内存起始地址,然后把该地址返回即可。