分段错误 - 为什么我收到此错误?

时间:2017-07-24 04:19:11

标签: c segmentation-fault

代码似乎做了它应该做的所有事情,但最后它给出了一个分段错误。我是C的新手,所以我不确定这里发生了什么。

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

const char *fileName = "data.txt";

int main(int argc, char** argv) {
    FILE *file = fopen(fileName, "r"); 
    int i, j;
    int temp;

    fscanf(file, "%d", &temp);
    const int tickNum = temp;
    fscanf(file, "%d", &temp);
    const int pNum = temp;

    struct data {
    int process, tau, tick;
    float alpha;
    int ticks[tickNum];

    };

    struct data *p[pNum];

    for(i = 0; i < pNum; i++) {
        fscanf(file, "%d %d %f", &p[i]->process, &p[i]->tau, &p[i]->alpha);
        for(j = 0; j < tickNum; j++) {
            fscanf(file, "%d", &p[i]->ticks[j]);
        }
    }
    fclose(file);

    return (EXIT_SUCCESS);
}

4 个答案:

答案 0 :(得分:4)

从哪里开始?您正在处理大量问题。对于新的C程序员来说,有些并不是那么微不足道。我将解决新程序员应特别注意的问题,然后将它们合并到一个示例中,说明如何重构代码以解决问题。

让我们首先宣读data。您遇到的问题是您事先并不知道ticknum的价值。正如所有人在评论和其他答案中都注意到的那样,你不能在结构声明中使用ticks中元素数量的非常量声明。不允许使用可变长度数组(VLA)。问题是编译器不知道sizeof (struct data);是否有一个可变长度的对象被添加到最后,这使得无法在struct data

的数组上进行指针算法或数组索引

从C99开始,C确实提供灵活阵列成员(FAM),允许最后一个成员的单个声明为int ticks[] - 如果包含struct data,则无法在另一个结构或联合中创建struct data或包含FAM的数组。还有零长度数组 - struct hack ,其中ticks被声明为int ticks[0];,它基本上用作VLA的标题,但也有类似的固有问题。

