繁体   English   中英

细分错误-为什么会出现此错误?

[英]Segmentation Fault - Why am I getting this error?

该代码似乎可以完成它应该做的所有事情,但是最后却给出了分段错误。 我是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);
}

从哪儿开始? 您正在处理大量问题。 对于新的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数组中包含大量元素,它也会耗尽堆栈空间。

pnum是将ticks声明为int指针 (例如int *ticks; ),然后在struct data p数组中的每个pnum元素内分别动态分配ticks 在这里,您可以受益于能够精确ticknum从文件读取的ticknum内存使用量,并且由于您是动态分配的,因此内存是从堆中分配的,并且仅受可用内存的限制(由操作系统处理)内存管理器)。 这是解决问题的正确方法,唯一的缺点是,您有责任为每个ticks数组分配ticks ,然后在完成它们后释放它们。

接下来,在样式由您决定的情况下,C通常会避免使用camelCaseMixedCase变量名,而使用所有小写字母,同时保留大写字母名称以供宏和常量使用。 (如果您想知道为什么我要更改您的pNumtickNum名称...)

验证,验证,验证所有输入 (尤其是用户输入 ),所有文件写入 ,所有内存分配以及所有文件写入之后的关闭文件。 如果您阅读了它,请验证您使用过的认为它已阅读预期的功能。 所有函数都提供返回 使用它们来最小化验证所有输入和转换以及内存分配。

除非您有理由在main()声明data (这很好,但是...),否则通常您需要数据类型(例如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

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

仔细检查一下,如果您还有其他问题,请与我联系。 祝您编码顺利。

*p[pNum]只是一个单独指针的字段,该指针指向尚未存在的struct data实例(如您编写struct data那样)。 它实际上尚未指向有效的内存区域,因此访问它会导致内存访问冲突。

摆脱多余的*可以改为堆栈分配,或者使用malloc在堆上分配实例。 在第一种情况下,您还需要修改对scanf的调用语法,并在同一术语中使用&->时习惯使用花括号,以避免混淆解析的顺序。

您实际上可能还打算写(*p)[pNum] ,它原来是指向结构字段的单个指针。

实际上,您还应该提高编译器的警告级别,因为它应该已经发出了强烈的抱怨。 -Wall -Werror -Wextra -pedantic添加到编译器命令行是一个不错的开始。

在这一行:

struct data *p[pNum];

您声明了一个数组,其中将包含指向初始化结构的指针,但是最初没有初始化的结构,并且指针指向“ nowhere”,因此您应该手动创建结构并对其进行指针,如下所示:

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM