繁体   English   中英

将字符串数组传递给 fscanf 的 function

[英]Passing an array of strings into a function for fscanf

当我运行这段代码时,我得到一个分段错误,我不知道为什么会发生这种情况,我试图从文本文件的每一行中读取一个名称和现金金额,并将其放入一个数组中。

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

void ReadFile(int *cash, char **name)
{
    int r, line = 0;

    FILE *fp;
    fp = fopen("coins.txt", "r");

    if (fp == NULL)
    {
        printf ("Error opening the file\n\n'");
        exit(EXIT_FAILURE);
    } else {
        while (line <= 14)
        {
            
            r = fscanf(fp, "%s %d\n", name[line], &cash[line]);
            if (r != 2)
            {
                printf ("Error, line %d in wrong format!\n\n", line);
            }
            //printf("Name: %s, Cash: $%d\n",name, *cash);
            line++;
        }
    fclose(fp);
    }
    
}

int main()
{
    int cash[14];
    char *name[14] = { "" };
    ReadFile(cash,name);
    //printf("%s\n", name[0]);
}

继续评论,直接错误是由于char *name[14]声明了一个包含 14 个未初始化char 指针的数组。 意味着每个指针无处指向(例如,指针作为其值指向的地址指向某个不确定的 memory 地址)。 在您可以存储任何东西之前,您必须确保您有一个有效的 memory 位置来保存您正在处理的任何值。 对于字符串,例如name[x] ,这意味着您需要可用的length + 1字符( +1nul 终止字符'\0'提供存储,相当于普通的0

您的ReadFile() function 严重不足,您的阅读很脆弱。 任何时候读取数据行时,都应该使用面向行的输入 function,例如fgets() (或 POSIX getline() )。 这样,您每次都将读取(使用)一行输入,并且任何导致匹配失败的输入文件格式差异只会影响该行,并且不会破坏从该点向前读取的文件。

切勿在代码中硬编码文件名或使用MagicNumbers 你不应该仅仅为了从不同的输入文件中读取而重新编译你的程序。 您可以提供一个默认使用的文件名,否则将要读取的文件名作为程序的参数(这就是int argc, char **argv的用途),或者提示用户并将文件名作为输入。 为了避免代码中的硬编码文件名和幻数:

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

#define COINS  14       /* if you need a constant, #define one (or more) */
#define MAXC 1024

int main (int argc, char **argv)
{
    ...
    FILE *fp = fopen (argc > 1 ? argv[1] : "coins.txt", "r");
    ...

此外,您通常希望在调用 function 中打开并验证文件是否已打开以便读取,并将FILE*指针传递给打开的 stream 作为 ZC1C425268E68385D14AB5074C17A 的参数。 如果文件打开失败,则无需开始调用 function。

在转换(和验证)转换之前,将所有部分放在一起并使用MAXC字符缓冲区来保存从文件中读取的每一行。 (在验证fscanf()的返回方面做得很好)。

要解决您的问题,您应该将name值读入临时数组,然后分配存储空间,只需获取临时数组中字符串的strlen()name[i]malloc (len + 1)字符(验证每个分配),然后从临时数组复制到name[i] (当您完成调用者中的值后,您需要释放 memory(此处为main()

您还需要lineindex单独的计数器。 由于您报告发生任何故障的行(再次做得好),您需要在每次迭代时增加line计数器,但您只想在成功转换时增加namecash的索引,例如

size_t ReadFile (FILE *fp, int *cash, char **name)
{
    char buf[MAXC];                     /* temporary array to hold line */
    size_t index = 0, line = 0;         /* separate counters for index and line */
    
    while (line < COINS && fgets (buf, MAXC, fp))   /* protect array, read line */
    {
        char coinname[MAXC];            /* temporary array for name */
        int r, value;                   /* declare variables in scope required */
        size_t len;                     /* length of name */
        
        r = sscanf (buf, "%1023s %d", coinname, &value);    /* convert values */
        if (r != 2) {   /* validate conversion */
            fprintf (stderr, "Error, line %zu in wrong format!\n\n", line);
            line++;     /* don't forget to increment line */
            continue;   /* before continuing to read next line */
        }
        
        len = strlen (coinname);                    /* get length of name */
        name[index] = malloc (len + 1);             /* allocate len + 1 chars */
        if (name[index] == NULL) {                  /* validate allocation */
            perror ("malloc-name[index]");
            return index;
        }
        memcpy (name[index], coinname, len + 1);    /* copy to name[index] */
        cash[index] = value;                        /* assign to cash[index] */
        
        index++;        /* increment index */
        line++;         /* increment line */
    }
    
    return index;       /* return index */
}

注意:返回类型从void更改为size_t以启用从文件中读取的名称和值的数量的返回。始终为任何可以成功或失败的 function 提供有意义的返回类型,例如除了简单打印之外的任何内容或免费功能)

您还有另一种存储name的选项。 除了使用malloc() (或calloc()realloc() )分配存储空间外,您还可以将name声明为 2D 字符数组,每行的存储空间足以容纳最长的名称 (+1) 字符。 这取决于名称之间的长度变化,可能需要比精确分配来保存每个name更多的存储空间。 通过消除分配的需要,它确实简化了一些事情。 由你决定。

现在您可以将 function 的返回值分配给main()中的变量,这样您就可以知道成功读取了多少namecash对,例如

int main (int argc, char **argv)
{
    int cash[COINS];
    char *name[COINS] = {NULL};
    size_t ncoins = 0;
    /* use filename provided as 1st argument (coins.txt by default) */
    FILE *fp = fopen (argc > 1 ? argv[1] : "coins.txt", "r");
    
    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }
    
    ncoins = ReadFile (fp, cash, name);
    fclose (fp);
    
    for (size_t i = 0; i < ncoins; i++) {
        printf ("%-12s %3d\n", name[i], cash[i]);
        free (name[i]);                             /* free mem when done */
    }
}

示例输入文件

$ cat dat/name_coins.txt
penny 20
nickle 3
dime 8
quarter 15
half-dollar 5
dollar 4

示例使用/输出

$ ./bin/name_coins dat/name_coins.txt
penny         20
nickle         3
dime           8
quarter       15
half-dollar    5
dollar         4

Memory 使用/错误检查

在您编写的任何动态分配 memory 的代码中,对于分配的 memory 的任何块,您有两个责任:(1)始终保留指向 ZCD69B4957F06CD818D7BF3D21980 块的起始地址的指针,所以它可以被释放,更需要。

您必须使用 memory 错误检查程序,以确保您不会尝试访问 memory 或写入超出/超出分配块的边界,尝试读取或基于未初始化值的条件跳转,最后确认释放所有已分配的 memory。

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

$ valgrind ./bin/name_coins dat/name_coins.txt
==5870== Memcheck, a memory error detector
==5870== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5870== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==5870== Command: ./bin/name_coins dat/name_coins.txt
==5870==
penny         20
nickle         3
dime           8
quarter       15
half-dollar    5
dollar         4
==5870==
==5870== HEAP SUMMARY:
==5870==     in use at exit: 0 bytes in 0 blocks
==5870==   total heap usage: 9 allocs, 9 frees, 5,717 bytes allocated
==5870==
==5870== All heap blocks were freed -- no leaks are possible
==5870==
==5870== For counts of detected and suppressed errors, rerun with: -v
==5870== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

如果您还有其他问题,请仔细查看并告诉我。

gdb 非常适合发现此类问题。

segfault$ gcc -ggdb segfault.c -o segfault
segfault$ gdb segfault 
GNU gdb (Ubuntu 8.1.1-0ubuntu1) 8.1.1
[...]
Reading symbols from segfault...done.
(gdb) break segfault.c:20
Breakpoint 1 at 0x896: file segfault.c, line 20.
(gdb) run
Starting program: /tmp/segfault/segfault 

Breakpoint 1, ReadFile (cash=0x7fffffffdd80, name=0x7fffffffddc0) at segfault.c:20
20              r = fscanf(fp, "%s %d\n", name[line], &cash[line]);
(gdb) print line
$1 = 0
(gdb) print &cash[line]
$2 = (int *) 0x7fffffffdd80
(gdb) print name[line]
$3 = 0x0

使用main()中的char *name[14] ,您正在创建一个 char 指针数组,但您没有设置指针。 因此,它们可能指向任何地方,最有可能指向无效位置(当您尝试读取/写入该位置时会导致段错误)。

在我的系统上,我们可以看到我们很幸运能够将未初始化的name[0]指向0x0 ( NULL )。 这不能保证,我们可能会遇到比这里的段错误更有趣的错误。 例如,想象一下,如果name[0]改为指向 memory 中存储line的位置。 你会覆盖 memory,并从你的循环中得到非常奇怪的行为。

您必须初始化name数组中的每个指针(在main()中),以便它们都指向足够大小的有效区域。

此外,您通常需要在调用 function 内部打开并验证报告是否已打开以供研究,并绕过 FILE* 指向打开循环的指针作为 function 页面的争议。 如果文档打开失败,则无需首先设置特征名称。

暂无
暂无

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

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