首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对玩具Vm的尝试

对玩具Vm的尝试
EN

Code Review用户
提问于 2020-06-26 12:59:06
回答 1查看 620关注 0票数 10

我通常使用c++,所以它可能不是c语言的最佳实践。

这是一个基于堆栈的玩具vm,因此它非常原始,并且没有按位排列的指令。

对于一个玩具vm来说,64k可能有点过分了。

编码很难

代码语言:javascript
复制
#include <stdio.h>
#include "vm.h"

int main() {
    Vm *vm = new_vm();
    i32 buffer[] = {
        0x00000A01, /* push 0x0A(\n) */
        0x00004301, /* push 0x43(C)  */
        0x00004201, /* push 0x42(B)  */
        0x00004101, /* push 0x41(A)  */
        0x00000009, /* output */
        0x00000002, /* pop */
        0x00000009,
        0x00000002,
        0x00000009,
        0x00000002,
        0x00000009,
        0x00000000  /* halt */
    };
    for (int i = 0; i < sizeof(buffer); i++) {
        vm->mem[vm->pc+i] = buffer[i];
    }
    run_vm(vm);
    free_vm(vm);
    return 0;
}

vm.h

代码语言:javascript
复制
#ifndef VM_H_
#define VM_H_

#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>

typedef uint32_t i32; /* other numbers */
typedef int32_t si32; /* stack pointer */
typedef unsigned char byte;

typedef struct {
    i32 mem[0xffff]; /* approx. 64k */
    si32 sp;
    i32 pc;
    
    i32 raw;
    byte opc;
    i32 param;
    
    bool running;
} Vm;

Vm *new_vm();
void reset_vm(Vm *vm);
void free_vm(Vm *vm);
void run_vm(Vm *vm);

#endif

vm.c

代码语言:javascript
复制
#include <stdio.h>
#include "vm.h"

Vm *new_vm() {
    Vm *ret = (Vm*)malloc(sizeof(Vm));
    ret->pc = 1024; /* add space for the stack */
    ret->sp = -1;
    ret->running = true;
    return ret;
}

void reset_vm(Vm *vm) {
    vm->running = true;
    for (int i = 0; i < 0xffff; i++) {
        vm->mem[i] = 0;
    }
    vm->sp = -1;
    vm->pc = 1024;
}
void free_vm(Vm *vm) {
    free(vm);
    vm = NULL;
}

static void fetch(Vm *vm) {
    vm->raw = vm->mem[vm->pc++];
}

static void decode(Vm *vm) {
    /* style of opcode
     * 24 bits for parameter
     * a byte for the opcode 
    */
    vm->opc = vm->raw & 0xff;
    vm->param = (vm->raw & 0xffffff00) >> 8;
}

static void execute(Vm *vm) {
    switch(vm->opc) {
        case 0x00: /* halt */
            vm->running = false;
            printf("Halt\n");
        break;
        case 0x01: /* push */
            vm->mem[++vm->sp] = vm->param;
        break;
        case 0x02: /* pop */
            vm->mem[vm->sp--] = 0;
        break;
        case 0x03: /* store */
            vm->mem[ vm->mem[vm->sp - 1] ] = vm->mem[vm->sp];
        break;
        case 0x04: /* load */
            vm->mem[vm->sp + 1] = vm->mem[ vm->mem[vm->sp] ];
            ++vm->sp;
        break;
        case 0x05: /* add */
            vm->mem[vm->sp + 1] = vm->mem[vm->sp] + vm->mem[vm->sp - 1];
            ++vm->sp;
        break;
        case 0x06: /* sub */
            vm->mem[vm->sp + 1] = vm->mem[vm->sp - 1] - vm->mem[vm->sp];
            ++vm->sp;
        break;
        case 0x07: /* mul */
            vm->mem[vm->sp + 1] = vm->mem[vm->sp] * vm->mem[vm->sp - 1];
            ++vm->sp;
        break;
        case 0x08: /* div */
            vm->mem[vm->sp + 1] = vm->mem[vm->sp - 1] / vm->mem[vm->sp];
            ++vm->sp;
        break;
        case 0x09: /* outc */
            printf("%c", vm->mem[vm->sp]);
        break;
        case 0x0A: /* inpc */
            vm->mem[++vm->sp] = getchar();
        break;
    }
}

void run_vm(Vm *vm) {
    while(vm->running) {
        fetch(vm);
        decode(vm);
        execute(vm);
    }
}
EN

回答 1

Code Review用户

发布于 2020-06-26 14:51:58

缺失错误检查

我通常使用c++,所以它可能不是c语言的最佳实践。

