【问题标题】:Segmentation fault in C while trying to declare an array of strings.尝试声明字符串数组时出现 C 中的分段错误。
【发布时间】:2016-12-05 18:52:11
【问题描述】:

“fruits.txt”是一个以数字开头的文本文件,例如 n,后跟 n 个水果的名称。我想将这 n 个名称存储到一个字符串数组中,但是在尝试声明该数组时,我收到“分段错误(核心转储)错误。

#include<stdio.h>
#include<stdlib.h>
int main()
{
    FILE *fp;
    int count;
    fp = fopen("fruits.txt", "r");
    if(fp == NULL)
    {
        printf("Can't open file!!");
        exit(0);
    }

    fscanf(fp, "%d", &count);
    printf("%d\n", count);
    char *fruits[count]; // This line is giving Segmentation fault.
    fclose(fp);

    return 0;
}  

【问题讨论】:

  • 如果你在不支持可变长度数组的 MSVC 上,那么char *fruits[count]; 会给你一个错误。
  • count 太大了?
  • 你能把fruits.txt的具体内容贴出来吗?另外,你应该检查fscanf 的返回值;如果失败,count 可能会留下垃圾。
  • @Romy 你没有fscanf(fp, "%s", fruits[i]) 之类的东西吗?
  • 这行printf("%d\n", count); 打印什么?

标签: c arrays string file segmentation-fault


【解决方案1】:

