如何读取文件并将内容保存在字符串中?(C语言,无崩溃或核心转储错误)

uplii1fm  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(96)

我写了一段代码来读取一个文件。读取默认文件或另一个文件,并将内容文件存储在字符串中。我不知道我错在哪里!当我运行它时,有时它是好的,但有时我会得到一个Core Dump错误!
下面是我的代码:

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

#define BUFFER_SIZE 1024
#define EXAMPLE_FILE "programing_languages.example"
#define EXAMPLE_FILE_SIZE strlen(EXAMPLE_FILE)+1

int read_file(char *buffer, char *file_name)
{
    FILE *file;

    file = fopen(file_name, "r");

    if (file == NULL)
    {
        printf("Warning!!!");
        return 1;
    }

    fread(buffer, sizeof(char), BUFFER_SIZE, file);

    fclose(file);

    return 0;
}

int main(int argc, char *argv[])
{   
    int opt;
    int file_name_customized = 0;
    char *file_name;

    while ((opt = getopt(argc, argv, "f:")) != -1) {
        switch (opt) {
            case 'f':
                snprintf(file_name, strlen(argv[2])+1, "%s", argv[2]);
                file_name_customized = 1;
                break;
            default:
                snprintf(file_name, EXAMPLE_FILE_SIZE, EXAMPLE_FILE);
        }
    }

    if (!file_name_customized)
    {
        snprintf(file_name, EXAMPLE_FILE_SIZE, EXAMPLE_FILE);
    }

    char buffer[BUFFER_SIZE] = {"\0"};

    if (read_file(buffer, EXAMPLE_FILE) == 1)
    {
        printf("Got Error To Open %s File!\n", file_name);
        return errno;
    }

    printf("%s\n", buffer);

}

字符串
我想读取一个文件并将其保存为字符串。(C语言)

bqujaahr

bqujaahr1#

你有很多错误的假设。我会给你一点关于黑暗的启示,给予
1.在下面的代码中,你误用了snprintf

int opt;
    int file_name_customized = 0;
    char *file_name;

    while ((opt = getopt(argc, argv, "f:")) != -1) {
        switch (opt) {
            case 'f':
                snprintf(file_name, strlen(argv[2])+1, "%s", argv[2]);
                file_name_customized = 1;
                break;
            default:
                snprintf(file_name, EXAMPLE_FILE_SIZE, EXAMPLE_FILE);
        }
    }

    if (!file_name_customized)
    {
        snprintf(file_name, EXAMPLE_FILE_SIZE, EXAMPLE_FILE);
    }

