重新分配导致 "double free or corruption (fasttop)" 错误

realloc causes "double free or corruption (fasttop)" error

我正在用 C 编写一个终端贪吃蛇游戏。用户输入是使用读取方向指针的 pthread 处理的。为了说明蛇的方向,每次蛇吃水果时,动态分配的方向向量的大小都会增加。为此,我创建了一个 append() 函数,它将向量指针、蛇的当前大小和要追加的值(最后一个方向)作为参数。

游戏正常运行,直到当前大小为 5,然后中止并出现“双重释放或损坏”错误。我完全不知道为什么会这样。任何提示或想法将不胜感激。

完整代码如下:

// a simple snake game which uses pthread.h library to handle user input
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <termios.h> // for getch
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

#define SIZE_X 20
#define SIZE_Y 20
#define TIME 200 // game update interval (milliseconds)

bool collision;

typedef struct position {
    int x;
    int y;
} position;

int *append(int *v, int size, int value) // append data to vector
{
    size = size + 1;
    puts("Reallocing memory ...");
    printf("\nCurrent size: %d\n", size);
    v = (int*)realloc(v, (size)*sizeof(int*));
    puts("Memory reallocating sucessful!");
    v[size-1] = value;
}

int msleep(long msec) // sleep for the requested number of milliseconds
{
    struct timespec ts;
    int res;

    if (msec < 0)
    {
        errno = EINVAL;
        return -1;
    }

    ts.tv_sec = msec / 1000;
    ts.tv_nsec = (msec % 1000) * 1000000;

    do {
        res = nanosleep(&ts, &ts);
    } while (res && errno == EINTR);

    return res;
}

char getch(void) // get char
{
    char buf = 0;
    struct termios old = {0};
    fflush(stdout);
    if(tcgetattr(0, &old) < 0)
        perror("tcsetattr()");
    old.c_lflag &= ~ICANON;
    old.c_lflag &= ~ECHO;
    old.c_cc[VMIN] = 1;
    old.c_cc[VTIME] = 0;
    if(tcsetattr(0, TCSANOW, &old) < 0)
        perror("tcsetattr ICANON");
    if(read(0, &buf, 1) < 0)
        perror("read()");
    old.c_lflag |= ICANON;
    old.c_lflag |= ECHO;
    if(tcsetattr(0, TCSADRAIN, &old) < 0)
        perror("tcsetattr ~ICANON");
    printf("%c\n", buf);
    return buf;
 }

int randomInt(int b, int a) // generates random integer between b and a
{
    return ((rand() % (b+1 - a)) + a);
}

void *readMove(void *data) // argument to p_thread to read user input
{
    int *direction = ((int*) data); // pass function void pointer to direction pointer

    pthread_detach(pthread_self());
    while (collision) {
        if (getch() == '3') { // if the first value is esc
            fputs("3[A3[2K",stdout);
            rewind(stdout);
            getch(); // skip the [
            fputs("3[A3[2K",stdout);
            rewind(stdout);
            switch(getch()) { // the real value
                case 'A':
                    // code for arrow up
                    fputs("3[A3[2K",stdout);
                    rewind(stdout);
                    *direction = 0;
                    break;
                case 'B':
                    // code for arrow down
                    fputs("3[A3[2K",stdout);
                    rewind(stdout);
                    *direction = 2;
                    break;
                case 'C':
                    // code for arrow right
                    fputs("3[A3[2K",stdout);
                    rewind(stdout);
                    *direction = 1;
                    break;
                case 'D':
                    // code for arrow left
                    fputs("3[A3[2K",stdout);
                    rewind(stdout);
                    *direction = 3;
                    break;
            }
        }
    }
    pthread_exit(NULL);
}

void beginGame() // welcome screen
{
    int i;
    for (i = 0; i < 4; i++) {
        printf("WELCOME TO C-SNAKE! - Initializing game in %d seconds ...\n", (3-i));
        sleep(1);
        fputs("3[A3[2K",stdout);
        rewind(stdout);
    }
}

void clearScreen() // clear game screen
{
    int i;
    for (i=0; i <= SIZE_Y; i++) {
        fputs("3[A3[2K",stdout);
        rewind(stdout);
    }
}

void printGame(char game[SIZE_X][SIZE_Y]) // print current game
{
    int i, j;
    printf("\r");
    for (i=0; i<SIZE_X; i++) {
        printf("\n");
        for (j=0; j<SIZE_Y; j++) {
            printf("%c ", game[i][j]);
        }
    }
    printf("\n");
}

void initializeGame(char game[SIZE_X][SIZE_Y], position *player_position, int *direction) // initializes variables
{
    int i, j;

    // initializes movement direction - 0: up, 1: right, 2: down, 3: left
    *direction = 2;

    // initializes player position
    player_position->x = 1;
    player_position->y = 1;

    // initializes board
    for (i=0; i<SIZE_X; i++) {
        for (j=0; j<SIZE_Y; j++) {
            if ((i == player_position->x) && (j == player_position->y)) {
                game[i][j] = 'o';
            }
            else {
                game[i][j] = '_';
            }
        }
    }
}

bool checkCollision(position *player_position) // checks for collision (game over)
{
    if (player_position->x == 0) {
        return false;
    }
    else if (player_position->y == 0) {
        return false;
    }
    else if (player_position->x == (SIZE_X-1)) {
        return false;
    }
    else if (player_position->y == (SIZE_Y-1)) {
        return false;
    }
    else {
        return true;
    }
}

void updateGame(char game[SIZE_X][SIZE_Y], position *player_position, int *direction, int it, int *length) // updates snake position
{
    int i, j, x_inc=0, y_inc=0;

    switch (direction[it])
    {
        case 0:
            x_inc = -1;
            break;
        case 1:
            y_inc = 1;
            break;
        case 2:
            x_inc = 1;
            break;
        case 3:
            y_inc = -1;
            break;
    }

    player_position->x = player_position->x+x_inc;
    player_position->y = player_position->y+y_inc;

    for (i=0; i<SIZE_X; i++) {
        for (j=0; j<SIZE_Y; j++) {
            if ((i == player_position->x) && (j == player_position->y)) {
                if (game[i][j] == '+') {
                    *length = *length+1;
                    append(direction, *length, direction[it]);
                }
                game[i][j] = 'o';
            }
            else if (game[i][j] != '+') {
                game[i][j] = '_';
            }
        }
    }
}

void generateFruit(char game[SIZE_X][SIZE_Y]) // generate random fruits
{
    position fruit_position;
    
    while (true) {
        fruit_position.x = randomInt(SIZE_X-2, 1);
        fruit_position.y = randomInt(SIZE_Y-2, 1);
        if (game[fruit_position.x][fruit_position.y] == '_') {
            game[fruit_position.x][fruit_position.y] = '+';
            break;
        }
    }
}

int main(int argc, char *argv[]) 
{
    char game[SIZE_X][SIZE_Y]; // game tiles
    int i=0; // auxiliary variable
    int length = 0; // initial length
    int current_length;
    int counter = 0; // time counter
    float total_time; // total game time
    int fruit_interval = 10; // time interval (seconds) to random fruit generation
    position player_position; // player head position (x,y)
    int *direction = (int*)malloc(1*sizeof(int*)); // moving direction
    int rc;
    pthread_t pthread; // thread to handle player input

    srand(time(NULL)); // uses time to generate random seed

    beginGame(); // intro screen
    initializeGame(game, &player_position, direction); // initializes variables
    collision = checkCollision(&player_position);
    
    rc = pthread_create(&pthread, NULL, readMove, (void *)direction);
    if (rc) {
        printf("\nError - return code from pthread_create is %d\n", rc);
        return EXIT_FAILURE;
    }

    while (collision) {
        collision = checkCollision(&player_position);
        printGame(game);
        msleep(TIME);
        clearScreen();
        updateGame(game, &player_position, direction, i, &length);
        counter++;
        if ((counter % fruit_interval) == 0) {
            generateFruit(game);
        }
    }
    total_time = TIME*counter/1000;
    printf("\n*** GAME OVER! ***\n\n");
    printf("\nTotal time: %.1fs\n\n", total_time);
    puts("Press any key to quit ...");

    free(direction);
    pthread_exit(NULL);
}

I have absolutely no idea why that happens.

发生这种情况是因为您以某种方式损坏了堆。常见方式:free两次,free未分配(例如指向堆栈)指针,在块结束之前或之后写入等

这些堆损坏错误通常很难使用代码检查或调试器进行调试,因为错误通常是在执行的后期检测到的,可能是在完全不相关的代码中。

幸运的是,有专门的工具可以更容易地找到这些错误 。如果您的平台支持 Address Sanitizer,那么您很幸运:只需使用 gcc -fsanitize=address -g ... 重新构建您的程序,它就会指出错误所在。 Valgrind 是一个较早的工具,它也可以帮助解决相同的 class 错误。

这是 运行 游戏 -fsanitize=address 的输出:

$ ./a.out
Reallocing memory ...

Current size: 2
Memory reallocating sucessful!
=================================================================
==1713589==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x563c5223c583 bp 0x7ffdacfb9a10 sp 0x7ffdacfb9a08
READ of size 4 at 0x602000000010 thread T0
    #0 0x563c5223c582 in updateGame /tmp/t.c:204
    #1 0x563c5223cd5f in main /tmp/t.c:284
    #2 0x7f8cb9f64e49 in __libc_start_main ../csu/libc-start.c:314
    #3 0x563c5223b2c9 in _start (/tmp/a.out+0x22c9)

0x602000000010 is located 0 bytes inside of 8-byte region [0x602000000010,0x602000000018)
freed by thread T0 here:
    #0 0x7f8cba1d1b48 in __interceptor_realloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:164
    #1 0x563c5223b3d8 in append /tmp/t.c:27
    #2 0x563c5223c816 in updateGame /tmp/t.c:228
    #3 0x563c5223cd5f in main /tmp/t.c:284
    #4 0x7f8cb9f64e49 in __libc_start_main ../csu/libc-start.c:314

previously allocated by thread T0 here:
    #0 0x7f8cba1d17cf in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:145
    #1 0x563c5223cc3d in main /tmp/t.c:263
    #2 0x7f8cb9f64e49 in __libc_start_main ../csu/libc-start.c:314
...

释放后堆使用意味着:你已经free释放了一些内存(例如realloc释放了旧的内存块),但之后继续使用它。

极有可能 append 应该 return v;,调用者应该像这样更改:

                 if (game[i][j] == '+') {
                     *length = *length+1;
-                    append(direction, *length, direction[it]);
+                    direction = append(direction, *length, direction[it]);
                 }
                 game[i][j] = 'o';

然而,即使进行了上述更改,其他堆损坏错误仍然存​​在。

这是因为(正如 dirck 之前评论的那样),您有两个 direction 实例——一个在主线程中,一个在 readMove 中。两者都需要更新变量,并且对这个变量的访问要同步。