根据您在阅读水果名称时希望如何提供存储(内存)来保存水果名称,您有 2 个选项 (2) 使用鲜为人知的 'm' 字段修饰符(在较旧的实现上是 'a',所以如果您正在使用windoze,请阅读文档以做出决定(或者同时尝试两者,看看哪个有效)。

您的代码的直接问题是fopen 是使用"w" 文件模式调用的。仅当 fruits.txt 已经显示 作为 "w" 模式不会创建不存在的文件时,这才有效。正确的mode"w+"(或"a""a+",其中任何一个都会创建一个不存在的文件)。只需将您的模式更改为"w+",即可将水果信息写入新创建的fruits.txt

有关在某些 MS 编译器上使用 VLA(可变长度数组)的问题可能会出现问题。您只需要查看您的版本和更改日志或文档(或者只是尝试阅读错误或警告。更糟糕的情况,您可以使用 pointer-to-pointer-to-type,或者一个足够大的静态数组。鉴于您使用fscanf,下面将继续使用该方法。具体而言:

    char *fruits[count]; /* here you delare an array of pointers to type char   */
                        /* utilizing a variable length array. some MS compiler */
                        /* version do not handle VLA's, VS13/VS15 should be ok */

    for (i = 0; i < count; i++)       /* read & allocate for each array element */
        if (fscanf (fp, " %ms", &fruits[i]) != 1) {    /* m allocates, a for ms */
            fprintf (stderr, "error: read/allocation for fruits[%d] failed.\n", i);
            exit (EXIT_FAILURE);
        }

(请注意上面使用的格式字符串" %ms",其中需要注意的是接受分配块的指针必须是指向char *pointer-to-pointer -- 这是 地址分配/文件的指针,例如&amp;fruits[i])

代码的其余部分应该非常熟悉,除了现在代码中的所有输入和其他关键点都已验证,方法是检查任何函数提供的return,以确保不存在错误条件到目前为止,没有一个是由所讨论的操作引起的。只有这样,您才能对代码的操作充满信心。养成习惯。

综合起来,您可以得出以下结论。

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

int main (void) {

    int count, i;
    FILE *fp;

    if (!(fp = fopen ("fruits.txt", "w+"))) {  /* "w+" required to create file */
        fprintf (stderr, "error: file open failed 'fruits.txt'.\n");
        exit (EXIT_FAILURE);
    }
    fputs ("4 Apple Banana mango berry", fp); /* write string to fruits.txt */
    fclose(fp);

    if (!(fp = fopen ("fruits.txt", "r"))) { /* validate file open for reading */
        fprintf (stderr, "error: file open failed 'fruits.txt'.\n");
        exit (EXIT_FAILURE);
    }

    if (fscanf (fp, " %d", &count) != 1) {  /* read all fruir from fruits.txt */
        fprintf (stderr, "error: in read of value from 'fruits.txt'.\n");
        exit (EXIT_FAILURE);
    }
    printf ("\n Quantity read from 'fruits.txt' is '%d'.\n\n", count);

    char *fruits[count]; /* here you delare an array of pointers to type char   */
                        /* utilizing a variable length array. some MS compiler */
                        /* version do not handle VLA's, VS13/VS15 should be ok */

    for (i = 0; i < count; i++)       /* read & allocate for each array element */
        if (fscanf (fp, " %ms", &fruits[i]) != 1) {    /* m allocates, a for ms */
            fprintf (stderr, "error: read/allocation for fruits[%d] failed.\n", i);
            exit (EXIT_FAILURE);
        }
    fclose(fp);  /* close file for the last time */

    for (i = 0; i < count; i++) /* output array */
        printf (" fruit[%d] : %s\n", i, fruits[i]);

    for (i = 0; i < count; i++) /* free allocated memory */
        free (fruits[i]);

    return 0;
}

代码的基本编译字符串(在abc.c 中)和放在bin/abc 中的可执行文件可能是:

$ gcc -Wall -Wextra -o bin/abc abc.c -std=gnu11

使用/输出示例

$ ./bin/abc

 Quantity read from 'fruits.txt' is '4'.

 fruit[0] : Apple
 fruit[1] : Banana
 fruit[2] : mango
 fruit[3] : berry

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

您必须使用内存错误检查程序来确保您没有写入超出/超出分配的内存块,尝试读取或基于未初始化的值进行跳转,最后确认您已释放所有您分配的内存。对于 Linux,valgrind 是正常的选择。

$ valgrind ./bin/abc
==30980== Memcheck, a memory error detector
==30980== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==30980== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==30980== Command: ./bin/abc
==30980==

 Quantity read from 'fruits.txt' is '4'.

 fruit[0] : Apple
 fruit[1] : Banana
 fruit[2] : mango
 fruit[3] : berry
==30980==
==30980== HEAP SUMMARY:
==30980==     in use at exit: 0 bytes in 0 blocks
==30980==   total heap usage: 10 allocs, 10 frees, 1,561 bytes allocated
==30980==
==30980== All heap blocks were freed -- no leaks are possible
==30980==
==30980== For counts of detected and suppressed errors, rerun with: -v
==30980== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 1 from 1)

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

对于一个简单的问题,我们甚至没有讨论读取文件的首选方式是使用 fgets 或 POSIX getline 一次读取整行然后从读取的行中解析单个水果,或者用strtok标记行。花点时间消化代码并为自己回答您必须查找的两个编译器问题(1)VLA 对fruits[count] 的支持和(2)您的编译器是否使用ma 来分配。

【讨论】:

    【解决方案2】:

    我试过下面的代码,发现工作正常。 这里的区别是首先我创建了文件并在其中填充了内容。

    如果我没有在文件中填写内容,那么我会遇到分段错误(并非在所有编译器上)。 因此,在您的情况下, fscanf() 似乎正在读取一些垃圾并返回一些大的垃圾号。 正如其他人也建议的那样,请检查 fscanf 返回的内容。如果返回-1,则表示您的文件中没有找到任何内容。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
        FILE *fp;
        int count;
        int i;
    
        fp = fopen ("fruits.txt", "w");
        if(fp == NULL)
        {
            printf("Can't open file!!");
            exit(0);
        }
        fputs("4 Apple Banana mango berry", fp);
        fclose(fp);*/
    
        fp = fopen("fruits.txt", "r");
        if(fp == NULL)
        {
            printf("Can't open file!!");
            exit(0);
        }
    
        fscanf(fp, "%d", &count);
        printf("%d\n", count);
    
        fclose(fp);
        char *fruits[count]; // This line is giving Segmentation fault.
    
        return 0;
    } 
    

    这会打印 4 且没有错误/故障。

    【讨论】:

      【解决方案3】:

      使用fscanf 时,必须始终检查返回值。只有当函数返回一个成功的值(阅读fscanf 文档),程序才应该继续。

      【讨论】:

      • 成功价值”是一个“有趣”的短语... ;-)
      • 我认为他的意思是表示返回的 匹配计数conversion specifiers 中存在的 i>格式字符串:p
      猜你喜欢
      • 2019-09-03
      • 2012-06-25
      • 2022-11-18
      • 1970-01-01
      • 2012-12-31
      • 1970-01-01
      • 2016-06-16
      • 2018-08-21
      • 1970-01-01
      相关资源
      最近更新 更多