【问题标题】:Segmentation fault when I fopen我打开时出现分段错误
【发布时间】:2015-12-13 18:30:31
【问题描述】:

我是 C 文件管理的新手。我的老师希望作为家庭作业创建一个从源文件复制到目标文件的函数。我创建了,但它一直给我错误:Segmentation Fault。

void source_to_destination(FILE *source , FILE *destination)
{
    char name_source[10], name_destination[10],line[100];
    memset(line,0,sizeof(line));
    memset(name_source,0,sizeof(name_source));
    memset(name_destination,0,sizeof(name_destination));
    read_name_file(name_source);
    read_name_file(name_destination);

    source = fopen(name_source,"r");
    destination = fopen(name_destination,"w");

    while(fgets(line,sizeof(line),source) != NULL)
    {
        fputs(line,destination);
    }

}

【问题讨论】:

  • 总是检查可能遇到错误的函数的结果!取消引用 null 指针 是未定义的行为。另请注意,您必须 fclose 文件 iff 成功打开。
  • sourcedestination 指针是函数的参数,但您不使用通过这些参数传递的参数值。这本质上不是错误的,但是您不 close 文件的事实表明您认为调用者会以某种方式通过这些参数从函数接收流指针。它不会,如果调用者认为它已经完成了,那肯定会产生段错误。
  • 如果无法将FILE 地址传递给main,如何在main 中关闭它们?也许你应该咨询你的老师;有一些误解。 C 仅是按值调用。
  • 编译所有警告和调试信息 (gcc -Wall -Wextra -g) 并使用调试器 (`gdb`)
  • 请阅读How to Ask 并提供minimal reproducible example。您的代码中有相当多的地方未定义行为。

标签: c file file-io segmentation-fault


【解决方案1】:

将数据从一个文件复制到另一个文件时,首选二进制读写。使用fgetsgetline 等面向行的输入函数读取文件中的所有字符失败的原因有很多。文本输出函数也有类似的缺点(例如,试图写入可打印范围之外的字符或具有 ASCII 替代含义的字符)

使用freadfwrite 从二进制模式读取和写入文件并不比使用fgetsfputs 更难。但是,使用 freadfwrite 可以避免在文本模式下尝试常规文件复制所固有的缺陷,从而确保您获得正确和准确的数据副本。

如果您知道源文件中只包含文本,那么在文本模式下复制它没有任何问题。这只是意味着您将不得不编写另一个函数来处理非文本文件。 (通常您不会看到基于文件内容的不同复制例程)。以二进制形式进行读写消除了所有这些考虑。

以下是filecopy 函数的简短示例,该函数将文件中的所有字节读入缓冲区,然后将缓冲区的内容写入目标文件。 (缓冲读/写通常效率更高,您可以通过调整MAXS 轻松调整缓冲区大小)该函数返回成功复制的字节数,否则返回-1。如果您有任何问题,请查看并告诉我:

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

#define MAXS 256

int filecopy (char *source, char *dest);

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

    if (argc < 3) { /* validate 2 arguments given */
        fprintf (stderr, "usage: %s file1 file2\n", argv[0]);
        return 1;
    }

    int filesize = 0;

    if ((filesize = filecopy (argv[1], argv[2])) == -1) {
        fprintf (stderr, "error: filecopy failed.\n");
        return 1;
    }

    printf ("\n copied '%s' -> '%s' ('%d' bytes)\n\n", 
            argv[1], argv[2], filesize);

    return 0;
}

int filecopy (char *source, char *dest)
{
    char *buf = NULL;   /* buffer used to read MAXS bytes from file */
    size_t nbytes = 0;  /* number of bytes read from file */
    size_t idx = 0;     /* file index (length)            */
    FILE *fp = fopen (source, "r"); /* stream pointer     */

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

    /* allocate MAXS size read buf initially */
    if (!(buf = calloc (MAXS, sizeof *buf))) {
        fprintf (stderr, "error: virtual memory exhausted.\n");
        return -1;
    }

    /* while data read MAXS *buf from file - realloc for next read */
    while ((nbytes = fread (buf+idx, sizeof *buf, MAXS, fp))) 
    {
        idx += nbytes;              /* update total bytes read */
        if (nbytes < MAXS) break;   /* end-of-file reached */

        /* full read - realloc for next   */
        void *tmp;
        if (!(tmp = realloc (buf, (idx + nbytes) * sizeof *buf))) {
            fprintf (stderr, "error: virtual memory exhausted.\n");
            exit (EXIT_FAILURE);
        }
        buf = tmp;
    }
    fclose (fp);    /* close input stream   */

    if (!(fp = fopen (dest, "w+b"))) { /* open output stream */
        fprintf (stderr, "error: file open failed '%s'.\n", dest);
        exit (EXIT_FAILURE);
    }
    fwrite (buf, sizeof *buf, idx, fp);
    fclose (fp);    /* close output stream  */

    free (buf);
    return (int)idx;
}