那么如何使用ticksticknum处理这种情况?你有两个选择。如果您知道ticknum不能超过最大值,则可以为最大值声明一个常量(例如#define TICKNUM 32),然后将ticks声明为静态声明的数组int ticks[TICKNUM];但是,这对于p小于TICKNUM个刻度的所有元素都是浪费的。如果您的数组struct data中有大量元素,它也会耗尽您的堆栈空间。

第二个选项是将ticks声明为指向int 的指针(例如int *ticks;),然后在每个{ticks内分别动态分配pnump struct data数组{1}}中的元素。在这里,您可以精确调整文件中ticknum读取的内存使用大小,并且由于您动态分配,因此内存是从堆中分配的,并且仅受您可用内存的限制(由您的操作系统的内存管理器处理)。这是解决问题的正确方法,唯一的缺点是您需要为每个ticks数组分配责任,然后在完成每个数组时释放它们。

接下来,虽然 style 取决于您,但C传统上避免使用camelCaseMixedCase变量名来支持所有小写同时保留大写名称以用于宏和常量。 (如果您想知道我为什么要更改您的pNumtickNum名称...)

验证,验证,验证 所有输入(尤其是用户输入),所有文件写入,所有内存分配,以及文件写入后的所有文件关闭。如果您阅读它,请验证您使用的任何功能,认为它读取您希望它读取的内容。所有功能都提供返回。使用它们可以最低限度地验证所有输入和转换以及内存分配。

除非您有理由在data内声明main()(这很好,但是......),一般您希望您的数据类型(例如struct data {....};声明为文件范围所以任何您编写的函数等具有可供使用的类型。标准C还规定所有声明都在您开始执行语句之前发生。实际上,标准要求在每个函数开头声明的所有变量(main()都是函数这并不总是可行的(或实际的),但最大限度地坚持它。

始终使用编译器警告启用编译,至少-Wall -Wextra(或编译器的等效项)和接受代码,直到它编译时没有警告。 (您可以通过阅读,理解和解决编译器告诉您的所有问题,从任何教程中学到很多C)如果您启用并解决了警告,那么您可能会发现tick data的元素在整个代码中未使用。

将所有这些部分组合在一起,您可以重新安排代码以使其工作类似于以下内容。 (我没有示例输入,所以我不得不读一下茶叶)

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

struct data {
    int process, tau /*, tick */;  /* tick unused in your code */
    float alpha;
    int *ticks;
};

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

    const char *filename = argc > 1 ? argv[1] : "data.txt";
    int ticknum = 0, pnum = 0;
    struct data *p = NULL;
    FILE *file = fopen(filename, "r"); 

    if (!file) {    /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", filename);
        return 1;
    }

    /* validate ALL input */
    if (fscanf (file, "%d", &ticknum) != 1) {
        fprintf (stderr, "error: read failure - ticknum.\n");
        return 1;
    }
    if (fscanf (file, "%d", &pnum) != 1) {
        fprintf (stderr, "error: read failure - pnum.\n");
        return 1;
    }

    /* allocate and validate ALL memory allocations */
    if (!(p = malloc (sizeof *p * pnum))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return 1;
    }

    for (int i = 0; i < pnum; i++) {
        if (fscanf (file, "%d %d %f", /* validate process, tau, alpha */
                    &p[i].process, &p[i].tau, &p[i].alpha) != 3) {
            fprintf (stderr, "error: read failure process[%d].\n", i);
            return 1;
        }
        /* allocate/validate p[i].ticks */
        if (!(p[i].ticks = malloc (sizeof *p->ticks * ticknum))) {
            fprintf (stderr, "error: memory exhausted p[%d].ticks.\n", i);
            return 1;
        }
        for (int j = 0; j < ticknum; j++) {     /* validate ticks[j] */
            if (fscanf (file, "%d", &p[i].ticks[j]) != 1) {
                fprintf (stderr, "error: read failure process[%d].ticks[%d].\n", 
                        i, j);
                return 1;
            }
        }
    }
    fclose (file);

    for (int i = 0; i < pnum; i++) {    /* output data */
        printf ("%2d  %8d    %8d    %.3f\n", 
                i, p[i].process, p[i].tau, p[i].alpha);
        for (int j = 0; j < ticknum; j++)
            printf ("  ticks[%2d] : %d\n", j, p[i].ticks[j]);
        free (p[i].ticks);  /* free p[i].ticks memory */
    }

    free (p);   /* free allocated memory for p */

    return 0;
}

示例输入文件

$ cat dat/ticks.dat
6 3
8152 1123 123.456
 1 3 5 7 9 11
8153 2123 124.567
 2 4 6 8 10 12
8154 3123 125.678
 1 2 3 4 5 6

示例使用/输出

$ ./bin/ticks dat/ticks.dat
 0      8152        1123    123.456
  ticks[ 0] : 1
  ticks[ 1] : 3
  ticks[ 2] : 5
  ticks[ 3] : 7
  ticks[ 4] : 9
  ticks[ 5] : 11
 1      8153        2123    124.567
  ticks[ 0] : 2
  ticks[ 1] : 4
  ticks[ 2] : 6
  ticks[ 3] : 8
  ticks[ 4] : 10
  ticks[ 5] : 12
 2      8154        3123    125.678
  ticks[ 0] : 1
  ticks[ 1] : 2
  ticks[ 2] : 3
  ticks[ 3] : 4
  ticks[ 4] : 5
  ticks[ 5] : 6

内存使用/错误检查

您必须使用内存错误检查程序,以确保您不会尝试在已分配的内存块的范围之外/之外进行写入,尝试读取或基于未初始化值的条件跳转,最后,确认您释放了已分配的所有内存。

对于Linux valgrind是正常的选择。每个平台都有类似的记忆检查器。它们都很简单易用,只需通过它运行程序即可。

$ valgrind ./bin/ticks dat/ticks.dat
==6270== Memcheck, a memory error detector
==6270== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==6270== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6270== Command: ./bin/ticks dat/ticks.dat
==6270==
 0      8152        1123    123.456
  ticks[ 0] : 1
  ticks[ 1] : 3
  ticks[ 2] : 5
  ticks[ 3] : 7
  ticks[ 4] : 9
  ticks[ 5] : 11
 1      8153        2123    124.567
  ticks[ 0] : 2
  ticks[ 1] : 4
  ticks[ 2] : 6
  ticks[ 3] : 8
  ticks[ 4] : 10
  ticks[ 5] : 12
 2      8154        3123    125.678
  ticks[ 0] : 1
  ticks[ 1] : 2
  ticks[ 2] : 3
  ticks[ 3] : 4
  ticks[ 4] : 5
  ticks[ 5] : 6
==6270==
==6270== HEAP SUMMARY:
==6270==     in use at exit: 0 bytes in 0 blocks
==6270==   total heap usage: 5 allocs, 5 frees, 696 bytes allocated
==6270==
==6270== All heap blocks were freed -- no leaks are possible
==6270==
==6270== For counts of detected and suppressed errors, rerun with: -v

始终确认已释放已分配的所有内存并且没有内存错误。

仔细看看,如果您有任何其他问题,请告诉我。祝你的编码好运。

答案 1 :(得分:1)

*p[pNum]只是一个指向struct data现有实例的单独指针的字段,就像你编写它一样。它实际上并没有指向有效的内存区域,因此访问它会导致内存访问冲突。

取消多余的*代替堆栈分配,或者使用malloc在堆上分配实例。在第一种情况下,您还需要修改对scanf的调用语法,并习惯在同一术语中使用&->时使用大括号,以避免让你自己解决它的解决顺序。

您实际上也可能打算编写(*p)[pNum],而这将是指向结构字段的单个指针。

事实上,你也应该提高你的编译器的警告级别,因为它应该已经暴力抱怨了。将-Wall -Werror -Wextra -pedantic添加到编译器命令行是一个良好的开端。

答案 2 :(得分:1)

在这一行:

struct data *p[pNum];

您声明一个包含指向初始化结构的指针的数组, 但最初没有初始化的结构,指针指向“无处”,你应该手动创建结构和指针,就像这样:

for (i = 0; i < pNum, i++) {
    p[i] = malloc(sizeof(struct data));
}

答案 3 :(得分:-1)

正如其他人所建议的那样,指针正在查看无效的内存区域,为struct变量分配内存以摆脱Segmentation Fault,我建议如下所示。

struct data **p=malloc(pNum*sizeof *p);
for (i=0;i<pNum;i++){
    p[i]=malloc(sizeof p);
}