在C++中,当内存分配在new中失败时,抛出异常时,在C编程语言中使用malloc()calloc()realloc()时情况并非如此。在任何内存分配调用之后都需要进行额外的检查。检查的目的是查看返回的内存是否为NULL,是否分配失败,通过指针引用是否为未知行为。

代码语言:javascript
复制
Vm *new_vm() {
    Vm *ret = (Vm*)malloc(sizeof(Vm));
    if (!ret)
    {
        fprintf(stderr, "Allocation of the Virtual Machine failed.\n");
        return ret;
    }
    
    ret->pc = 1024; /* add space for the stack */
    ret->sp = -1;
    ret->running = true;
    return ret;
}

main()中:

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#include "vm.h"

int main() {
    Vm *vm = new_vm();
    if (vm == NULL)
    {
        fprintf(stderr, "Exiting Toy Virtual Machine.\n");
        return EXIT_FAILURE;
    }
    i32 buffer[] = {
            0x00000A01, /* push 0x0A(\n) */
            0x00004301, /* push 0x43(C)  */
            0x00004201, /* push 0x42(B)  */
            0x00004101, /* push 0x41(A)  */
            0x00000009, /* output */
            0x00000002, /* pop */
            0x00000009,
            0x00000002,
            0x00000009,
            0x00000002,
            0x00000009,
            0x00000000  /* halt */
    };
    for (int i = 0; i < sizeof(buffer); i++) {
        vm->mem[vm->pc+i] = buffer[i];
    }
    run_vm(vm);
    free_vm(vm);
    return EXIT_SUCCESS;
}

只包括必要的标头

在发布的代码中,stdlib.h包含在vm.h中,stdlib.hmain()中没有必要,只有在vm.c中。为了缩小头文件和源文件的范围,只能包括所需的内容。在其他方面,这将减少编译时间,也可能减少链接问题。

开关语句中缺少默认大小写

在开关语句中使用default : case语句来处理尚未指定的情况通常是一种很好的编程实践:

代码语言:javascript
复制
static void execute(Vm *vm) {
    switch(vm->opc) {
        default:
            fprintf(stderr, "Unknown Opcode in execute(). 0x%x\n", vm->opc);
            return;
        case 0x00: /* halt */
            vm->running = false;
            printf("Halt\n");
            break;
        ...
    }

然后实现了通过该函数的所有可能的路径。这在C或C++以及大多数具有switch语句的其他编程语言中都是正确的。

对操作码使用ENUM

如果使用较少的数值常量和更多的符号常量,则代码将更具可读性。在C中,有两种方法可以做到这一点,要创建单个符号常量,请使用宏定义

代码语言:javascript
复制
#define SYMBOL  VALUE

或者使用枚举

代码语言:javascript
复制
typedef enum {
    HALT = 0x00,
    PUSH = 0x01,
    POP = 0x02,
    ...
    INPUTCHAR = 0x0A
} OPCODE;


typedef struct {
    i32 mem[0xffff]; /* approx. 64k */
    si32 sp;
    i32 pc;

    i32 raw;
    OPCODE opc;
    i32 param;

    bool running;
} Vm;

使用无符号类型作为索引

堆栈指针索引当前是一个有符号整数,并被初始化为-1,这是我认为不好的做法,因为堆栈-1会导致未知的行为。最好使用size_tunsigned作为堆栈指针索引。这将迫使一些领域发生变化,但以下是我的建议:

  1. running初始化为false而不是true。
  2. 将sp初始化为零
  3. 只有索引堆栈,如果running为真,则增加堆栈指针
  4. 在循环之前的run_vm(Vm *vm)开头,将running设置为true
  5. 更改reset_vm(Vm *vm)的实现以匹配上述所有内容

类型名称和变量名称

一开始,我对Vm感到困惑,不管它是虚拟机还是虚拟机,其他变量和类型也是如此。编写良好的代码是自文档化的,不需要大量注释,类型名称和变量名称在这方面起了很大作用。在我看来,Vm应该改名为VirtualMachinesp应该改名为StackPointerpc应该改名为ProgramCounter等等。

在可用的地方使用库函数

C++包含std::memset(),C中的memset()先于C++。函数reset_vm()应该使用memset()而不是用于重置内存的循环。函数memset()应该比当前循环更快。

代码语言:javascript
复制
void reset_vm(Vm *vm) {
    vm->running = true;
    memset(&vm->mem[0], 0, sizeof(*vm->mem[0]) * 0xffff);
    vm->sp = -1;
    vm->pc = 1024;
}
票数 9
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/244566

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档