java开发C语言解释器:实现指针直接读写

更详细的讲解和代码调试演示过程,请参看视频
用java开发C语言编译器

上一节,我们实现了以数组的方式对指针指向的动态内存进行读写,本节,我们实现通过指针直接读写内存,完成本节代码后,我们的解释器能够解释执行下面的代码:

void main() {
    char *p;
    char *p1;
    p = malloc(2);
    p1 = malloc(1);
    p1[0] = 0;

    *(p+0) = 1;
    *(p+1) = 2;
    *(p+2) = 3;

    printf("p[0] is : %d,  p[1] is : %d, p1[0] is :%d", *(p+0), *(p+1), p1[0]);
}

代码中,*(p+1) 就是通过指针p对内存进行直接读写,本节的目的就是使得解释器能够解释执行这样的语句。

这种语句对应的语法表达式如下:

UNARY -> EXPR
UNARY -> STAR UNARY

括号以及里面的表达式对应的正是EXPR, *(p+1) 这一句代码对应的表达式就是UNARY -> STAR UNARY

当解释器读取到语句*(p+1)时,解释器先解释执行p+1, 它的做法是先把p对应的值读出来,由于p是指针变量,因此它的值是其对应的内存地址,然后计算p+1的值,把得到的结果当做内存地址,通过MemoryHeap对象获得该地址对应的字节数组,最后把对内存的读取或写入转换成对字节数值的读取或写入。

解释器实现对*(p+1)此类语句的解析是在类UnaryExecutor.java中实现的,我们看看对应代码:

public class UnaryNodeExecutor extends BaseExecutor{

    @Override
    public Object Execute(ICodeNode root) {
        executeChildren(root);

        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION); 
        String text ;
        Symbol symbol;
        Object value;
        ICodeNode child;

        switch (production) {
        ...
         case CGrammarInitializer.Start_Unary_TO_Unary:
            child = root.getChildren().get(0); 
            int addr = (Integer)child.getAttribute(ICodeKey.VALUE); //get mem addr
            MemoryHeap memHeap = MemoryHeap.getInstance();
            Map.Entry<Integer, byte[]> entry = memHeap.getMem(addr);
            int offset = addr - entry.getKey();
            if (entry != null) {
                byte[] memByte = entry.getValue();
                root.setAttribute(ICodeKey.VALUE, memByte[offset]);
            }

            DirectMemValueSetter directMemSetter = new DirectMemValueSetter(addr);
            root.setAttribute(ICodeKey.SYMBOL, directMemSetter);
            break;
        ...
        }

上面代码中:
child = root.getChildren().get(0);
int addr = (Integer)child.getAttribute(ICodeKey.VALUE);
这两句的作用是获得p+1的结果,把该结果当做动态内存的读写地址, 然后通过MemoryHeap查找该地址对应的字节数组。

p+1所对应的地址,不是所分配内存的起始地址,变量p对应的值才是内存的起始地址,如果p的值是10000, *(p+1)表示读取内存地址为10001处的字节数据,那么解释器会通过MemoryHeap,得到10000所对应的字节数组,然后读出该数组下标为1处的字节数据,该逻辑正是由下面的代码片段实现的。

MemoryHeap memHeap = MemoryHeap.getInstance();
            Map.Entry<Integer, byte[]> entry = memHeap.getMem(addr);
            int offset = addr - entry.getKey();
            if (entry != null) {
                byte[] memByte = entry.getValue();
                root.setAttribute(ICodeKey.VALUE, memByte[offset]);
            }

*(p+1) = 1; 表示把1写入到内存地址10001处的字节,那么解释器通过MemoryHeap获得10000所对应的字节数组,然后把数值1写入到字节数组下标为1处的字节,这个写入逻辑是通过类DirectMemValueSetter来实现的,我们看看它的实现代码。

public class DirectMemValueSetter implements IValueSetter {
    private int memAddr = 0;

    public DirectMemValueSetter(int memAddr) {
        this.memAddr = memAddr;
    }

    @Override
    public void setValue(Object obj) throws Exception {
        MemoryHeap memHeap = MemoryHeap.getInstance();
        Map.Entry<Integer, byte[]> entry = memHeap.getMem(memAddr);
        byte[] content = entry.getValue();
        int offset = memAddr - entry.getKey();
        Integer i = (Integer)obj;
        content[offset] = (byte)(i & 0xFF);
    }

}

该类的逻辑就是通过MemoryHeap对象找到对应的字节数组,然后把要写入的地址减去动态内存的入口地址,进而得到写入的地址偏移,这个偏移作为字节数组的下标,把数组写入到字节数组中。

通过上面的代码,解释器便能够解释执行*(p+1)=1;这种通过指针直接读写内存的语句。

我们给定的C程序中,存在一个故意设置的bug,就是内存越界读取,p指针对应的内存只有两个字节长,但*(p+2) = 3;是一种内存越界读写的情况,这条语句的作用是,把数值3写入到p1指针所对应的内存中,于是解释器执行上述代码后得到的输出结果是:
p[0] is : 1, p[1] is : 2, p1[0] is :3

内存越界读写是C程序的一大弊病,大多数C语言开发的程序所出现的那些难以复现,难以调试的bug,几乎都是由于内存越界读写导致的。

更加详细的代码讲解和调试演示过程,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值