C 使用结构读取PGM文件

C 使用结构读取PGM文件,c,pointers,struct,dynamic-arrays,pgm,C,Pointers,Struct,Dynamic Arrays,Pgm,我正在做一个项目,涉及编写函数来读取/写入和编码/解码PGM文件。Im使用带函数的结构读取PGM文件。我对结构以及它们是如何进行语法化的非常陌生,所以我只是想知道这段代码是否能正确地将扫描的数据读入我的结构中 这是我的代码(C): 你们觉得还好吗?我不知道PGM规范,但你们有三个常见的错误,当在不同于你们的平台上编译代码时,它们会导致代码行为不端: 持久性。您必须为您的数据格式精确地定义它。 在您的情况下,int可能是小端,在将代码移植到big-endian平台时,您必须考虑到这一点。 另见 结

我正在做一个项目,涉及编写函数来读取/写入和编码/解码PGM文件。Im使用带函数的结构读取PGM文件。我对结构以及它们是如何进行语法化的非常陌生,所以我只是想知道这段代码是否能正确地将扫描的数据读入我的结构中

这是我的代码(C):


你们觉得还好吗?

我不知道PGM规范,但你们有三个常见的错误,当在不同于你们的平台上编译代码时,它们会导致代码行为不端:

  • 持久性。您必须为您的数据格式精确地定义它。 在您的情况下,int可能是小端,在将代码移植到big-endian平台时,您必须考虑到这一点。 另见

  • 结构填料。根据平台的不同,编译器可以在结构中填充字段以加快访问速度。您可能希望对结构使用类似pragma pack的构造,否则,您的代码可能与其他编译器有问题(即使假设使用相同的平台)。 另见

  • 使用固定宽度类型。例如,使用int64\u t而不是long等。 另见


  • 我不知道PGM规范,但您有三个常见错误,它们会使您的代码在不同于您的平台上编译时出现错误:

  • 持久性。您必须为您的数据格式精确地定义它。 在您的情况下,int可能是小端,在将代码移植到big-endian平台时,您必须考虑到这一点。 另见

  • 结构填料。根据平台的不同,编译器可以在结构中填充字段以加快访问速度。您可能希望对结构使用类似pragma pack的构造,否则,您的代码可能与其他编译器有问题(即使假设使用相同的平台)。 另见

  • 使用固定宽度类型。例如,使用int64\u t而不是long等。 另见


  • 继续我前面的评论,您在处理时遇到了一些问题,这些问题将阻止您成功读取文件

    首先,
    fgets(PGMcheck,sizeof(PGMcheck),infle)不能保证正确读取
    PGMcheck
    。幻数后面可能跟有
    (空格、制表符、CRs、LFs)
    ,因此
    fgets
    将读取的不仅仅是幻数,除非它后面跟有一个
    “\n”
    ——格式不保证。虽然
    fgets()
    通常是进行面向行输入的正确方法,但PGM格式不能保证按行进行格式化,因此只能使用格式化输入函数或逐字符方法

    (您可以使用
    fgets()
    ,但这需要解析生成的缓冲区,并保存超出幻数的缓冲区的任何部分,以作为下次读取的开始部分)

    您已使用
    更正了字符串比较的尝试=
    而不是
    strcmp
    ,但您仍然必须将幻数与
    “P2”
    进行比较,以便读取普通PGM格式文件(如您最初的问题所述),继续将幻数读取为字符串,但使用格式化输入函数(
    fscanf
    )只读取,直到遇到第一个空格为止,不管空白是什么

    最后,不需要将幻数存储为
    plain_pgm
    结构的一部分。这是您在尝试填充结构之前要验证的内容。它要么是
    “P2”
    ,要么不是--不需要存储它

    为了便于携带,在读取图像文件时,最好使用精确的宽度类型进行存储。有许多好处,但主要是,无论是在x86_64上运行还是在TI-MSP432芯片上运行,您的程序都将正常运行。精确宽度类型在
    stdint.h
    中定义,打印和读取精确宽度类型的宏在
    inttypes.h
    中提供。您使用的不是
    char
    ,而是
    int8\u t
    ;您使用的是
    uint8\u t
    ,依此类推,其中数值指定类型的确切字节数

    因此,您的pgm结构可以如下所示:

    typedef struct {            /* struct for plain pgm image */
        uint32_t w, h;          /* use exact width types for portable code */
        uint16_t max;           /* 16-bit max */
        uint16_t **pixels;      /* pointer-to-pointer for pixel values */
    } plain_pgm;
    
    int read_pgm (FILE *fp, plain_pgm *pgm)
    {
        char buf[RDBUF];            /* buffer for magic number */
        uint32_t h = 0, w = 0;      /* height/width counters */
    
        if (fscanf (fp, "%s", buf) != 1) {  /* read magic number */
            fputs ("error: invalid format - magic\n", stderr);
            return 0;
        }
    
        if (strcmp (buf, MAGIC_PLN) != 0) { /* validate magic number */
            fprintf (stderr, "error: invalid magic number '%s'.\n", buf);
            return 0;
        }
    
        /* read pgm width, height, max gray value */
        if (fscanf (fp, "%" SCNu32 " %" SCNu32 " %" SCNu16, 
                    &pgm->w, &pgm->h, &pgm->max) != 3) {
            fputs ("error: invalid format, h, w, max or included comments.\n",
                    stderr);
            return 0;
        }
    
        /* validate allocation of pointers and storage for pixel values */
        if (!(pgm->pixels = alloc_pgm_pixels (pgm->w, pgm->h)))
            return 0;
    
        for (;;) {  /* loop continually until image read */
            if (fscanf (fp, "%" SCNu16, &pgm->pixels[h][w]) != 1) {
                fputs ("error: stream error or short-read.\n", stderr);
                return 0;
            }
            if (++w == pgm->w)
                w = 0, h++;
            if (h == pgm->h)
                break;
        }
    
        return 1;
    }
    
    您的分配基本上是正确的,但要重新排列以返回指向
    uint16\u t
    的指针(适合
    最大灰度值
    像素值),可以执行以下操作:

    uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
    {
        uint16_t **pixels = NULL;
    
        /* allocate/validate height number of pointers */
        if (!(pixels = malloc (h * sizeof *pixels))) {
            perror ("malloc-pixels");
            return NULL;
        }
        /* allocate/validate width number of values per-pointer */
        for (uint32_t i = 0; i < h; i++)
            if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
                perror ("malloc-pixels[i]");
                return NULL;
            }
    
        return pixels;  /* return allocated pointers & storage */
    }
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <inttypes.h>
    
    #define RDBUF       32      /* if you need a constant, #define one (or more) */
    #define MAGIC_PLN  "P2"
    
    typedef struct {            /* struct for plain pgm image */
        uint32_t w, h;          /* use exact width types for portable code */
        uint16_t max;           /* 16-bit max */
        uint16_t **pixels;      /* pointer-to-pointer for pixel values */
    } plain_pgm;
    
    uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
    {
        uint16_t **pixels = NULL;
    
        /* allocate/validate height number of pointers */
        if (!(pixels = malloc (h * sizeof *pixels))) {
            perror ("malloc-pixels");
            return NULL;
        }
        /* allocate/validate width number of values per-pointer */
        for (uint32_t i = 0; i < h; i++)
            if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
                perror ("malloc-pixels[i]");
                return NULL;
            }
    
        return pixels;  /* return allocated pointers & storage */
    }
    
    int read_pgm (FILE *fp, plain_pgm *pgm)
    {
        char buf[RDBUF];            /* buffer for magic number */
        uint32_t h = 0, w = 0;      /* height/width counters */
    
        if (fscanf (fp, "%s", buf) != 1) {  /* read magic number */
            fputs ("error: invalid format - magic\n", stderr);
            return 0;
        }
    
        if (strcmp (buf, MAGIC_PLN) != 0) { /* validate magic number */
            fprintf (stderr, "error: invalid magic number '%s'.\n", buf);
            return 0;
        }
    
        /* read pgm width, height, max gray value */
        if (fscanf (fp, "%" SCNu32 " %" SCNu32 " %" SCNu16, 
                    &pgm->w, &pgm->h, &pgm->max) != 3) {
            fputs ("error: invalid format, h, w, max or included comments.\n",
                    stderr);
            return 0;
        }
    
        /* validate allocation of pointers and storage for pixel values */
        if (!(pgm->pixels = alloc_pgm_pixels (pgm->w, pgm->h)))
            return 0;
    
        for (;;) {  /* loop continually until image read */
            if (fscanf (fp, "%" SCNu16, &pgm->pixels[h][w]) != 1) {
                fputs ("error: stream error or short-read.\n", stderr);
                return 0;
            }
            if (++w == pgm->w)
                w = 0, h++;
            if (h == pgm->h)
                break;
        }
    
        return 1;
    }
    
    int main (int argc, char **argv) {
    
        plain_pgm pgm = { .w = 0 }; /* plain_pgm struct instance */
        /* use filename provided as 1st argument (stdin by default) */
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {  /* validate file open for reading */
            perror ("file open failed");
            return 1;
        }
    
        if (!read_pgm (fp, &pgm)) { /* validate/allocate/read pgm file */
            fputs ("error: read_pgm failed.\n", stderr);
            return 1;
        }
        if (fp != stdin)            /* close file if not stdin */
            fclose (fp);
    
        /* output success */
        printf ("successful read of '%s'\n%" PRIu32 "x%" PRIu32 " pixel values.\n",
                argc > 1 ? argv[1] : "stdin", pgm.w, pgm.h);
    
        for (uint32_t i = 0; i < pgm.h; i++)    /* free pixel storage */
            free (pgm.pixels[i]);
        free (pgm.pixels);                      /* free pointers */
    }
    

    (<强>注:此读取函数不考虑注释行,实现对注释行的忽略”。您可以在读取“魔法号”、“宽度”、“高度”和“最大灰度值”的每个部分之前,在与“代码> >“%%[^ \n′]”之间读取对“代码> fSCANF

    跳过任意数量的空格,读取并包括下一个
    “#”
    字符和行尾,或者在循环中使用
    fgetc
    搜索下一个非空格字符并检查它是否是
    “#”
    如果不是,则清除到行尾。)

    将其全部放在一个示例中,您可以执行以下操作:

    uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
    {
        uint16_t **pixels = NULL;
    
        /* allocate/validate height number of pointers */
        if (!(pixels = malloc (h * sizeof *pixels))) {
            perror ("malloc-pixels");
            return NULL;
        }
        /* allocate/validate width number of values per-pointer */
        for (uint32_t i = 0; i < h; i++)
            if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
                perror ("malloc-pixels[i]");
                return NULL;
            }
    
        return pixels;  /* return allocated pointers & storage */
    }
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <inttypes.h>
    
    #define RDBUF       32      /* if you need a constant, #define one (or more) */
    #define MAGIC_PLN  "P2"
    
    typedef struct {            /* struct for plain pgm image */
        uint32_t w, h;          /* use exact width types for portable code */
        uint16_t max;           /* 16-bit max */
        uint16_t **pixels;      /* pointer-to-pointer for pixel values */
    } plain_pgm;
    
    uint16_t **alloc_pgm_pixels (uint32_t w, uint32_t h)
    {
        uint16_t **pixels = NULL;
    
        /* allocate/validate height number of pointers */
        if (!(pixels = malloc (h * sizeof *pixels))) {
            perror ("malloc-pixels");
            return NULL;
        }
        /* allocate/validate width number of values per-pointer */
        for (uint32_t i = 0; i < h; i++)
            if (!(pixels[i] = malloc (w * sizeof *pixels[i]))) {
                perror ("malloc-pixels[i]");
                return NULL;
            }
    
        return pixels;  /* return allocated pointers & storage */
    }
    
    int read_pgm (FILE *fp, plain_pgm *pgm)
    {
        char buf[RDBUF];            /* buffer for magic number */
        uint32_t h = 0, w = 0;      /* height/width counters */
    
        if (fscanf (fp, "%s", buf) != 1) {  /* read magic number */
            fputs ("error: invalid format - magic\n", stderr);
            return 0;
        }
    
        if (strcmp (buf, MAGIC_PLN) != 0) { /* validate magic number */
            fprintf (stderr, "error: invalid magic number '%s'.\n", buf);
            return 0;
        }
    
        /* read pgm width, height, max gray value */
        if (fscanf (fp, "%" SCNu32 " %" SCNu32 " %" SCNu16, 
                    &pgm->w, &pgm->h, &pgm->max) != 3) {
            fputs ("error: invalid format, h, w, max or included comments.\n",
                    stderr);
            return 0;
        }
    
        /* validate allocation of pointers and storage for pixel values */
        if (!(pgm->pixels = alloc_pgm_pixels (pgm->w, pgm->h)))
            return 0;
    
        for (;;) {  /* loop continually until image read */
            if (fscanf (fp, "%" SCNu16, &pgm->pixels[h][w]) != 1) {
                fputs ("error: stream error or short-read.\n", stderr);
                return 0;
            }
            if (++w == pgm->w)
                w = 0, h++;
            if (h == pgm->h)
                break;
        }
    
        return 1;
    }
    
    int main (int argc, char **argv) {
    
        plain_pgm pgm = { .w = 0 }; /* plain_pgm struct instance */
        /* use filename provided as 1st argument (stdin by default) */
        FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
    
        if (!fp) {  /* validate file open for reading */
            perror ("file open failed");
            return 1;
        }
    
        if (!read_pgm (fp, &pgm)) { /* validate/allocate/read pgm file */
            fputs ("error: read_pgm failed.\n", stderr);
            return 1;
        }
        if (fp != stdin)            /* close file if not stdin */
            fclose (fp);
    
        /* output success */
        printf ("successful read of '%s'\n%" PRIu32 "x%" PRIu32 " pixel values.\n",
                argc > 1 ? argv[1] : "stdin", pgm.w, pgm.h);
    
        for (uint32_t i = 0; i < pgm.h; i++)    /* free pixel storage */
            free (pgm.pixels[i]);
        free (pgm.pixels);                      /* free pointers */
    }
    
    内存使用/错误检查

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

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

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

    $ valgrind ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
    ==8086== Memcheck, a memory error detector
    ==8086== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==8086== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
    ==8086== Command: ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
    ==8086==
    successful read of 'dat/apollonian_gasket.ascii.pgm'
    600x600 pixel values.
    ==8086==
    ==8086== HEAP SUMMARY:
    ==8086==     in use at exit: 0 bytes in 0 blocks
    ==8086==   total heap usage: 604 allocs, 604 frees, 730,472 bytes allocated
    ==8086==
    ==8086== All heap blocks were freed -- no leaks are possible
    ==8086==
    ==8086== For counts of detected and suppressed errors, rerun with: -v
    ==8086== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    
    总是
    $ valgrind ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
    ==8086== Memcheck, a memory error detector
    ==8086== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
    ==8086== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
    ==8086== Command: ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
    ==8086==
    successful read of 'dat/apollonian_gasket.ascii.pgm'
    600x600 pixel values.
    ==8086==
    ==8086== HEAP SUMMARY:
    ==8086==     in use at exit: 0 bytes in 0 blocks
    ==8086==   total heap usage: 604 allocs, 604 frees, 730,472 bytes allocated
    ==8086==
    ==8086== All heap blocks were freed -- no leaks are possible
    ==8086==
    ==8086== For counts of detected and suppressed errors, rerun with: -v
    ==8086== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)