pythoncmallocsigabrtbrainfuck

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


I am working on a C to Brainfuck transpiler, based on the translation described in Brainfuck's Wikipedia page. Each program that I have tested works perfectly, until the end. In the beginning, I allocate an array of 30000 bytes, char* ptr = malloc(30000 * sizeof(char));, and in the end I free it via free(ptr);. My transpiler is below:

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}")

This Brainfuck program is Sierpinski's triangle, from here. I verified it with this online interpreter.

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

My program generates the following C code:

#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);
}

The output of compiling and running that with clang was this:

                               *
                              * *
                             *   *
                            * * * *
                           *       *
                          * *     * *
                         *   *   *   *
                        * * * * * * * *
                       *               *
                      * *             * *
                     *   *           *   *
                    * * * *         * * * *
                   *       *       *       *
                  * *     * *     * *     * *
                 *   *   *   *   *   *   *   *
                * * * * * * * * * * * * * * * *
               *                               *
              * *                             * *
             *   *                           *   *
            * * * *                         * * * *
           *       *                       *       *
          * *     * *                     * *     * *
         *   *   *   *                   *   *   *   *
        * * * * * * * *                 * * * * * * * *
       *               *               *               *
      * *             * *             * *             * *
     *   *           *   *           *   *           *   *
    * * * *         * * * *         * * * *         * * * *
   *       *       *       *       *       *       *       *
  * *     * *     * *     * *     * *     * *     * *     * *
 *   *   *   *   *   *   *   *   *   *   *   *   *   *   *   *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
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

As you can see, the program runs just fine, just until the end. The free is what causes the abort trap, which I do not understand because I allocated my array via the heap, not the stack. LLDB does not help me very much here. This is so confusing! Does anyone know what I am doing wrong?

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) 

Solution

  • Do this:

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

    Since you're incrementing and decrementing the pointer ptr you of course cannot trust it to point at the same place as it did upon initialization. If fact, that would be very unlikely.

    And just for good habits:

    char *buffer = calloc(30000, sizeof *buffer);
    char *ptr = buffer;
    
    // Code
    
    free(buffer);
    

    But to be honest. While making sure that you always free resources is a good thing in general to avoid memory leaks, this is in general not necessary in the end of the main function. Unless you're programming embedded systems, operating systems or something very rare and special, you can trust the operating system to release all allocated memory for you upon program exit. For this application, you can skip the call to free if you want.