编译

gcc -Wall -Wextra -O3 -o bin/filecopy_simple filecopy_simple.c

输入文件(二进制)

-rw-r--r--  1 david david  66672 Nov 19 13:17 acarsout2.bin

使用/输出

$ ./bin/filecopy_simple dat/acarsout2.bin dat/acarsout3.bin

 copied 'dat/acarsout2.bin' -> 'dat/acarsout3.bin' ('66672' bytes)

验证

$ ls -al acarsout[23]*
-rw-r--r--  1 david david  66672 Nov 19 13:17 acarsout2.bin
-rw-r--r--  1 david david  66672 Dec 13 14:51 acarsout3.bin

$ diff dat/acarsout2.bin dat/acarsout3.bin
$

【讨论】:

  • 很好,但对于许多只懂文本脚本的“程序员”来说基本上是一个无用的功能:) 他们除了文本行之外什么都不能处理;他们的大脑爆炸了。无效的答案被接受了:(
  • 我同意。与接受哪个答案无关,重要的是提醒您并非所有文件都是文本,并提供如何以一般方式处理文件副本的合理示例。仔细想想,教育只不过是慢慢揭示真相——我们所能做的就是帮助把封面拉得更远:)
【解决方案2】:

以下代码

  1. 编译干净
  2. 执行期望操作(复制文件)
  3. 执行适当的错误检查
  4. 正在从命令行获取文件名 您需要修改它以通过现有函数获取文件名
  5. 总是自行清理,包括关闭打开的文件
  6. 演示如何将FILE* 变量--传入-- 函数

    子函数原型等需要修改 如果要在子函数中打开文件 然后让main() 关闭它们。

这是执行所需操作的建议方法

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


#define MAX_LINE_LENGTH (256)

// prototypes
void source_to_destination(FILE *source , FILE *destination);


int main( int argc, char * argv[] )
{
    if( 3 != argc )
    { // not correct number of command line parameters
        fprintf( stderr, "USAGE: %s <sourceFili> <destinationFile>\n", argv[0]);
        exit( EXIT_FAILURE );
    }

    // implied else, correct number of arguments

    FILE *fp_in = NULL;
    if( NULL == (fp_in = fopen( argv[1], "r") ) )
    { // then fopen failed
        fprintf( stderr, "fopen for input file: %s failed due to %s\n", argv[1], strerror(errno) );
        exit( EXIT_FAILURE );
    }

    // implied else, fopen input file successful

    FILE *fp_out = NULL;
    if( NULL == (fp_out = fopen( argv[2], "w") ) )
    { // then fopen failed
        fprintf( stderr, "fopen for output file: %s failed due to %s\n", argv[2], strerror(errno) );
        fclose( fp_in ); // cleanup
        exit( EXIT_FAILURE );
    }

    // implied else, fopen output file successful

    source_to_destination( fp_in, fp_out );
    fclose( fp_in );
    fclose( fp_out );
    return 0;
} // end function: main


void source_to_destination(FILE *source , FILE *destination)
{
    char line[ MAX_LINE_LENGTH ];

    while(fgets(line,sizeof(line),source) )
    {
        if( EOF == fputs(line,destination) )
        { // then fputs failed
            fprintf( stderr,  "fputs to output file failed due to %s\n", strerror(errno) );
            fclose( source );
            fclose( destination );
            exit( EXIT_FAILURE );
        }
    }
} // end function: source_to_destination

【讨论】:

  • 未能在错误消息中包含文件名是一个相当严重的遗漏。只需执行perror( argv[1] )perror( argv[2] )。此外,将所有错误打印到 stderr,包括报告错误使用的消息。
  • 感谢@WilliamPursell,我会相应地修改我的答案
  • 奇怪的是,有些人对这个答案投了反对票,当我在多个文本文件上运行它时,它工作正常。 (但是,我没有尝试复制二进制文件,因为问题没有询问如何复制二进制文件。)
猜你喜欢
  • 2019-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多