Bootstrap

brainfuck原理及C语言实现

Brainfuck 是一种特殊的编程语言,它只有 8 个指令,而且非常简单。Brainfuck的源代码是由一系列的指令组成的,这些指令可以操作一个数组,也可以输出或输入数据。

要用 C 语言来编写 Brainfuck 的代码,可以按照以下步骤进行:

  1. 定义一个 char 类型的数组作为 Brainfuck 的数据空间。

  2. 读取用户输入的 Brainfuck 源代码。

  3. 根据读入的源代码,使用 switch-case 语句实现 Brainfuck 的 8 个指令,包括:+,-,>,<,.,,,[,]。
    >:将指针右移。如果指针已经达到数组的末尾,则返回出错。
    <:将指针左移。如果指针已经达到数组的开头,则返回出错。
    +:将指针所指的内存块的值加1。
    -:将指针所指的内存块的值减1。
    .:输出指针所指的内存块的值。(ASCII码)
    ,:等待输入,并将输入的字符存储到指针所指的内存块中。(ASCII码)
    [:如果指针所指的内存块的值为0,则跳转到对应的’]‘之后;否则继续执行下一个指令。
    ]:如果指针所指的内存块的值不为0,则跳转到对应的’['之后;否则继续执行下一个指令。
    该函数返回0表示执行成功,返回-1表示出错。

  4. 声明一个指针用于指向当前数据空间的位置。

实现 Brainfuck 中的循环语句。对于 [ 指令,需要记录当前指针位置,当该位置存储的值为 0 时则跳转到下一个 ] 指令;对于 ] 指令,需要回到上一个 [ 指令的位置重新执行。

简易的代码实现:

#include <stdio.h>

int main(){
    char array[30000] = {0}; // 定义数据空间
    char input[1000]; // 存储 Brainfuck 源代码
    char *ptr = array; // 定义指针指向数据空间头部

    printf("请输入 Brainfuck 源代码:\n");
    fgets(input, 1000, stdin); // 读取用户输入的 Brainfuck 源代码

    for (char *p = input; *p; p++)
    {
        switch (*p)
        {
            case '>': ptr++; break;
            case '<': ptr--; break;
            case '+': (*ptr)++; break;
            case '-': (*ptr)--; break;
            case '.': putchar(*ptr); break;
            case ',': *ptr = getchar(); break;
            case '[':
            {
                char *tmp = p;
                while (*ptr)
                {
                    p++;
                    while (*p)
                    {
                        if (*p == '[') p++;
                        else if (*p == ']')
                        {
                            if (--tmp == p) break;
                            else p++;
                        }
                    }
                }
                break;
            }
            case ']':
            {
                char *tmp = p;
                while (*ptr)
                {
                    p--;
                    while (p >= input)
                    {
                        if (*p == ']') p--;
                        else if (*p == '[')
                        {
                            if (++tmp == p) break;
                            else p--;
                        }
                    }
                }
                break;
            }
        }
    }
    return 0;
}

在上述代码中,我们定义了一个包含 30000 个字符的数组,作为 Brainfuck 的数据空间;然后读取用户输入的源代码,通过 switch-case 语句实现 Brainfuck 的 8 种指令;最后定义一个指针指向数据空间头部,实现循环语句。

需要注意的是,Brainfuck 的源代码较为特殊,一些字符可能会被特殊处理。因此,在实现时需要注意,不能被转义的字符需要单独处理。

除了用一个字符数组来模拟,我们还可以利用结构体来做一些比较复杂的实现:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include<string.h>

// Interpreter state
struct BFState {
    // The array and the size of the array.
    size_t array_len;
    uint8_t* array;

    // Pointer to the current position in the array; array <= cur < (array+array_len)
    uint8_t* cur;
};

//typedef unsigned char   uint8_t;

/*
parameter:
	state:
	一个指向 BFState 结构体的指针,该结构体记录了 brainfuck 程序的运行状态和数据。
	在函数内部,我们通过对 state 指针所引用的结构体进行操作,来实现 brainfuck 程序的运行。
	program:
	一个指向常量字符串的指针,该字符串表示要执行的 brainfuck 程序。
	在函数内部,我们需要解析该字符串并根据解析结果来执行各种指令,对state进行操作, 
	从而实现整个 brainfuck 程序的执行。
*/

// Return 0 on success, and -1 in case of an error (e.g., an out-of-bounds access).
int brainfuck(struct BFState* state, const char* program) {
    
    size_t program_len = 0; 	//brainfuck程序的长度 
    int ip = 0; 							// instruction pointer 程序指针 
	while(program[program_len] != '\0')	program_len++;
	
//	printf("%d\n%s\n", program_len, program);
	//扫描程序的每一个指令 
    while (ip < program_len) {
//    	printf("%c ", program[ip]);
        switch (program[ip]) {
            case '>':	// rightward shift
                if (state->cur + 1 >= state->array + state->array_len) {
                    return -1; // out of bounds
                }
                ip++; 
                state->cur++;
                break;
            case '<':	// leftward shift
                if (state->cur - 1 < state->array) {
                	printf("error here\n"); 
                    return -1; // out of bounds
                }
                ip++;
                state->cur--;
                break;
            case '+':	// plus 1
                (*(state->cur))++;
                ip++;
                break;
            case '-':	// minus 1
                (*(state->cur))--;
                ip++;
                break;
            case '.':	// output
                putchar(*state->cur);
                ip++;
                fflush(stdout); // ensure output is immediately written to the console
                break;
            case ',':	// Waits for input and stores the input character into the block of memory pointed to by the pointer
                *state->cur = getchar();
                ip++;
                break;
            case '[':
			    if ((*(state->cur)) == 0) { // move to the matching ]
					size_t nesting_level = 1;
					ip++;
			        while (ip < program_len) {
			            if (program[ip] == '[') nesting_level++;		//左括号 
						else if (program[ip] == ']') nesting_level--;	//右括号(抵消) 
			            if(nesting_level == 0) break;					//如果抵消之后为零,这个右括号就是与之匹配的 
			            ip++; 
			        }
			        if (ip == program_len) {
			            return -1; // no matching ]
			        }
			    }
			    ip++;
			    break;
			    
			case ']':
			    if ((*(state->cur)) != 0) { // move to the matching [
			        int nesting_level = 1;
			        ip--;
			        while (ip >= 0) {
			            if (program[ip] == ']')	nesting_level++;		//右括号
						else if (program[ip] == '[') nesting_level--;	//左括号(抵消)
			            if (nesting_level == 0) break;        			//如果抵消之后为零,这个右括号就是与之匹配的     
			            ip--;
			        }
			        if (ip < 0) {
			            return -1; // no matching [
			        }
			    }
			    ip++;
			    break;
            default:
                ip++; // invalid command
        }
    }

    return 0; // program successfully executed

}

int main(){
	size_t array_len = 3000;
    uint8_t* array = (uint8_t*)malloc(array_len*sizeof(uint8_t));
//    uint8_t* array = calloc(array_len, sizeof(uint8_t));
    if (!array) {
        fprintf(stderr, "could not allocate memory\n");
        return EXIT_FAILURE;
    }
	
	// 定义结构体,初始化结构体变量,指针指向首地址 
    struct BFState state = {
        .array_len = array_len,
        .array = array,
        .cur = array,
    };
    state.cur++;
    state.cur++;
    state.cur++;
    memset(state.array, 0, array_len);
//    state.array[0] = 1;
    
    int res = brainfuck(&state, "[[-<<+>>]<+[->+<]>>]<<<[>>[-<+>]<<[->>+<<]<]>>>");
	printf("res = %d\n", res);
	for(int i = 0; i < 50; i++) printf("%d ", state.array[i]);
	printf("\n");
	return 0;
}

//int main(int argc, char** argv) {
//    if (argc != 2) {
//        fprintf(stderr, "usage: %s <bfprog>\n", argv[0]);
//        return EXIT_FAILURE;
//    }
//
//    size_t array_len = 3000;
//    uint8_t* array = (uint8_t*)malloc(array_len*sizeof(uint8_t));
    uint8_t* array = calloc(array_len, sizeof(uint8_t));
//    if (!array) {
//        fprintf(stderr, "could not allocate memory\n");
//        return EXIT_FAILURE;
//    }
//	
//	// 定义结构体,初始化结构体变量,指针指向首地址 
//    struct BFState state = {
//        .array_len = array_len,
//        .array = array,
//        .cur = array,
//    };
//    int res = brainfuck(&state, argv[1]);
//    if (res) {
//        puts("an error occured");
//        return EXIT_FAILURE;
//    }
//
//    return EXIT_SUCCESS;
//}

在brainfuck语言中,程序通过操作存储在数据数组中的数据完成计算过程。在BFState这个结构体中,array指向数据数组的首地址,cur表示当前指向的位置。

因为brainfuck语言中操作数据数组的指令非常多,需要经常更改cur的指向,这些指令包括“I”(指针加1)、“D”(指针减1)等等。cur指针的存在可以让程序更加方便地对数据数组进行操作,而无需每次都从数组的首地址开始进行寻址。此外,cur还可以在不同操作中传递,实现了数据数组的多位置操作。

;