您的位置 首页 java

Java开发C解释器:实现动态内存的分配和读写

C语言 有一个强大的功能,就是通过指针实现直接操作内存,正是因为C语言含有直接读写内存的机制,使得C语言在系统开发,底层开发等方面占据了难以撼动的地位,同时也正是这个原因,C语言开发的程序经常出现内存存泄露和野指针等令人头疼的问题。

本节,我们为解释器添加动态内存的分配和读写机制,完成本节后,解释器能准确地执行下面的代码:

 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);
             copy Child(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 接口,得到一个虚拟的内存起始地址,然后把该地址返回即可。

文章来源:智云一二三科技

文章标题:Java开发C解释器:实现动态内存的分配和读写

文章地址:https://www.zhihuclub.com/183564.shtml

关于作者: 智云科技

热门文章

网站地图