java 内存指针_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 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 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 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、付费专栏及课程。

余额充值