【问题标题】:strcpy: detected source and destination buffer overlapstrcpy:检测到源缓冲区和目标缓冲区重叠
【发布时间】:2017-07-18 12:40:54
【问题描述】:

这是我对康威生命游戏的实现。

我目前正在尝试实现重复检测,它允许程序检测长达 4 代的重复。为此,我使用了一个名为statelist 的列表,该列表旨在存储当前状态后面的4 个状态(状态表示为由函数serialize_board() 创建的字符串)。为了将新状态推送到statelist(并可能丢弃跟踪中的最后一个状态),我实现了push_list() 函数。但是在push_list()函数中,出现了错误:

2017-07-18 13:26:32.496180+0100 Assignment 3[38082:4064098] detected source and destination buffer overlap

push_list 所需的功能在函数初始化的注释中描述。我不明白字符串如何重叠。我已经为每个字符串的statelistnrows*ncols+1 字节的内存分配了足够的内存,这是每个状态字符串的大小。很抱歉这个冗长的程序,但是当这么多的功能相互依赖时,很难把它缩短。

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

#define MAX_COORDINATE_SIZE 50
#define MAX_FILENAME_SIZE 20
#define MAX_GENERATIONS 10
#define MAX_REPETITION_PERIOD 4

struct coord{ //Holds coordinates to a cell
    int x;
    int y;
};

struct cell{
    int pop;    //Populated
    //    char pos; //C: corner, T: top, R: right, B: bottom, L: left
    int age;

};

struct coord *read_init(FILE *fp, int *i);

static int read_line(FILE *fp, char *line, int max_length);

struct coord read_coords(char *line);

struct cell **create_board(int x, int y);

void populate_board(struct coord *coords, struct cell ***board, int *n);

struct cell new_cell(int x, int y, int pop, int age);

struct cell **start_game(FILE *fp, int nrows, int ncols);

void print_board(struct cell **board, int nrows, int ncols);

void init_board(int nrows, int ncols, struct cell ***board);

int live(int i, int j, struct cell **board, int nrows, int ncols);

void step(struct cell ***board, int nrows, int ncols);

void push_list(char **list, int len, char *state);

int repetitive(char **list, int len);

char *serialize_board(struct cell **board, int nrows, int ncols);


int main(int argc, const char * argv[]) {
    int gens;
    char gens_string[MAX_GENERATIONS];
    if(argc != 3){
        fprintf(stderr, "Usage: %s <seed-file> <generations>\n<seed-file> can me up to %d characters long\n", argv[0], MAX_FILENAME_SIZE);
        exit(1);
    }

    FILE *fp = fopen(argv[1], "r");
    strncat(gens_string, argv[2], MAX_GENERATIONS);
    gens = atoi(gens_string);

    int nrows = 10;
    int ncols = 10;

    int repetitions;

    //allocate memory and set up pointers for statelist
    char **statelist;
    statelist = malloc((MAX_REPETITION_PERIOD+1) * sizeof(char *) + (MAX_REPETITION_PERIOD+1+1) * nrows * ncols); //MAXREP+1 is the number of strings we want to store. The other +1 is to fit in the null temrinator
    char *firstrow = statelist + MAX_REPETITION_PERIOD+1;
    for(int i = 0; i < MAX_REPETITION_PERIOD+1; i++){
        statelist[i] = firstrow + i * nrows * ncols;
    }

    struct cell **board= start_game(fp, nrows, ncols);
    print_board(board, nrows, ncols);

    push_list(statelist, MAX_REPETITION_PERIOD+1, serialize_board(board, nrows, ncols));

    struct timespec t;
    t.tv_sec = 0;
    t.tv_nsec = 500000000L;
    for(int i = 0; i < gens; i++){
        step(&board, nrows, ncols);
        nanosleep(&t, NULL);
        print_board(board, nrows, ncols);
        push_list(statelist, MAX_REPETITION_PERIOD+1, serialize_board(board, nrows, ncols));
        if((repetitions = repetitive(statelist, MAX_REPETITION_PERIOD+1))){
            printf("Repetition detected (%d)\n", repetitions);
            exit(1);
        }
    }

    return 0;
}

struct coord *read_init(FILE *fp, int *n){ //Takes in filename and returns list of coordinates to be populated
    char raw_n[100];
    struct coord *coords;
    char *line;
    line = malloc(sizeof(char)*MAX_COORDINATE_SIZE);

    read_line(fp, raw_n, 100); // get the first line of the file (number of popuated cells)

    *n = atoi(raw_n);//make an int out of raw_n

    coords = malloc(sizeof(struct coord)*(*n)); //Allocate memory for each coord

    for(int i = 0; i<(*n); i++){ // for each line in the file (each populated cell)
        read_line(fp, line, MAX_COORDINATE_SIZE);
        coords[i] = read_coords(line); //Put coordinates in coords
        line[0] = '\0';
    }