字符串
snprintf的前两个参数必须用缓冲区指针填充(有效指针,而不是未初始化的指针)和缓冲区大小(已经从optarg原始文件中计算出来)您假设缓冲区将在snprintf内部构建,但这是不可能的(你通过值传递指针,复制它的值,所以例程不可能改变它可能具有的未知和不可预测的值:在调用snprintf()* 之前,有一种正确的方法(因为你可以知道命令行参数的字符串长度)来做这件事 *:

int opt;
    int file_name_customized = 0;
    char *file_name = NULL;  /* initialize the buffer pointer to NULL */

    while ((opt = getopt(argc, argv, "f:")) != -1) {
        switch (opt) {
            case 'f':
                file_name = malloc(strlen(optarg)+1); /* see below, after this code snippet */
                if (file_name == NULL) { 
                    /* error, no allocatable memory, print error message
                     * and exit */
                    exit(1);
                }
                strcpy(file_name, optarg); /* copy the contents */
                file_name_customized = 1;
                break;
        }
    }


或者简单地说,如果您不打算触摸file_name内容:

int opt;
    int file_name_customized = 0;
    char *file_name = NULL;  /* initialize the buffer pointer to NULL */

    while ((opt = getopt(argc, argv, "f:")) != -1) {
        switch (opt) {
            case 'f':
                file_name = optarg; /* see below, after this code snippet */
                break;
        }
    }


(note:使用optarg的原因是你使用的argv[2]不需要是-f选项的参数的位置,你可以按照你喜欢的任何顺序指定getopt()的选项,所以如果你有更多的选项,你可以在指定-f之前指定其他选项,该选项将是-f位置之后的参数(这是由getopt()全局变量optarg指向的,如getopt(1)中所述)。
1.函数read_file()不知道缓冲区的 * 实际 * 大小。它只是假设它是BUFFER_SIZE,通常这是不正确的(如果你将它用于其他大小不同的缓冲区)。如果你将缓冲区大小作为参数传递给函数会更好(就像snprintf()在标准库中所做的那样):

int read_file(char *buffer, size_t buffer_size, char *file_name)
{
    FILE *file;

    file = fopen(file_name, "r");

    if (file == NULL)
    {
        printf("Warning!!!");  /* an explanation on why the file could not be opened would have been appreciated, with the next instruction */
        fprintf(stderr, /* common to write errors to stderr, instead */
             "fopen \"%s\": %s\n", file_name, strerror(errno));
        return -1;  /* better return -1 (as the operating system does) to
                     * indicate an error, and positive to indicate how long
                     * was the file */
    }

    /* this will return the number of read chars */
    int nread = fread(buffer, sizeof(char), buffer_size, file);

    fclose(file);

    return nread;  /* so, you know how long the filled part of the buffer was */
}


1.接下来,在main()中,您假设缓冲区已被填充并以'\0'字符终止,因为%s说明符需要知道字符串结束的位置。这是不正确的,您的文件可以包含'\0'字符,这将提前结束文件的打印,或者更糟,可以没有'\0'字符,并使printf()继续尝试访问缓冲区内容,并失败到未定义的行为(这可能是你有时欣赏)
一般来说,如果你只处理文本文件,你可以假设你的文件不会有'\0'字符,并添加它(但你需要这样做,fread()没有)如下:

int read_bytes = read_file(buffer,
           sizeof buffer - 1,  /* reserve one cell at the end to add a '\0' char */
           file_name);  /* it should be filename */
    /* we can decide to treat errors here or in the function, as you prefer,
     * but it is better not to do it in both places. */
    if (read_bytes < 0) /* now the error definition is this */
    {
        printf("Got Error To Open %s File!\n", file_name);
        return errno;
    }

    buffer[read_bytes] = '\0';/* as read_file() now cannot read a file larger
                               * or equal than the buffer size, this 
                               * statement will never assign the 0 outside
                               * the buffer */
    /* now we can print it. */
    printf("%s\n", buffer);
}


但是如果你只是想打印一个文件的内容,你可以直接做(更简单的解决方案,不需要缓冲,因为 stdio package已经以透明的方式进行缓冲),并且不受缓冲区大小的限制(所以你可以打印文件的文件内容,文件的字节数更大)。

#include <errno.h>  /* for errno variable */
#include <getopt.h> /* for getopt() and optarg */
#include <stdio.h>  /* for fgetc() and fputc() and all printf()s */
#include <stdlib.h> /* for exit() */
#include <string.h> /* for strerror() function */

int
main(int argc, char **argv)
{
    int opt;
    const char *file_name = NULL;
    FILE *f = stdin; /* by default, read from stdin */
    while ((opt = getopt(argc, argv, "f:")) != EOF) {
        switch (opt) {
        case 'f': file_name = optarg; break;
        }
    }
    if (file_name != NULL) { /* if we got a -f option with an argument */
        f = fopen(file_name, "r");
        if (!f) {
            fprintf(stderr, "Error: %s: couldn't fopen; %s\n",
                    file_name, strerror(errno));
            exit(1);
        }
    }

    /* now read and print it (at the same time) */
    int c;  /* WARNING: c ***MUST*** be integer, not a char, see fgetc() manual
             * page. */
    while ((c = fgetc(f)) != EOF) {
        putchar(c);
    }
    return 0;
} /* main */


这有一些好处:
1.你不需要做缓冲区管理(stdio自动为你做的事情)
1.您可以使用该程序的两个接口:a)使用-f选项指定文件,或者B)通过从shell重定向标准输入
1.越简单越好。
或者,如果你想要一个函数来做所有的文件工作:

#include <errno.h>  /* for errno variable */
#include <getopt.h> /* for getopt() and optarg */
#include <stdio.h>  /* for fgetc() and fputc() and all printf()s */
#include <stdlib.h> /* for exit() */
#include <string.h> /* for strerror() function */

ssize_t print_file(char *name, FILE *out)
{
    FILE *in = fopen(name, "r");
    if (!in) return -1;
    int c;
    ssize_t count = 0;
    while ((c = fgetc(in)) != EOF) {
        fputc(c, out);
        count++;
    }
    fclose(in);
    return count;
}

int
main(int argc, char **argv)
{
    int opt;
    size_t total = 0;

    while ((opt = getopt(argc, argv, "f:")) != EOF) {
        switch (opt) {
        case 'f':
            ssize_t chars = print_file(optarg, stdout);
            fprintf(stderr,
                    "%s: %zd chars\n",
                    optarg, chars);
            total += chars;
            break;
        }
    }
    fprintf(stderr,
            "total: %zu chars\n",
            total);
    return 0;
} /* main */


最后一种方法(在我们处理选项时处理文件)允许您添加多个-f选项,如:

$ a.out -f a.c -f a.c -f a.c


a.c复制三次到标准输出(就像unix cat命令一样)

nhn9ugyo

nhn9ugyo2#

代码在许多方面都很脆弱。

  • 如果文件大小>= BUFFER_SIZEread_file()将失败。
  • 不保证有\0来结束读取的字节序列,除非文件的确切大小是BUF_SIZE-1,所以
char buffer[BUFFER_SIZE] = {"\0"};
    if (read_file(buffer, EXAMPLE_FILE) == 1)
    {
        printf("Got Error To Open %s File!\n", file_name);
        return errno;
    }
    printf("%s\n", buffer);

字符串
并且printf()调用将失败。

  • 调用read_file()时,使用EXAMPLE_FILE代替file_name
  • getopt()有点太多了,当程序的单一目的是加载和打印一个用文件内容构建的字符串时:只需使用文件名。

3个示例

下面是在C中执行此操作的3个简单示例。
所有3个编译器在微软编译器和gcc下编译,并运行正常。
示例中使用的文件是28.txt,大小为28字节

01234
FGHIJ
56789
ABCDE


在许多情况下,我们需要更改\n的编码。我现在在Windows中,默认内容是

SO >
SO > od -x 28.txt
0000000 3130 3332 0d34 460a 4847 4a49 0a0d 3635
0000020 3837 0d39 410a 4342 4544 0a0d
0000034

SO >


我们看到\n使用2个字节作为0x0d0a

示例1:一次性读取文件

#ifdef __linux__
  #define _GNU_SOURCE 
  #include <unistd.h>
#else
  #pragma warning(disable : 4013)
#endif

#include <errno.h>
#include <fcntl.h>
#include <iso646.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

long long int get_file_size(const char*);

int main(int argc, char** argv)
{
  const char* dflt_f_name = "28.txt";
  char        file_name[256];
  if (argc < 2)
      strcpy(file_name, dflt_f_name);
  else
      strcpy(file_name, argv[1]);
  int in = open(file_name, O_RDONLY);
  if (in < 0) return -1;  // could not open
  struct stat f_stats;
  if (fstat(in, &f_stats) < 0) return -2;  // fstat error
  // is it a regular file?
  if (not(f_stats.st_mode & S_IFREG)) return -3;
  if (f_stats.st_size < 1) return -4;
  fprintf(
      stderr, "size of file \"%s\" is %d\n", file_name,
      f_stats.st_size);
  off_t          size   = 1 + f_stats.st_size;
  unsigned char* buffer = malloc(size);
  if (buffer == NULL) return -5;
  int n = read(in, buffer, size);
  close(in);
  *(n + buffer) = 0; // terminate string
  fprintf(stderr,
      "%d bytes read into buffer\nstrlen(buffer) is "
      "%llu\n\n",
      n, strlen(buffer));
  // show on screen
  printf("Bytes:\t");
  for (size_t i = 0; i < n; ++i)
  {
      if (isprint(*(buffer + i)))
          printf("%c ", *(buffer + i));
      else
          printf("0x%X ", *(buffer + i));
  }
  printf("\n");    free(buffer);
  fprintf(stderr,"\n");
  return 0;
}


