继续我之前的评论,您在处理 Plain PGM File Format 时遇到了几个问题,这将阻止您成功读取文件。
首先,fgets(PGMcheck, sizeof(PGMcheck), inFile); 不能保证正确读取PGMcheck。 magic-number 后面可能跟着"(blanks, TABs, CRs, LFs)",所以fgets 将不仅仅是magic-number,除非它后面跟着一个'\n'——格式不保证。虽然fgets() 通常是进行面向行的输入的正确方法,但 PGM 格式不能保证按行格式化,因此您只能使用 formatted-input 函数或字符——逐个字符的方法。
(您可以使用fgets(),但这需要解析生成的缓冲区并保存超出magic-number 的缓冲区的任何部分,以作为下一次阅读的开始部分)
您已更正了使用!= 而不是strcmp 进行字符串比较的尝试,但您仍然必须将magic-number 与"P2" 进行比较才能读取Plain-PGM 格式文件(正如您最初包含的问题)继续将 magic-number 读入字符串,但使用格式化输入函数 (fscanf) 只读取直到遇到第一个空格,不管那个空白是什么。
最后,没有必要将 magic-number 存储为 plain_pgm 结构的一部分。在您尝试填充结构之前,这是您的 validate 内容。它要么是"P2",要么不是——不需要存储它。
为了便携性,在读取图像文件时最好使用 exact-width 类型进行存储。有很多好处,但最主要的是,无论是在 x86_64 还是在 TI-MSP432 芯片上运行,您的程序都能正常运行。精确宽度类型在stdint.h 中定义,来自打印和读取精确宽度类型的宏在inttypes.h 中提供。不是char,而是int8_t,而不是unsigned char,而是uint8_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;
您的分配在很大程度上是正确的,但重新排列以返回 pointer-to-pointer-to uint16_t(对于 maximum gray value 像素值足够),您可以这样做:
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 */
}
您的阅读功能需要很多帮助。首先,您通常希望在调用函数中打开并验证文件是否已打开以供读取,并将打开的FILE * 指针作为参数传递给您的读取函数,而不是文件名。 (如果文件无法在调用者中打开,则无需进行函数调用开始)。通过该更改并将指针传递给您的结构,您的读取函数可能如下所示:
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;
}
(注意:这个读取函数不考虑注释行,实现忽略注释行由您决定。您可以另外调用fscanf在读取幻数、宽度、高度和最大灰度值的每个部分之前和之间,使用类似于 " # %[^\n']" 的内容来跳过任意数量的空格并读取直到并包括下一个 '#' 字符和末尾行,或者只是在循环中使用fgetc 搜索下一个非空白字符并检查它是否是'#',如果不是使用ungetc,则清除到行尾。)
举个例子,你可以这样做:
#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 */
}
使用/输出示例
使用示例 apollonian_gasket.ascii.pgm, a 600 wide by 600 high image of an Apollonian gasket 文件作为测试文件,您会得到:
$ ./bin/read_pgm_plain dat/apollonian_gasket.ascii.pgm
successful read of 'dat/apollonian_gasket.ascii.pgm'
600x600 pixel values.
内存使用/错误检查
在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(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)
始终确认您已释放已分配的所有内存并且没有内存错误。
查看所做的更改,如果您不明白为什么做某事,请发表评论询问,我很乐意提供进一步的帮助。