    return coords; // return coordinates
}

static int read_line ( FILE *fp, char *line, int max_length)
{
    int i;
    char ch;
    /* initialize index to string character */
    i = 0;
    /* read to end of line, filling in characters in string up to its
     maximum length, and ignoring the rest, if any */
    for(;;)
    {
        /* read next character */
        ch = fgetc(fp);
        /* check for end of file error */
        if ( ch == EOF )

            return -1;
        /* check for end of line */
        if ( ch == '\n' )
        {
            /* terminate string and return */
            line[i] = '\0';
            return 0;
        }
        /* fill character in string if it is not already full*/
        if ( i < max_length )
            line[i++] = ch;
    }
    /* the program should never reach here */
    return -1;
}

struct coord read_coords(char *line){ // Returns coordinates read from char *line
    struct coord c;
    char *x;
    char *y;
    x = malloc(sizeof(char)*MAX_COORDINATE_SIZE);
    y = malloc(sizeof(char)*MAX_COORDINATE_SIZE);

    int i = 0;
    do{
        x[i] = line[i]; //Get the x coordinate
        i++;
    }while(line[i] != ' ');
    i++;
    do{
        y[i-2] = line[i];
        i++;
    }while(line[i] != '\0');

    c.x = atoi(x)-1;
    c.y = atoi(y)-1;

    return c;
}

void init_board(int nrows, int ncols, struct cell ***board){

    *board = malloc(nrows * sizeof(*board) + nrows * ncols * sizeof(**board));

    //Now set the address of each row or whatever stackoverflow says
    struct cell * const firstrow = *board + nrows;
    for(int i = 0; i < nrows; i++)
    {
        (*board)[i] = firstrow + i * ncols;
    }

    for(int i = 0; i < nrows; i++){ //fill the entire board with pieces
        for(int j = 0; j < ncols; j++){
            (*board)[i][j] = new_cell(i, j, 0, 0);
        }
    }
}

struct cell new_cell(int x, int y, int pop, int age){ //Return new populated or non-populated cell with specified coordinates
    struct cell c;
    c.pop = pop;
    c.age = age;
    return c;
}

struct cell **start_game(FILE *fp, int nrows, int ncols){ //x,y are no of rows/columns, fn is filename
    int n; // n is the number of populated cells specified in the seed
    struct coord *coords = read_init(fp, &n); // get the list of coords to populate board with
    struct cell **board;

    init_board(nrows, ncols, &board); // Set up the board

    populate_board(coords, &board, &n); //populate the cells specified in the seed

    return board;
}

void populate_board(struct coord *coords, struct cell ***board, int *n){
    for(int i = 0; i < *n; i++){
        (*board)[coords[i].x][coords[i].y].pop = 1; //populate the cell
    }
}

void print_board(struct cell **board, int nrows, int ncols){
    printf("--------------------\n");
    for(int i = 0; i<nrows; i++){
        for(int j = 0; j<ncols; j++){
            if(board[i][j].pop == 1){
                printf("%d ", board[i][j].age);
            }else if(board[i][j].pop == 0){
                printf("  ");
            }else{
                printf("\n\nERROR!");
                exit(0);
            }
        }
        printf("\n");
    }
    printf("--------------------");
    printf("\n");
}

