重新分配导致 "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
中。两者都需要更新变量,并且对这个变量的访问要同步。
我正在用 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
中。两者都需要更新变量,并且对这个变量的访问要同步。