该计划只是

  • 调用stat获取文件大小
  • 为文件分配内存块
  • 使用单次读取来上载文件
  • 终止字符串
  • 显示了数据
SO > ex1 28.txt
size of file "28.txt" is 28
24 bytes read into buffer
strlen(buffer) is 24

Bytes:  0 1 2 3 4 0xA F G H I J 0xA 5 6 7 8 9 0xA A B C D E 0xA

SO >

示例2:根据需要使用扩展缓冲区

这个方法使用了一个初始缓冲区,并根据需要进行扩展。其思想是分配一个足够大的缓冲区,以便在单次读取中容纳大多数预期文件,但随后根据需要扩展它以容纳大于初始块的文件。
这里使用的缓冲区只有4个字节,这只是为了测试.使用更大的东西,如1MB。

#define BUF_SIZE 4

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

int main(int argc, char** argv)
{
    const char* dflt_f_name = "28.txt";
    char        file_name[256];
    if (argc < 2)
        strcpy(file_name, dflt_f_name);
    else
        strcpy(file_name, argv[1]);

    FILE* in = fopen(file_name, "rb");
    if (in == NULL) return -1;

    size_t         b_size = 1 + BUF_SIZE;  // buffer
    size_t         f_size = 0;             // file
    unsigned char* buffer = malloc(b_size);
    if (buffer == NULL) return -2;
    size_t n       = fread(buffer, 1, BUF_SIZE, in);
    if (n < 1)
    {
        fclose(in), free(buffer);
        return -3;
    }
    f_size += n;
    if (n < BUF_SIZE)
    {
        buffer[n] = 0;  // terminate string
        fprintf(
            stderr,
            "\n\n\
    buffer size is: %llu\n\
    file size is: %llu\n\
    string size in memory is: %llu\n\n",
            b_size, f_size, strlen(buffer));
        fclose(in), free(buffer);
        return 0;
    }

    while (n > 0)
    {
        unsigned char* buf =
            realloc(buffer, b_size + BUF_SIZE);
        if (buf == NULL)
        {
            fclose(in), free(buffer);
            return -4;
        }
        buffer = buf;
        b_size += BUF_SIZE;
        n = fread(buffer + f_size, 1, BUF_SIZE, in);
        f_size += n;
        if (n == BUF_SIZE) continue;
        *(buffer + f_size) = 0;
        break;
    }
    fprintf(
        stderr,
        "\n\n\
    buffer size is: %llu\n\
    file size is: %llu\n\
    string size in memory os: %llu\n\n",
        b_size, f_size, strlen(buffer));

    printf("Bytes:\t");
    fclose(in);
    for (size_t i = 0; i < f_size; ++i)
    {
        if (isprint(*(buffer + i)))
            printf("%c ", *(buffer + i));
        else
            printf("0x%X ", *(buffer + i));
    }
    printf("\n");
    free(buffer);
    return 0;
}


重要的部分是循环内部,围绕realloc的逻辑和缓冲区大小的变化。

SO > ex2 28.txt

    buffer size is: 33
    file size is: 28
    string size in memory os: 28

Bytes:  0 1 2 3 4 0xD 0xA F G H I J 0xD 0xA 5 6 7 8 9 0xD 0xA A B C D E 0xD 0xA

SO >

示例3:将文件作为指向各行的指针数组上传

一般来说,这是更有用的方式,因为在加载后我们可以直接访问原始文件的任何一行。作为一个例子,这里输入文件被排序,然后再次 * 打印 *。

以下是用于保存文件的struct

typedef struct
{
    size_t incr;   // increment size
    size_t limit;  // actual allocated size
    size_t size;   // size in use
    char** line;   // the lines

} Block;

Block* create_blk(size_t size, size_t increment);
Block* delete_blk(Block* block_to_go);
int    resize_blk(Block* block_to_go);
int    show_blk(Block* block, const char* msg);

Block* load_file(const char*);
int    cmp_line(const void*, const void*);
void   usage();


以及用于加载文件、显示其内容等的方法。
main程序可以简单地转换为:

  • 获取文件名并调用load_file()
  • 显示线路
  • 对它们进行排序
  • 再次显示行,排序
  • 释放所有
int main(int argc, char** argv)
{
    char msg[80] = {0};
    if (argc < 2) usage();
    Block* test = load_file(argv[1]);
    if (test == NULL) return -1;
    sprintf(
        msg, "\n\n==> Loading \"%s\" into memory", argv[1]);
    show_blk(test, msg);
    qsort(test->line, test->size, sizeof(void*), cmp_line);
    sprintf(
        msg, "\n\n==> \"%s\" after sort in-memory",
        argv[1]);
    show_blk(test, msg);
    test = delete_blk(test);
    return 0;
};

完整代码

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

typedef struct
{
    size_t incr;   // increment size
    size_t limit;  // actual allocated size
    size_t size;   // size in use
    char** line;   // the lines

} Block;

Block* create_blk(size_t size, size_t increment);
Block* delete_blk(Block* block_to_go);
int    resize_blk(Block* block_to_go);
int    show_blk(Block* block, const char* msg);

Block* load_file(const char*);
int    cmp_line(const void*, const void*);
void   usage();

int main(int argc, char** argv)
{
    char msg[80] = {0};
    if (argc < 2) usage();
    Block* test = load_file(argv[1]);
    if (test == NULL) return -1;
    sprintf(
        msg, "\n\n==> Loading \"%s\" into memory", argv[1]);
    show_blk(test, msg);
    qsort(test->line, test->size, sizeof(void*), cmp_line);
    sprintf(
        msg, "\n\n==> \"%s\" after sort in-memory",
        argv[1]);
    show_blk(test, msg);
    test = delete_blk(test);
    return 0;
};

int cmp_line(const void* one, const void* other)
{
    return strcmp(
        *((const char**)one), *((const char**)other));
}

Block* create_blk(size_t size, size_t increment)
{
    Block* nb = (Block*)malloc(sizeof(Block));
    if (nb == NULL) return NULL;
    nb->incr  = increment;
    nb->limit = size;
    nb->size  = 0;
    nb->line  = (char**)malloc(sizeof(char*) * size);
    return nb;
}

Block* delete_blk(Block* blk)
{
    if (blk == NULL) return NULL;
    for (size_t i = 0; i < blk->size; i += 1)
        free(blk->line[i]);  // free lines
    free(blk->line);         // free block
    free(blk);               // free struct
    return NULL;
}

int resize_blk(Block* nb)
{
    const size_t new_sz = nb->limit + nb->incr;
    char*        new_block =
        realloc(nb->line, (new_sz * sizeof(char*)));
    if (new_block == NULL)
    {
        fprintf(
            stderr,
            "\tCould not extend block to %zd "
            "lines\n",
            new_sz);
        return -1;
    }
    nb->limit = new_sz;
    nb->line  = (char**)new_block;
    return 0;
}  // resize_blk()

