java开发C语言模拟器:sizeof函数的实现

来源:互联网 发布:扫鬼软件下载 编辑:程序博客网 时间:2024/06/05 16:44

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

在C语言中,有一个非常基础,也是极其重要的函数,就是sizeof, 这个函数的作用是计算变量所占内存的大小。只有知道了对应变量的大小,那么我们才能动态分配对应大小的内存。

如果sizeof 计算的对象,仅仅是简单的基础类型变量,例如 int, char 等,那么他们的大小很容易计算,由于我们当前代码假设运行在32位机器上,因此如果传入sizeof的变量是int , 或是 指针类型,那么该变量的内存大小就是4字节,如果变量的类型是short, 那么它的内存大小就是2字节,如果是char, 那么内存大小就是1字节。

因此实现sizeof的时候,我们通过传入的变量名,找到它对应的Symbol对象,通过Symbol对象找到它的数据类型,根据上面的分类就可以得知该对象的内存大小了。

问题在于,sizeof传入的可能是符合数据类型,也就是说,传入的变量可以是结构体,例如下面的结构体:

 struct TAG {        int p;        char c;        int arr[3];    };

结构体的内存大小,实际上是组成该结构体的的内部成员大小之和,因此计算结构体TAG的内存大小,就得计算p, c, 和arr[3]这三个成员的大小后求和。

一种更难的情况是结构体间套,也就是一个结构体里面还包含结构体成员,这么看来,sizeof要正确实现,必须是一种层级递归的方式。

我们采用深度优先的搜索来实现sizeof, 深度优先搜索主要是用于图的遍历,这里我们把该算法平移到sizeof的计算上来,深度优先搜索的基本原理如下,假定一个有向图:
这里写图片描述

在遍历上面的有向图时,如果被访问的节点含有子节点,那么先访问其子节点,然后在访问该节点或该节点的兄弟。那么上面的有向图,深度优先遍历的次序可以如下:1->2->3->4->3->2->5->6->5->1

同理,在实现sizeof函数时,如果传进来的变量是结构体,那么我们先通过sizeof计算结构体每个成员的大小,然后再把每个成员的大小加总,得到当前结构体的大小。

由此sizeof函数实现的基本逻辑是:
1, 如果传入的变量是基本数据类型,那么直接返回该变量的字节大小。
2, 如果传入的变量是结构体,那么再次调用sizeof计算每个成员的大小,然后加总得到当前结构体的大小。
3, 如果当前变量是数组类型,那么把前两步计算的大小乘以数组的元素个数,得到的就是当前变量的大小。

由于sizeof是库函数,因此它的实现在ClibCall.java 中,实现代码如下:

public class ClibCall {    private Set<String> apiSet;    private ClibCall() {        apiSet = new HashSet<String>();        apiSet.add("printf");        apiSet.add("malloc");        apiSet.add("sizeof");    }    ....     public Object invokeAPI(String funcName) {        switch (funcName) {        case "printf":            return handlePrintfCall();        case "malloc":            return handleMallocCall();        case "sizeof":            return handleSizeOfCall();        default:            return null;        }    }private Integer calculateVarSize(Symbol symbol) {        int size = 0;        if (symbol.getArgList() == null) {            size = symbol.getByteSize();        } else {            Symbol head = symbol.getArgList();            while (head != null) {                size += calculateVarSize(head);                head = head.getNextSymbol();            }        }        Declarator declarator = symbol.getDeclarator(Declarator.ARRAY);         if ( declarator != null) {            size = size * declarator.getElementNum();        }        return size;    }

calculateVarSize就是解释器对sizeof函数的解释执行,传入参数是sizeof变量对应的Symbol对象,如果getArgsList 返回为空,那表明当前变量不是结构体类型,于是直接通过调用Symbol的getByteSize 函数获取基础数据类型的字节大小。

如果getArgsList返回不是空,那么当前变量类型是结构体,于是把结构体中的每个成员取得,然后递归的调用calculateVarSize去计算每个成员的内存大小。

最后判断下,当前变量是否是数组,如果是数组的话,那么就要把单个变量的大小乘以数组中元素个数,才能得到总的内存大小。

以上代码就是实现sizeof的主逻辑。解释器还有一些地方,代码需要变动,函数调用需要获得传入参数,以前我们的做法是,先获得传入参数的值,然后把这些值存到FunctionArgumentList这个类中,以便解释器执行库函数时容易取得传入参数的值。

sizeof函数需要的不再是变量的值,而是变量对应的Symbol对象,因此我们在解析传入参数时,需要得到参数对应的Symbol对象,由于解析传入参数是在类
ArgsExecutor中实现的,因此需要在该类中,增加获取输入参数Symbol对象的代码:

package backend;import java.util.ArrayList;import frontend.CGrammarInitializer;public class ArgsExecutor extends BaseExecutor {    @Override    public Object Execute(ICodeNode root) {        int production = (Integer)root.getAttribute(ICodeKey.PRODUCTION);        ArrayList<Object> argList = new ArrayList<Object>();        ArrayList<Object> symList = new ArrayList<Object>();        ICodeNode child ;        switch (production) {        case CGrammarInitializer.NoCommaExpr_TO_Args:            child = (ICodeNode)executeChild(root, 0);            Object objVal = child.getAttribute(ICodeKey.VALUE);            argList.add(objVal);            objVal = child.getAttribute(ICodeKey.SYMBOL);            symList.add(objVal);            break;        case CGrammarInitializer.NoCommaExpr_Comma_Args_TO_Args:            child = executeChild(root, 0);            objVal = child.getAttribute(ICodeKey.VALUE);            argList.add(objVal);            objVal = child.getAttribute(ICodeKey.SYMBOL);            symList.add(objVal);            child = (ICodeNode)executeChild(root, 1);            ArrayList<Object> list = (ArrayList<Object>)child.getAttribute(ICodeKey.VALUE);            argList.addAll(list);            list = (ArrayList<Object>)child.getAttribute(ICodeKey.SYMBOL);            symList.add(list);            break;        }        root.setAttribute(ICodeKey.VALUE, argList);        root.setAttribute(ICodeKey.SYMBOL, symList);        return root;    }}

最后,FunctionArgumentList这个类也要做一些改动,原来这个类只提供接口存储参数的值,这次要增加接口,存储参数的Symbol对象,改动如下:

package backend;import java.util.ArrayList;import java.util.Collections;public class FunctionArgumentList {    private static FunctionArgumentList argumentList = null;    private ArrayList<Object> funcArgList = new ArrayList<Object>();    private ArrayList<Object> argSymList = new ArrayList<Object>();    public static FunctionArgumentList getFunctionArgumentList() {        if (argumentList == null) {            argumentList = new FunctionArgumentList();        }        return argumentList;    }    public void setFuncArgList(ArrayList<Object> list) {        funcArgList = list;    }    public void setFuncArgSymbolList(ArrayList<Object> list) {        this.argSymList = list;    }    public ArrayList<Object> getFuncArgList(boolean reverse) {        if (reverse == true) {            Collections.reverse(funcArgList);        }        return funcArgList;    }    public ArrayList<Object> getFuncArgSymsList(boolean reverse) {        if (reverse == true) {            Collections.reverse(argSymList);        }        return argSymList;    }    private FunctionArgumentList() {}}

有了以上的代码实现后,我们的解释器就可以解释执行如下C语言代码了:

void main() {    struct TAG {        int p;        char c;        int arr[3];    };    struct TAG myTag[3];    int size ;    size = sizeof(myTag);    printf("The size of struct TAG is : %d", size);}

上面的代码中,定义了一个结构体叫TAG, 结构体里有三个成员,其中一个是数组,一个结构体变量的内存大小是:4 + 1 + 4*3 = 17. 由于代码中又定义了一个含有三个成员的结构体数组myTag,因此myTag的总大小是51 = 17*3.

我们的解释器解释执行上面代码后得到的结果为:
The size of struct TAG is : 51

也就是我们的解释器确实能够正确的执行sizeof函数。

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

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

0 0
原创粉丝点击