分配数组 C 时检测到堆栈粉碎

Stack smashing detected when allocating array C

如题说明问题。在我程序的这个函数中,每当我在数组中分配一定大小时,我的终端上都会出现大量错误代码。该数组被正确复制,除了在打印它之后程序崩溃之外的所有内容。该程序的目的是从文件中读取,然后将每一行存储在数组索引中。非常感谢您的帮助。谢谢

数组在main中声明为指针,然后在里面动态分配。

    void read_file_into_array(char **filearray, FILE * fp)
        {
            /* use get line to count # of lines */
            int numlines = -1;
            numlines = linecount(fp);
            size_t size = 50;
            char *buffer;

            buffer = (char *)malloc(size * sizeof(char));
            if (buffer == NULL) {
                perror("Unable to allocate buffer");
                exit(1);
            }

            if (numlines < 0) {
                fprintf(stderr, "error: unable to determine file length.\n");
                return;
            }

            printf(" number of lines counted are ; %d\n", numlines);

            /* allocate array of size numlines + 1 */

            if (!(*filearray = (char *)malloc(numlines + 1 * sizeof(char)))) {
                fprintf(stderr, "error: virtual memory exhausted.\n");
                return;
            }

            fseek(fp, 0, SEEK_SET);
            for (int i = 0; i < numlines; i++) {
                if (!feof(fp)) {
                    fgets(buffer, size, fp);
                    filearray[i] = (char *)malloc(size * sizeof(char *));
                    strcpy(filearray[i], buffer);
                    printf("buffer at %d : %s\n", i, buffer);
                    printf("array at %d : %s\n", i, filearray[i]);
                }
            }
            free(buffer);
        }



        /* THIS IS MY MAIN BELOW */

        int main (int argc, char **argv)

        {
        FILE *fp = NULL;
        char*  array;

        /* open file for reading (default stdin) */
            fp = argc > 1 ? fopen (argv[1], "r") : stdin;

            if (!fp) {  /* validate file open */
                fprintf (stderr, "error: file open failed '%s'\n", argv[1]);
                return 1;
            }

            read_file_into_array (&array, fp);

        if (fp != stdout) 
                if (fclose (fp) == EOF) {
                    fprintf (stderr, "error: fclose() returned EOF\n");
                    return 1;
                }


        return 0;
        }


    /* MY LNE COUNT FUNCITON */
    int linecount(FILE *fp) {
    char buff[MAXLINE];
    int count = 0;

    while(fgets(buff,MAXLINE,fp) != NULL) {
    count++;
    }
    return count;
}

您收到编译警告(+注意指出代码中其他不同错误的注释)的主要问题之一是

  1. if (!(*filearray = (char**)malloc(sizeof(char*) * (numlines + 1))))

*filearraychar* 类型而不是 char**; + 不需要转换 malloc.

返回的值
  1. char **filearray 实际上是 &arraymain() 定义为 char *array;

现在以下完全破坏稳定性并导致未定义的行为

filearray[i] = (char *)malloc(size * sizeof(char *));

这相当于 (&array)[i] = (char *)malloc(size * sizeof(char *)); 的主要内容,这 完全 损坏了。您正在遍历堆栈,从 mainarray 的位置开始,并用 malloc.[= 返回的 char* 的值覆盖所有内容。 29=]

如果您希望 array 是字符串数组,将其定义为 char **array,通过指针 &array 传递它,更新 filearray 的声明和用法因此。

好的,让我们为您整理一下。首先,要将 pointer to pointer to char 作为参数传递给函数,在函数内分配并将结果返回给调用函数(没有更多),您必须传递 [=58 的地址=] 指向 char 的指针。否则,该函数会收到一个副本,并且在函数中所做的任何更改在调用者(此处为main)中都不可用,因为当函数时副本丢失(分配块开头的地址丢失) returns.

将指针的地址传递给char指针意味着你必须成为3-STAR Programmer(这不是恭维)。更好的方法是根本不传递指针,只需在函数内分配 return 一个指向调用者的指针(但在某些情况下您仍然需要将其作为参数传递,如下所示)

下面的程序将读取作为第一个参数传递的任何文本文件(或者如果没有给出参数,它将从 stdin 读取)。它将动态分配指针和需要的(在 MAXL 块中而不是一次一个,这是非常低效的)。它将根据需要分配(和重新分配)行以容纳任何长度的行。它使用 MAXC 个字符的固定缓冲区连续读取,直到读取完整行,将 offset 保存到当前行中的位置以根据需要追加后续读取。

最后读取文件后,它会将文本和关联的行号打印到stdout,并在退出前释放所有分配的内存。查看它,如果您有任何问题,请告诉我:

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

enum { MAXL = 32, MAXC = 1024 };

char **read_file_into_buf (char ***buf, FILE *fp, int *nlines);

int main (int argc, char **argv) {

    char **buf = NULL;  /* buffer to hold lines of file */
    int n;              /* number of lines read */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }
    
    if (read_file_into_buf (&buf, fp, &n)) {        /* read file into buf  */
        for (int i = 0; i < n; i++) {               /* loop over all lines */
            printf ("line[%3d]: %s\n", i, buf[i]);  /* output line */
            free (buf[i]);      /* free line */
        }
        free (buf);             /* free buffer */
    }

    if (fp != stdin) fclose (fp);     /* close file if not stdin */

    return 0;
}

/** read text file from 'fp' into 'buf' update 'nlines'.
 *  Being a 3-STAR Programmer, is NOT a compliment. However,
 *  to pass a pointer to pointer to char as a parameter and 
 *  allocate within the function, it is required. You must
 *  pass the address of buf from main otherwise the function
 *  recieves a copy whose allocation is lost on return. A better
 *  approach is to simply assign the return in 'main' and not 
 *  pass buf at all.
 */
char **read_file_into_buf (char ***buf, FILE *fp, int *nlines)
{
    /* current allocation, current index, and offset (if less 
     * than a whole line is read into line on fgets call), and
     * eol indicates '\n' present.
     */
    size_t n = MAXL, idx = 0, offset = 0, eol = 0;
    char line[MAXC] = "";   /* temp buffer for MAXC chars */
    void *tmp = NULL;       /* pointer for realloc */
    
    /* validate address, file ptr & nlines address */
    if (!buf || !fp || !nlines) {
        fprintf (stderr, "error; invalid parameter.\n");
        return NULL;
    }
    
    /* allocate initial MAXL pointers, calloc used to avoid valgrind
     * warning about basing a conditional jump on uninitialized value.
     * calloc allocates and initializes.
     */
    if (!(*buf = calloc (sizeof *buf, MAXL))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return NULL;
    }
    
    while (fgets (line, MAXC, fp))  /* read every line in file */
    {
        /* save offset from prior read (if any), get len */
        size_t end = offset, len = strlen (line);
    
        if (len && line[len - 1] == '\n') { /* test for new line */
            line[--len] = 0;                /* overwrite with nul */
            offset = 0;                     /* zero offset, all read */
            eol = 1;                        /* POSIX eol present */
        }
        else {
            line[len] = 0;  /* nul-terminate */
            offset += len;  /* short read, save offset to last char */
            eol = 0;        /* no POSIX eol */
        }
        
        /* allocate/reallocate for current line + nul-byte */
        tmp = realloc ((*buf)[idx], sizeof ***buf * (end + len + 1));
        if (!tmp) {
            fprintf (stderr, "error: realloc, memory exhausted.\n");
            return *buf;  /* return current buf */
        }
        (*buf)[idx] = tmp;  /* assign block to current index */
        strcpy ((*buf)[idx] + end, line);  /* copy line to block */
        
        if (!eol) continue;   /* chars remain in line, go read them */
        
        if (++idx == n) {   /* check pointer allocation, realloc as needed */
            tmp = realloc (*buf, sizeof **buf * (n + MAXL));
            if (!tmp) {
                fprintf (stderr, "error: realloc buf, memory exhausted.\n");
                return *buf;
            }
            *buf = tmp; /* assign new block to *buf */
            memset (*buf + n, 0, sizeof **buf * MAXL);  /* zero new memory */
            n += MAXL;  /* update the current number of ptrs allocated */
        }
        *nlines = idx;  /* update the number of lines read */
    }
    if (!eol) {         /* protect against file with no POSIX ending '\n' */
        idx++;          /* account for final line */
        *nlines = idx;  /* update nlines */
    }
    
    /* final realloc to size buf to exactly fit number of lines */
    tmp = realloc (*buf, sizeof **buf * (idx));
    if (!tmp)   /* if it fails, return current buf */
        return *buf;
    
    *buf = tmp; /* assign reallocated block to buf */
    
    return *buf;
}

注意: 将分配给 memset (*buf + n, 0, sizeof **buf * MAXL); 的新指针置零是可选的,但它消除了 valgrind 关于基于未初始化值的条件跳转的警告,就像初始指针的 calloc/malloc 一样。 realloc 没有归零选项,因此需要单独调用 memset

