是什么导致我的 Brainfuck 转译器的输出 C 文件中出现中止陷阱?

What is causing the abort trap in the output C file for my Brainfuck transpiler?

我正在开发 C 到 Brainfuck 的转译器,基于 Brainfuck Wikipedia page 中描述的翻译。我测试过的每个程序都能完美运行,直到最后。一开始,我分配了一个 30000 字节的数组 char* ptr = malloc(30000 * sizeof(char));,最后我通过 free(ptr); 释放了它。我的转译器如下:

def make_tokens(chars):
    return [char for char in chars if char in {">", "<", "+", "-", ".", ",", "[", "]"}]

def translate_instruction(i):
    return {">": "++ptr;",
    "<": "--ptr;",
    "+": "++*ptr;",
    "-": "--*ptr;",
    ".": "putchar(*ptr);",
    ",": "*ptr = getchar();",
    "[": "while (*ptr) {",
    "]": "}"}[i] + "\n"

def to_c(instructions):
    with open("bfc.c", "w") as c_file:
        for header in ("stdio", "stdlib", "string"):
            c_file.write(f"#include <{header}.h>\n")
        c_file.write("\nint main() {\n\tchar* ptr = malloc(30000 * sizeof(char));\n")
        c_file.write("\tmemset(ptr, 0, 30000);\n")

        indentation = 1
        for i in make_tokens(instructions):
            c_file.write("\t" * indentation + translate_instruction(i))
            if i == "[": indentation += 1
            elif i == "]": indentation -= 1

        c_file.write("\tfree(ptr);\n}")

这个 Brainfuck 程序是 Sierpinski 的三角形,来自 here. I verified it with this 在线解释器。

to_c("""++++++++[>+>++++<<-]>++>>+<[-[>>+<<-]+>>]>+[
    -<<<[
        ->[+[-]+>++>>>-<<]<[<]>>++++++[<<+++++>>-]+<<++.[-]<<
        ]>.>+[>>]>+
    ]""")

我的程序生成以下 C 代码:

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

int main() {
    char* ptr = malloc(30000 * sizeof(char));
    memset(ptr, 0, 30000);
    ++*ptr;
    ++*ptr;
    ++*ptr;
    ++*ptr;
    ++*ptr;
    ++*ptr;
    ++*ptr;
    ++*ptr;
    while (*ptr) {
        ++ptr;
        ++*ptr;
        ++ptr;
        ++*ptr;
        ++*ptr;
        ++*ptr;
        ++*ptr;
        --ptr;
        --ptr;
        --*ptr;
        }
    ++ptr;
    ++*ptr;
    ++*ptr;
    ++ptr;
    ++ptr;
    ++*ptr;
    --ptr;
    while (*ptr) {
        --*ptr;
        while (*ptr) {
            ++ptr;
            ++ptr;
            ++*ptr;
            --ptr;
            --ptr;
            --*ptr;
            }
        ++*ptr;
        ++ptr;
        ++ptr;
        }
    ++ptr;
    ++*ptr;
    while (*ptr) {
        --*ptr;
        --ptr;
        --ptr;
        --ptr;
        while (*ptr) {
            --*ptr;
            ++ptr;
            while (*ptr) {
                ++*ptr;
                while (*ptr) {
                    --*ptr;
                    }
                ++*ptr;
                ++ptr;
                ++*ptr;
                ++*ptr;
                ++ptr;
                ++ptr;
                ++ptr;
                --*ptr;
                --ptr;
                --ptr;
                }
            --ptr;
            while (*ptr) {
                --ptr;
                }
            ++ptr;
            ++ptr;
            ++*ptr;
            ++*ptr;
            ++*ptr;
            ++*ptr;
            ++*ptr;
            ++*ptr;
            while (*ptr) {
                --ptr;
                --ptr;
                ++*ptr;
                ++*ptr;
                ++*ptr;
                ++*ptr;
                ++*ptr;
                ++ptr;
                ++ptr;
                --*ptr;
                }
            ++*ptr;
            --ptr;
            --ptr;
            ++*ptr;
            ++*ptr;
            putchar(*ptr);
            while (*ptr) {
                --*ptr;
                }
            --ptr;
            --ptr;
            }
        ++ptr;
        putchar(*ptr);
        ++ptr;
        ++*ptr;
        while (*ptr) {
            ++ptr;
            ++ptr;
            }
        ++ptr;
        ++*ptr;
        }
    free(ptr);
}

