更详细的讲解和代码调试演示过程,请参看视频
用java开发C语言编译器
我们完成结构体的解析执行后,当前的解释器已经相对完善了,如果不考虑性能因素,当前解释器能够解释执行很多C语言程序,但当前解释器还有一些问题,我们看下面这段C程序:
void main() {
struct TAG {
char c;
int p;
int arr[3];
}tag;
struct TAG* pTag;
pTag->p = 400;
printf("value of p in struct pTag is : %d", pTag->p);
}
上面程序被我们的解释器执行后,printf语句把pTag->p 的值输出为400,这显然是有问题的,因为pTag是个结构体指针,在没有为其分配动态内存的情况下,直接对成员变量进行赋值访问是错误的。接下来的几节,我们将致力于解决
这个问题。
要解决这个问题,我们需要实现的一点是,在解释器内部的各个执行模块间实现相互通讯。我们知道,不同的C语言语句,是由不同的Executor来执行的。这些
Executor之间互不关联,但随着解释器功能的增强,我们需要它们之间实现相互通讯。例如当执行赋值语句的执行器在执行前和后执行后需要发出消息,让其他执行器知道,以便让解释器的其他模块有机会做相应的操作。
我们将使用观察者模式来搭建联通不同解释器之间的桥梁,我们先看看这个“桥梁”的接口定义:
public interface IExecutorBrocaster {
public void brocastBeforeExecution(ICodeNode node);
public void brocastAfterExecution(ICodeNode node);
public void registerReceiverForBeforeExe(IExecutorReceiver receiver);
public void registerReceiverForAfterExe(IExecutorReceiver receiver);
public void removeReceiver(IExecutorReceiver receiver);
}
当有某个Executor想通知其他模块相关信息时,例如在执行之前要通知其他模块时,可以调用brocastBeforeExecution 接口,如果在执行之后想通知其他模块,那么就可以调用brocastAfterExecution接口。
如果解释器中某个模块想接收其他模块发送的消息,它可以实现
IExecutorReceiver接口,然后调用接口registerReceiverForBeforeExe 和
registerReceiverForAfterExe , 前者接收executor在执行前的消息,后者接收executor在执行之后的消息。
我们看看它的实现:
package backend;
import java.util.ArrayList;
import java.util.List;
public class ExecutorBrocasterImpl implements IExecutorBrocaster{
private List<IExecutorReceiver> beforeExecutionReceiver = new ArrayList<IExecutorReceiver>();
private List<IExecutorReceiver> afterExecutionReceiver = new ArrayList<IExecutorReceiver>();
private static ExecutorBrocasterImpl instance = null;
public static IExecutorBrocaster getInstance() {
if (instance == null) {
instance = new ExecutorBrocasterImpl();
}
return instance;
}
private ExecutorBrocasterImpl() {
}
@Override
public void brocastBeforeExecution(ICodeNode node) {
notifyReceivers(beforeExecutionReceiver, node);
}
@Override
public void brocastAfterExecution(ICodeNode node) {
notifyReceivers(afterExecutionReceiver, node);
}
@Override
public void registerReceiverForBeforeExe(IExecutorReceiver receiver) {
if (beforeExecutionReceiver.contains(receiver) == false) {
beforeExecutionReceiver.add(receiver);
}
}
@Override
public void registerReceiverForAfterExe(IExecutorReceiver receiver) {
if (afterExecutionReceiver.contains(receiver) == false) {
afterExecutionReceiver.add(receiver);
}
}
private void notifyReceivers(List<IExecutorReceiver> receivers, ICodeNode node) {
for (int i = 0; i < receivers.size(); i++) {
receivers.get(i).handleExecutorMessage(node);
}
}
@Override
public void removeReceiver(IExecutorReceiver receiver) {
if (beforeExecutionReceiver.contains(receiver)) {
beforeExecutionReceiver.remove(receiver);
}
if (afterExecutionReceiver.contains(receiver)) {
afterExecutionReceiver.remove(receiver);
}
}
}
它的实现逻辑比较简单,把接受者存储到相关队列中,如果有Executor发布执行前通知,那么该对象就把beforeExecutionReceiver队列中的对象全部调用IExecutorReceiver 的接口执行一遍。
如果有Executor发布执行后的消息,那么该对象就把afterExecutionReceiver队列中的对象,全部调用IExecutorReceiver的接口执行一遍。
在每个Executor在执行前和执行后,我们都让他们发出消息,以便其他模块及时处理,在BaseExecutor.java中做如下修改:
protected void executeChildren(ICodeNode root) {
ExecutorFactory factory = ExecutorFactory.getExecutorFactory();
root.reverseChildren();
int i = 0;
while (i < root.getChildren().size()) {
if (continueExecute != true) {
break;
}
ICodeNode child = root.getChildren().get(i);
executorBrocaster.brocastBeforeExecution(child);
Executor executor = factory.getExecutor(child);
if (executor != null) {
executor.Execute(child);
}
else {
System.err.println("Not suitable Executor found, node is: " + child.toString());
}
executorBrocaster.brocastAfterExecution(child);
i++;
}
}
我们再看看IExecutorReceiver的实现:
package backend;
public interface IExecutorReceiver {
public void handleExecutorMessage(ICodeNode code);
}
有了模块间的消息收发机制后,我们做个试验,让UnaryExecutor去接收NoCommandExecutor的相关信息。UnaryExecutor如果想要接收NoCommandExecutor执行后的信息,特别是接收变量赋值后的事件通知,
那么它需要实现IExecutorReceiver接口,然后把自己注册到ExecutorBrocasterImpl对象中。代码如下:
public class UnaryNodeExecutor extends BaseExecutor implements IExecutorReceiver{
....
@Override
public Object Execute(ICodeNode root) {
executeChildren(root);
switch (production) {
....
case CGrammarInitializer.Unary_StructOP_Name_TO_Unary:
child = root.getChildren().get(0);
String fieldName = (String)root.getAttribute(ICodeKey.TEXT);
symbol = (Symbol)child.getAttribute(ICodeKey.SYMBOL);
Symbol args = symbol.getArgList();
while (args != null) {
if (args.getName().equals(fieldName)) {
break;
}
args = args.getNextSymbol();
}
if (args == null) {
System.err.println("access a filed not in struct object!");
System.exit(1);
}
root.setAttribute(ICodeKey.SYMBOL, args);
root.setAttribute(ICodeKey.VALUE, args.getValue());
if (isSymbolStructPointer(symbol) == true) {
structObjSymbol = symbol;
monitorSymbol = args;
ExecutorBrocasterImpl.getInstance().registerReceiverForAfterExe(this);
}
break;
....
}
}
@Override
public void handleExecutorMessage(ICodeNode code) {
int productNum = (Integer)code.getAttribute(ICodeKey.PRODUCTION);
Object object = code.getAttribute(ICodeKey.SYMBOL);
if(object == null || (object instanceof Symbol) == false) {
return;
}
Symbol symbol = (Symbol)object;
if (productNum == CGrammarInitializer.NoCommaExpr_Equal_NoCommaExpr_TO_NoCommaExpr
&& symbol == monitorSymbol) {
System.out.println("UnaryNodeExecutor receive msg for assign execution");
}
}
....
}
当解释器解析到有关结构体的访问或赋值时,UnaryNodeExecutor会在ExecutorBrocasterImpl中注册它自己,准备监听赋值语句的执行,一旦赋值语句被NoCommanExecutor执行后,它就会得到通知,于是他的handleExecutorMessage就会被调用。此时UnaryNodeExecutor会检测当前
被赋值的变量是否是结构体成员,如果是,它就打印出一条语句。
有了执行组件间的消息通讯机制后,我们就可以着手解决前面所说的问题了。
更详细的讲解和代码调试演示过程,请参看视频。
更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

本文介绍了解决C语言编译器中结构体指针未分配内存直接访问问题的方法,通过引入观察者模式实现在不同解释器模块间的通讯,确保在执行赋值等操作前后能通知相关模块。
236

被折叠的 条评论
为什么被折叠?



