【问题标题】:Segmentation fault when reading space separated values from file in C从 C 中的文件中读取空间分隔值时出现分段错误
【发布时间】:2017-06-17 20:29:43
【问题描述】:

我有一个包含空格分隔值的输入文件

AA BB 4
A B
AB BA
AA CC
CC BB
A B 3 
A C
B C
c B

我想将第一行的内容分别移动到s1s2n 和接下来的 n 行内容 2 个空格分隔的字符串,这些将移动到 production_leftproduction_right 分别。 后续的行块也将重复布局。样本数据有两个输入块。

下面给出我的代码

int main()
{
    char *s1[20], *t1[20];
    char *production_left[20], *production_right[20];
    int n;
    FILE *fp;
    fp = fopen("suffix.txt","r");
    int iss=0;
    do{
         fscanf(fp,"%s %s %d",s1[iss],t1[iss],&n);
         int i=0;
         while(i<n)
         {
             fscanf(fp,"%s %s",production_left[i],production_right[i]);
             i++;
         }
    }while(!eof(fp));
}

每次都给出分段错误。

【问题讨论】:

  • 提示 char *s1[20]s1char* 的数组。并且指针在使用时应该实际指向对象。
  • eof 与文件句柄描述符一起使用,而不是与流一起使用。 feof 与流一起使用,但工作方式不同。控制循环的一种方法是while(fscanf(fp,"%s %s %d", s1[iss], t1[iss], &amp;n) == 3) {...},尽管还有其他方法,例如while(fgets(...) != NULL) { if sscanf(...) != 3) ... }。关键点始终是检查scanf函数族的返回值。
  • A B 3 A C 是一个错字,不是吗?
  • @WeatherVane Well OP 现在固定为 2-3 而不是 2-5。别担心。
  • 您已经传递了满足格式规范%s 的指针,它们未初始化,因此可能指向也可能不指向有效内存。可能不是,因此是段错误。在下一个fscanf 与其他指针数组同上。我想将其写为答案,但代码还有很多其他错误,例如任何尝试对 I/O 操作进行基本错误检查,或启用编译器警告 - 并采取行动。所以我将把这个和其他提示保留为 cmets。

标签: c file fopen scanf


【解决方案1】:

很多 C 语言只是在您的脑海中保持清晰,您需要在代码的什么范围(块)中提供哪些数据。在您的情况下,您正在使用的数据是数据文件每个部分的第一行(或标题)。为此,您有 s1t1,但您没有任何东西可以保留 n,以便它可以与您的数据一起重复使用。由于n 包含每个标题下production_leftproduction_right 的预期索引数,因此只需创建一个索引数组,例如int idx[MAXC] = {0}; 存储与每个 s1t1 关联的每个 n。这样您就可以保留该值以供以后在迭代中使用。 (MAXC 只是为20 定义的常量,以防止在您的代码中使用幻数

接下来,您需要转向您对指针声明及其使用的理解。 char *s1[MAXC], *t1[MAXC], *production_left[MAXC], *production_right[MAXC];s1t1production_leftproduction_right 声明了 4 个 指针数组(每个 20 指针)。指针未初始化和未分配。虽然每个都可以初始化为一个指针值,但没有与它们中的任何一个关联的存储(分配的内存)允许复制数据。您不能简单地使用 fscanf 并为每个分配相同的指针值(它们最终都会指向最后一个值 - 如果它仍在范围内)

所以你有两个选择,(1)要么使用二维数组,要么(2)为每个字符串分配存储空间并将字符串复制到新的内存块,并将指向该块开头的指针分配给(例如s1[x])。 strdup 函数在单个函数调用中提供分配和复制。如果您没有strdup,则使用strlenmallocmemcopy 编写一个简单的函数(如果字符串可能重叠,请使用memmove)。

一旦您确定了需要保留哪些值以供以后在代码中使用,您已确保声明的变量具有正确的范围,并且您已确保每个变量都已正确初始化和分配存储,剩下的就是编写逻辑让事情如你所愿。

在转向示例之前,值得注意的是,您对面向行的输入 与您的数据感兴趣。 scanf 系列提供格式化输入,但通常最好对实际输入使用面向行 函数(例如fgets),然后单独解析与,例如sscanf。在这种情况下,这主要是一种清洗,因为您的第一个值是 字符串值,而 %s 格式说明符将跳过干预 空格,但通常情况并非如此。例如,您正在有效地阅读:

    char tmp1[MAXC] = "", tmp2[MAXC] = "";
    ...
    if (fscanf (fp, "%s %s %d", tmp1, tmp2, &n) != 3)
        break;

这可以很容易地用更健壮的东西代替:

    char buf[MAXC] = "", tmp1[MAXC] = "", tmp2[MAXC] = "";
    ...
    if (!fgets (buf, sizeof buf, fp) ||
        sscanf (buf, "%s %s %d", tmp1, tmp2, &n) != 3)
        break;

(将分别验证读取和转换)

在上面,还要注意临时缓冲区tmp1tmp2 的使用(以及buffgets 一起使用)将输入读入临时值通常是有利的,这些临时值可以在最终存储之前进行验证以后再用。

剩下的就是按照正确的顺序将各个部分组合在一起以实现您的目标。下面是一个示例,它从作为第一个参数给出的文件名(如果没有给出文件名,则从stdin)读取数据,然后输出数据并在退出前释放所有分配的内存。

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

#define MAXC 20

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

    char *s1[MAXC], *t1[MAXC],
        *production_left[MAXC], *production_right[MAXC];
    int idx[MAXC] = {0},              /* storage for 'n' values */
        iss = 0, ipp = 0, pidx = 0;
    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;
    }

    /* loop until no header row read, protecting array bounds */
    for (; ipp < MAXC && iss < MAXC; iss++) {
        char buf[MAXC] = "", tmp1[MAXC] = "", tmp2[MAXC] = "";
        int n = 0;

        if (!fgets (buf, sizeof buf, fp) ||
            sscanf (buf, "%s %s %d", tmp1, tmp2, &n) != 3)
            break;

        idx[iss] = n;
        s1[iss] = strdup (tmp1);    /* strdup - allocate & copy */
        t1[iss] = strdup (tmp2);

        if (!s1[iss] || !t1[iss]) { /* if either is NULL, handle error */
            fprintf (stderr, "error: s1 or s1 empty/NULL, iss: %d\n", iss);
            return 1;
        }

        /* read 'n' data lines from file, protecting array bounds */
        for (int i = 0; i < n && ipp < MAXC; i++, ipp++) {
            char ptmp1[MAXC] = "", ptmp2[MAXC] = "";

            if (!fgets (buf, sizeof buf, fp) ||
                sscanf (buf, "%s %s", ptmp1, ptmp2) != 2) {
                fprintf (stderr, "error: read failure, ipp: %d\n", iss);
                return 1;
            }
            production_left[ipp] = strdup (ptmp1);
            production_right[ipp] = strdup (ptmp2);

            if (!production_left[ipp] || !production_right[ipp]) {
                fprintf (stderr, "error: production_left or "
                        "production_right empty/NULL, iss: %d\n", iss);
                return 1;
            }
        }
    }

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

    for (int i = 0; i < iss; i++) {
        printf ("%-8s %-8s  %2d\n", s1[i], t1[i], idx[i]);
        free (s1[i]);  /* free s & t allocations */
        free (t1[i]);
        for (int j = pidx; j < pidx + idx[i]; j++) {
            printf ("  %-8s %-8s\n", production_left[j], production_right[j]);
            free (production_left[j]);  /* free production allocations */
            free (production_right[j]);
        }
        pidx += idx[i];  /* increment previous index value */
    }

    return 0;
}

输入文件示例

$ cat dat/production.txt
AA BB 4
A B
AB BA
AA CC
CC BB
A B 3
A C
B C
c B

使用/输出示例

$ ./bin/production <dat/production.txt
AA       BB         4
  A        B
  AB       BA
  AA       CC
  CC       BB
A        B          3
  A        C
  B        C
  c        B

内存使用/错误检查

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

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

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

$ valgrind ./bin/production <dat/production.txt
==3946== Memcheck, a memory error detector
==3946== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==3946== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==3946== Command: ./bin/production
==3946==
AA       BB         4
  A        B
  AB       BA
  AA       CC
  CC       BB
A        B          3
  A        C
  B        C
  c        B
==3946==
==3946== HEAP SUMMARY:
==3946==     in use at exit: 0 bytes in 0 blocks
==3946==   total heap usage: 18 allocs, 18 frees, 44 bytes allocated
==3946==
==3946== All heap blocks were freed -- no leaks are possible
==3946==
==3946== For counts of detected and suppressed errors, rerun with: -v
==3946== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

查看一下,如果您还有其他问题,请告诉我。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-27
    • 1970-01-01
    • 1970-01-01
    • 2022-10-04
    • 2020-08-14
    • 2013-08-04
    • 1970-01-01
    相关资源
    最近更新 更多