示例Use/Output

$ ./bin/fgets_readfile < dat/damages.txt
line[  0]: Personal injury damage awards are unliquidated
line[  1]: and are not capable of certain measurement; thus, the
line[  2]: jury has broad discretion in assessing the amount of
line[  3]: damages in a personal injury case. Yet, at the same
line[  4]: time, a factual sufficiency review insures that the
line[  5]: evidence supports the jury's award; and, although
line[  6]: difficult, the law requires appellate courts to conduct
line[  7]: factual sufficiency reviews on damage awards in
line[  8]: personal injury cases. Thus, while a jury has latitude in
line[  9]: assessing intangible damages in personal injury cases,
line[ 10]: a jury's damage award does not escape the scrutiny of
line[ 11]: appellate review.
line[ 12]:
line[ 13]: Because Texas law applies no physical manifestation
line[ 14]: rule to restrict wrongful death recoveries, a
line[ 15]: trial court in a death case is prudent when it chooses
line[ 16]: to submit the issues of mental anguish and loss of
line[ 17]: society and companionship. While there is a
line[ 18]: presumption of mental anguish for the wrongful death
line[ 19]: beneficiary, the Texas Supreme Court has not indicated
line[ 20]: that reviewing courts should presume that the mental
line[ 21]: anguish is sufficient to support a large award. Testimony
line[ 22]: that proves the beneficiary suffered severe mental
line[ 23]: anguish or severe grief should be a significant and
line[ 24]: sometimes determining factor in a factual sufficiency
line[ 25]: analysis of large non-pecuniary damage awards.

在您编写的任何动态分配内存的代码中,您对分配的任何内存块负有 2 责任:(1) 始终保留指向内存块的起始地址 因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您没有写入 beyond/outside 您分配的内存块,试图读取或基于未初始化的值进行跳转,并最终确认您已释放所有已分配的内存。

对于Linux valgrind是正常的选择。

$ valgrind ./bin/fgets_readfile < dat/damages.txt
==5863== Memcheck, a memory error detector
==5863== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==5863== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==5863== Command: ./bin/fgets_readfile
==5863==
line[  0]: Personal injury damage awards are unliquidated
line[  1]: and are not capable of certain measurement; thus, the
line[  2]: jury has broad discretion in assessing the amount of
line[  3]: damages in a personal injury case. Yet, at the same
line[  4]: time, a factual sufficiency review insures that the
line[  5]: evidence supports the jury's award; and, although
line[  6]: difficult, the law requires appellate courts to conduct
line[  7]: factual sufficiency reviews on damage awards in
line[  8]: personal injury cases. Thus, while a jury has latitude in
line[  9]: assessing intangible damages in personal injury cases,
line[ 10]: a jury's damage award does not escape the scrutiny of
line[ 11]: appellate review.
line[ 12]:
line[ 13]: Because Texas law applies no physical manifestation
line[ 14]: rule to restrict wrongful death recoveries, a
line[ 15]: trial court in a death case is prudent when it chooses
line[ 16]: to submit the issues of mental anguish and loss of
line[ 17]: society and companionship. While there is a
line[ 18]: presumption of mental anguish for the wrongful death
line[ 19]: beneficiary, the Texas Supreme Court has not indicated
line[ 20]: that reviewing courts should presume that the mental
line[ 21]: anguish is sufficient to support a large award. Testimony
line[ 22]: that proves the beneficiary suffered severe mental
line[ 23]: anguish or severe grief should be a significant and
line[ 24]: sometimes determining factor in a factual sufficiency
line[ 25]: analysis of large non-pecuniary damage awards.
==5863==
==5863== HEAP SUMMARY:
==5863==     in use at exit: 0 bytes in 0 blocks
==5863==   total heap usage: 28 allocs, 28 frees, 1,733 bytes allocated
==5863==
==5863== All heap blocks were freed -- no leaks are possible
==5863==
==5863== For counts of detected and suppressed errors, rerun with: -v
==5863== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认所有堆块都已释放——不可能有泄漏,同样重要的是错误摘要:0 个上下文中的 0 个错误。 (尽管注意:一些 OS 没有提供足够的内存排除文件(排除系统和 OS 内存被报告为正在使用的文件 )将导致 valgrind 报告一些内存尚未被释放(尽管事实上你已经完成了你的工作并释放了你分配并在你控制下的所有块。)