int show_blk(Block* bl, const char* msg)
{
    if (msg != NULL) printf("%s\n", msg);
    if (bl == NULL)
    {
        printf("Status: not allocated\n");
        return -1;
    }
    printf(
        "Status: %zd of %zd lines. [Incr. is %zd]:\n",
        bl->size, bl->limit, bl->incr);
    for (unsigned i = 0; i < bl->size; i += 1)
        printf("%4d\t%s", 1 + i, bl->line[i]);
    return 0;
}

Block* load_file(const char* f_name)
{
    if (f_name == NULL) return NULL;
    fprintf(stderr, "loading \"%s\" into memory\n", f_name);
    FILE* F = fopen(f_name, "r");
    if (F == NULL) return NULL;
    // file is open
    Block* nb = create_blk(4, 16);  // block size is 8
    char   line[200];
    char*  p = &line[0];
    p        = fgets(p, sizeof(line), F);
    while (p != NULL)
    {
        // is block full?
        if (nb->size >= nb->limit)
        {
            resize_blk(nb);
            printf(
                "Block extended for a total of %zd "
                "pointers\n",
                nb->limit);
        }
        // now copy the line
        nb->line[nb->size] = (char*)malloc(1 + strlen(p));
        strcpy(nb->line[nb->size], p);
        nb->size += 1;
        // read next line
        p = fgets(p, sizeof(line), F);
    };  // while()
    fclose(F);
    return nb;
}

void usage()
{
    fprintf(stderr, "Use: program file_to_load\n");
    exit(EXIT_FAILURE);
}

示例输出

SO > ex3
Use: program file_to_load

SO > ex3 28.txt
loading "28.txt" into memory

==> Loading "28.txt" into memory
Status: 4 of 4 lines. [Incr. is 16]:
   1    01234
   2    FGHIJ
   3    56789
   4    ABCDE

==> "28.txt" after sort in-memory
Status: 4 of 4 lines. [Incr. is 16]:
   1    01234
   2    56789
   3    ABCDE
   4    FGHIJ

SO >

aelbi1ox

aelbi1ox3#

如何读取文件并将内容保存为字符串?
1.打开文件。检查错误。
1.通过fread()读取整个文件,使用一个大小合适的缓冲区,比如4K,并对返回的长度求和。
1.继续第2步,直到出现错误或未读取任何内容。
1.分配sum + 1。检查分配是否成功。
1.重新读取整个文件,将1 fread()放入分配的缓冲区。注意返回的长度。
1.关闭档案。
1.追加一个 *null字符 *。
1.如果长度符合预期,则成功,否则失败。
1.返回分配的指针,失败时返回NULL。

ehxuflar

ehxuflar4#

其中一个答案告诉你要读两遍文件,但没有解释为什么。下面是解释。
确定文件大小的常用方法是使用fseek/ftell。将文件的读指针设置为文件的结尾;那么它的位置将告诉文件的大小。这种方法适用于二进制文件(您以"rb"模式打开的文件),但用于文本文件("r")它是不准确的。在常见的情况下,不准确的唯一原因是从\r\n\n在Windows上的转换。因为它的确定分配一个更大的缓冲区,你可以使用这个方法来读取整个文件。

char *read_file(char *file_name)
{
    FILE *file = fopen(file_name, "r");
    char *result = NULL;

    if (file == NULL)
        goto error;

    fseek(file, 0, SEEK_END);
    size_t size = (size_t)ftell(file);

    result = malloc(size + 1);
    if (result == NULL)
        return NULL;

    rewind(file);
    size_t real_size = fread(result, 1, size, file);
    if (ferror(file))
        goto error;

    result[real_size] = '\0';
    fclose(file);
    return result;

error:
    free(result);
    if (file)
        fclose(file);
    return NULL;
}

字符串
这种常用的方法不能保证有效:这样使用时,ftell的输出在技术上是不确定的;但在实践中,它应该可以工作。在以下情况下它会失败:

  • 宽字符文件(从其中读取wchar_t字符的文件)
  • 超长文件(大小大于LONG_MAX,可能等于2 GB)
  • 任何不可预见的情况

相关问题