clang 的编译和 运行 的输出是这样的:

                               *
                              * *
                             *   *
                            * * * *
                           *       *
                          * *     * *
                         *   *   *   *
                        * * * * * * * *
                       *               *
                      * *             * *
                     *   *           *   *
                    * * * *         * * * *
                   *       *       *       *
                  * *     * *     * *     * *
                 *   *   *   *   *   *   *   *
                * * * * * * * * * * * * * * * *
               *                               *
              * *                             * *
             *   *                           *   *
            * * * *                         * * * *
           *       *                       *       *
          * *     * *                     * *     * *
         *   *   *   *                   *   *   *   *
        * * * * * * * *                 * * * * * * * *
       *               *               *               *
      * *             * *             * *             * *
     *   *           *   *           *   *           *   *
    * * * *         * * * *         * * * *         * * * *
   *       *       *       *       *       *       *       *
  * *     * *     * *     * *     * *     * *     * *     * *
 *   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
a.out(91318,0x11b627e00) malloc: *** error for object 0x7fb354808883: pointer being freed was not allocated
a.out(91318,0x11b627e00) malloc: *** set a breakpoint in malloc_error_break to debug
Abort trap: 6

如您所见,程序运行得很好,直到结束。 free 是导致中止陷阱的原因,我不明白这是因为我是通过堆而不是堆栈分配数组的。 LLDB 在这里对我帮助不大。这太令人困惑了!有谁知道我做错了什么?

Process 93919 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100003f47 a.out`main at bfc.c:122:7
   119          ++ptr;
   120          ++*ptr;
   121          }
-> 122      free(ptr);
   123  }
Target 0: (a.out) stopped.
(lldb) n
a.out(93919,0x1000e7e00) malloc: *** error for object 0x100808883: pointer being freed was not allocated
a.out(93919,0x1000e7e00) malloc: *** set a breakpoint in malloc_error_break to debug
Process 93919 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
    frame #0: 0x00007fff20340462 libsystem_kernel.dylib`__pthread_kill + 10
libsystem_kernel.dylib`__pthread_kill:
->  0x7fff20340462 <+10>: jae    0x7fff2034046c            ; <+20>
    0x7fff20340464 <+12>: mov    rdi, rax
    0x7fff20340467 <+15>: jmp    0x7fff2033a6a1            ; cerror_nocancel
    0x7fff2034046c <+20>: ret    
Target 0: (a.out) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGABRT
  * frame #0: 0x00007fff20340462 libsystem_kernel.dylib`__pthread_kill + 10
    frame #1: 0x00007fff2036e610 libsystem_pthread.dylib`pthread_kill + 263
    frame #2: 0x00007fff202c1720 libsystem_c.dylib`abort + 120
    frame #3: 0x00007fff201a2430 libsystem_malloc.dylib`malloc_vreport + 548
    frame #4: 0x00007fff201a54c8 libsystem_malloc.dylib`malloc_report + 151
    frame #5: 0x0000000100003f50 a.out`main at bfc.c:122:2
    frame #6: 0x00007fff20389621 libdyld.dylib`start + 1
(lldb) 

这样做:

char* ptr = malloc(30000 * sizeof(char));
memset(ptr, 0, 30000);
char *orig = ptr;

// Code

free(orig);

由于您要递增和递减指针 ptr,您当然不能相信它指向与初始化时相同的位置。如果是这样,那可能性很小。

为了养成良好的习惯:

  • sizeof(char) 始终为 1,因此要么使用 malloc(30000 * sizeof *ptr)(无论类型如何都有效)或简单地使用 malloc(30000)

  • 使用 calloc 而不是 malloc 来保存对 memset

    的调用
char *buffer = calloc(30000, sizeof *buffer);
char *ptr = buffer;

// Code

free(buffer);

但说实话。虽然确保始终释放资源通常是避免内存泄漏的好事,但在 main 函数的末尾通常不需要这样做。除非您正在编写嵌入式系统、操作系统或非常罕见和特殊的东西,否则您可以相信操作系统会在程序退出时为您释放所有分配的内存。对于此应用程序,您可以根据需要跳过对 free 的调用。