int live(int i, int j, struct cell **board, int nrows, int ncols){ //returns 1 if cell will live, 0 if cell will die,

    int status = board[i][j].pop; // status = 1 if alive, 0 if dead
    int n=0; //neighbours

    //Counting all the neighbours

    if(i == 0 && j == 0){    //if top left cornerpiece
        if(board[i][j+1].pop)  { n++; } // east
        if(board[i+1][j+1].pop){ n++; } // southeast
        if(board[i+1][j].pop)  { n++; } // south
    }
    else if(i == 0 && j==ncols-1){ //if top right cornerpiece
        if(board[i+1][j].pop)  { n++; } // south
        if(board[i+1][j-1].pop){ n++; } // southwest
        if(board[i][j-1].pop)  { n++; } // west
    }
    else if(i == nrows-1 && j == ncols-1){ //if bottom right cornerpiece
        if(board[i][j-1].pop)  { n++; } // west
        if(board[i-1][j-1].pop){ n++; } // northwest
        if(board[i-1][j].pop)  { n++; } // north
    }
    else if(i == nrows-1 && j == 0){ //if bottom left cornerpiece
        if(board[i-1][j].pop)  { n++; } // north
        if(board[i-1][j+1].pop){ n++; } // northeast
        if(board[i][j+1].pop)  { n++; } // east
    }
    else if(i == 0){ // middle top piece (not in a corner)
        if(board[i][j+1].pop)  { n++; } // east
        if(board[i+1][j+1].pop){ n++; } // southeast
        if(board[i+1][j].pop)  { n++; } // south
        if(board[i+1][j-1].pop){ n++; } // southwest
        if(board[i][j-1].pop)  { n++; } // west
    }
    else if(j == ncols-1){ // middle right side piece
        if(board[i+1][j].pop)  { n++; } // south
        if(board[i+1][j-1].pop){ n++; } // southwest
        if(board[i][j-1].pop)  { n++; } // west
        if(board[i-1][j-1].pop){ n++; } // northwest
        if(board[i-1][j].pop)  { n++; } // north
    }
    else if(i == nrows-1){ // middle bottom piece
        if(board[i][j-1].pop)  { n++; } // west
        if(board[i-1][j-1].pop){ n++; } // northwest
        if(board[i-1][j].pop)  { n++; } // north
        if(board[i-1][j+1].pop){ n++; } // northeast
        if(board[i][j+1].pop)  { n++; } // east
    }
    else{
        if(board[i-1][j].pop)  { n++; } // north
        if(board[i-1][j+1].pop){ n++; } // northeast
        if(board[i][j+1].pop)  { n++; } // east
        if(board[i+1][j+1].pop){ n++; } // southeast
        if(board[i+1][j].pop)  { n++; } // south
        if(board[i+1][j-1].pop){ n++; } // southwest
        if(board[i][j-1].pop)  { n++; } // west
        if(board[i-1][j-1].pop){ n++; } // northwest
    }

    if(status){
        if(n < 2)           { return 0; }
        if(n >= 2 && n <= 3){ return 1; }
        if(n > 3)           { return 0; }
    }
    if(!status){
        if(n == 3)          { return 1; }
        return 0;
    }
    //We should never reach here
    return -1;
}

void step(struct cell ***board, int nrows, int ncols){ // returns array of strings that contain all of the 5 latest states
    struct cell **nextgenboard;
    nextgenboard = malloc(nrows*(sizeof(nextgenboard)) + nrows * ncols * sizeof(**nextgenboard));
    struct cell * const firstrow = nextgenboard + nrows;
    for(int i = 0; i < nrows; i++){
        nextgenboard[i] = firstrow + i*ncols;
    }

    for(int i = 0; i < nrows; i++){
        for(int j = 0; j < ncols; j++){

        }
    }
    for(int r = 0; r < nrows; r++){

        for(int c = 0; c < ncols; c++){

            nextgenboard[r][c].pop = live(r, c, *board, nrows, ncols);
            if((*board)[r][c].pop == 0){
                nextgenboard[r][c].age = 0;
            }else if((*board)[r][c].pop == 1){
                nextgenboard[r][c].age = (*board)[r][c].age + 1;
            }

            //nextgenboard[r][c].age = 0;

        }

    }

    free(*board);

    *board = nextgenboard;

}

void push_list(char **list, int len, char *state){ //takes an array of strings, the length of it and a new string. Pushes the other strings down and puts the new string in the first place. The last one gets destroyed.

    //move all the other elements down.
    for(int i = len-1; i > 0; i--){
        strcpy(list[i], list[i-1]); <---- HERE IS THE ERROR
    }
    //put the new element first
    strcpy(list[0], state);
}

int repetitive(char **statelist, int len){ // takes the statelist and checks if the first element reoccurs. Returns 0 for no repetition, and the period otherwise.
    for(int i = 1; i < len; i++){
        if(strcmp(statelist[0], statelist[i]) == 0){
            return i;
        }
    }
    return 0;
}

char *serialize_board(struct cell **board, int nrows, int ncols){
    char *str;
    str = malloc(sizeof(char)*nrows*ncols);

    int z = 0;
    printf("\n");
    for(int i = 0; i < nrows; i++){
        for(int j = 0; j < ncols; j++){
            str[z] = board[i][j].pop+48;
            z++;
        }
    }
    printf("\n");
    return str;
}

EDIT1:添加了生成错误的注释。我的问题是:为什么会发生这种情况,我该怎么办?

EDIT2:

@Neil 在他的回答中说我的代码有效地做到了这一点:

char str[10];
strcpy(&str[1], &str[0]);

为了创建一个更好地模拟我的程序的示例(例如 str 是 char ** 而不是 char *),我写了这个:

int main(){
    char **str;
    str = malloc(sizeof(char *)*3 + sizeof(char)*3*10);
    char * firststring = str+3;

    for(int i = 0; i < 10; i++){
        str[i] = firststring + i*10;
    }

    strcpy(str[0], "123456789");
    strcpy(str[1], "000000000");

    printf("%s\n", str[0]);
    printf("%s\n", str[1]);

    strcpy(str[1], str[0]);

    printf("%s\n", str[0]);
    printf("%s\n", str[1]);
}

在这里,我使用strcpy 将一个数组元素(包含指向字符的指针)复制到另一个。这工作正常。那么为什么我的代码不起作用?我希望它在原则上和这个迷你示例一样做同样的事情。

EDIT3:

因此,一些更改使我的程序最终完成了我想要它做的事情。

  1. 修复main() 内存分配错误。
  2. '\0' 终止statelist 中的每个字符串。
  3. 将 statelist 初始化为 \0(尽管仅使用上述两个程序确实可以正常执行)

1.:

char **statelist;
statelist = malloc((MAX_REPETITION_PERIOD+1) * sizeof(char *) + (MAX_REPETITION_PERIOD+1) * (nrows * ncols + 1) * sizeof(char));
char *firstrow = statelist + MAX_REPETITION_PERIOD+1;
for(int i = 0; i < (MAX_REPETITION_PERIOD+1); i++){
    statelist[i] = firstrow + i * (nrows * ncols + 1);
    *statelist[i] = '\0';
}

2.:

char *serialize_board(struct cell **board, int nrows, int ncols){
    char *str;
    str = malloc(sizeof(char)*nrows*ncols);

    int z = 0;
    printf("\n");
    for(int i = 0; i < nrows; i++){
        for(int j = 0; j < ncols; j++){
            str[z] = board[i][j].pop+48;
            z++;
        }
    }
    str[z] = '\0'; <---- HERE
    return str;
}

3.:

见 1。

【问题讨论】:

  • 先去掉警告。
  • 寻求调试帮助的问题(“为什么这段代码不起作用?”)必须包括所需的行为、特定的问题或错误以及在问题本身中重现它所需的最短代码。没有明确问题陈述的问题对其他读者没有用处。请参阅:如何创建minimal reproducible example
  • 我们不是调试服务。这远非最小

标签: c strcpy


【解决方案1】:

首先,statelist 包含一堆指向未初始化内存的指针。当您尝试从这些指针之一strcpy 时,这会导致未定义的行为。

但是即使您将内存初始化为零,您所做的事情也基本上是不正确的。您使用 strcpy 函数和其他 str* 函数,但这些函数仅适用于以零结尾的字符串。 serialize_board 返回的内存,你试图 strcpy 只是一堆chars 不是零终止的。所以你又有未定义的行为。

您可以使用memcpy 函数来复制这些内存块,因为您知道要复制的内存大小。您还需要使用memcpy 而不是strcmp

这里还有一些严重的内存泄漏(例如,serialize_board 分配的内存没有被释放)。

【讨论】:

  • 1. Statelist 通过malloc()-call 在main() 中初始化,然后是for 循环,就像在我的edit2 示例中一样。 2. 你说得对,我没有用'\0'-终止符来终止字符串。我会这样做并继续努力让它发挥作用。
  • @Sandi malloc返回的内存是未初始化的,你有指针指向它。
  • 知道了。修复了您指出的问题,除了我仍在处理的内存泄漏。在 Edit3 中指定
【解决方案2】:

这里:

void push_list(char **list, int len, char *state){ //takes an array of strings, the length of it and a new string. Pushes the other strings down and puts the new string in the first place. The last one gets destroyed.

//move all the other elements down.
for(int i = len-1; i > 0; i--){
    strcpy(list[i], list[i-1]); // <-- HERE
}
//put the new element first
strcpy(list[0], state);

}

list 是指向 char 的指针。它没有内在长度(除了 char*)。编译器知道 list[i] 的长度超过 1 个字节。

实际上,您拥有的代码是:

char str[10];
strcpy(&str[1], &str[0]);

看看重叠是如何发生的?

好的,让我们试试这个。

1. char str[10] = "ABCDEFGHI";
2. strcpy(&str[1], &str[0]);

假设你有一个 9 个字符的“字符串”(+1 个 nul 终止符) - 第 1 行。 第 2 行将尝试将“BCDEFGHI”移动到“ABCDEFGHI”占用的空间中:

【讨论】:

  • 您好,感谢您的回答。也许我没有说清楚。我知道那是导致问题的原因。但是您的答案如何解释编译器检测重叠缓冲区的原因?解决方案是什么?
  • 假设您有一个长度为 10 个字符的字符串。您要求 strcpy 从位置 0 复制 10 个字符到位置 1(在同一字符串中)
  • 我没有传递 push_list 一个字符数组吗?如果是这样,为什么我不能简单地将一个字符数组复制到数组中的另一个位置?
  • 我没有在你的例子中看到重叠,没有。也许我错过了什么。
  • 请记住,字符不是“字符串”。 C没有“字符串”。 C 库有一个带有 nul 终止符的字符数组的概念是一个“字符串”,但这不是你在这里所拥有的。您是一大块内存,并且您将块的开头引用为 char *
猜你喜欢
  • 2015-07-07
  • 2010-12-08
  • 2014-04-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多