提问人:fulcus 提问时间:8/17/2019 最后编辑:chqrliefulcus 更新时间:8/18/2019 访问量:198
fscanf() != EOF 作为循环的退出条件,在错误的时间退出
fscanf() != EOF as exit condition of loop exits at the wrong time
问:
我从这个文件中获取输入,我知道它包含一系列类型的元素:
typedef struct{
char artist[50];
char title[50];
int num; //numero traccia
int minutes;
int seconds;
} track;
但是当我用循环获取我的输入时,程序认为它紧跟在第一个结构之后,即使我发现其中有 9 个元素,然后是一堆垃圾。因此,在最坏的情况下,它应该在 9 个轨道之后打印一堆垃圾,而不是只读取第一个,然后认为它击中了.EOF
EOF
起初我有,但我读到它真的很糟糕的代码,所以我尝试了每个人似乎都建议的返回,但它仍然不起作用。while(!feof(fp))
fscanf
#include <stdio.h>
#define N 15
typedef struct {
char artist[50];
char title[50];
int num;
int minutes;
int seconds;
} track;
int main() {
FILE *fp = fopen("/path/album.bin", "rb+");
if (fp == NULL) {
printf("Error opening file\n");
return -1;
}
int i = 0;
track song[N]; //array of structs
while (fscanf(fp,"%[^\n] %[^\n] %d %d %d",
song[i].artist, song[i].title, &song[i].num,
&song[i].minutes, &song[i].seconds) != EOF) {
printf("Artist: %s\nTitle: %s\nNum: %d\nLength: %d:%d\n\n",
song[i].artist, song[i].title, song[i].num,
song[i].minutes, song[i].seconds);
++i;
}
fclose(fp);
return 0;
}
输出为:
Artist: Frank Zappa
Title: Inca Roads
Num: 1
Length: 8:45
而它实际上应该打印相同格式的其他 8 首曲目。
答:
既然你没有问一个具体的问题,我会尽量让你朝着正确的方向前进。
您链接的文件是一个二进制文件,其中包含 的记录。所以你不能用 .您可以用作标准替代方案;请阅读其文档,了解如何使用它以及返回的内容。如果文件在一台计算机上写入并在另一台计算机上读取,则考虑字节序。struct track
fscanf()
fread()
评论中提到了另一个明显的问题:函数族返回成功扫描的字段数。如果出现错误,它将返回一个低于预期的值。scanf()
int
首先,您显然尝试读取二进制数据,请参阅 busybee 对此问题的回答(fscanf 不适用于二进制数据!
其次(现在假设数据存储在文本文件中),fscanf
不会返回文件流状态,而是返回成功扫描的参数数,因此您应该检查格式参数的数量是否相等,即 就你而言。5
如果您还想检查是否已到达文件末尾,则可以使用带有指针的 feof
作为参数。但请注意,阅读也可能因其他原因而失败。然后,您的代码可能如下所示:FILE
while(fscanf(...) == 5) { /* ... */ }
if(feof(fp))
{
// you read entire file
}
else
{
// something went wrong
}
也许只剩下空格了,所以你可能需要在最终检查之前注意是否已经到达文件末尾。
你不能用来读取二进制文件,而你的文件是二进制文件。fscanf()
了解文件的二进制格式后,您可以复制结构并使用 .fread()
分析文件,我们可以看到结构没有填充,因此使用打包属性,我们可以强制编译器进行相同的填充。
然后,代码将如下所示:
#include <stdio.h>
#include<stdint.h> //Include this header to access standard integer types as int32_t
#define N 15
#ifdef (__GNUCC__)
#define PACK __attribute__((packed))
#else
#define PACK
#pragma pack(1) //Use this if MS compiler or compatible
#endif
typedef struct
{
char artist[50];
char title[50];
int32_t num; //Note use of int32_t to force the use of 4bytes ints
int32_t minutes;
int32_t seconds;
} track PACK;
int main(int argc, char *argv[])
{
FILE *fp = fopen("/path/album.bin","rb+");
if(fp == NULL)
{
printf("Error opening file\n");
return -1;
}
int i = 0;
track song[N]; //array of structs
// Note that fread returns 1 if a complete structure has been read
// If the file contains less bytes of the size of the structure,
// fread() will return 0 ending the input.
while(fread( &song[i], sizeof(track), 1, fp))
{
printf("Artist: %s\nTitle: %s\nNum: %d\nLength:%d:%d\n\n",
song[i].artist, song[i].title, song[i].num,
song[i].minutes, song[i].seconds);
++i;
}
fclose(fp);
return 0;
}
编辑:请注意,使用该类型强制使用 32 位(4 字节)整数。这对于在标准 int
类型为 <> 32 位的系统上保持一致的结构布局是绝对强制性的。int32_t
观察文件的布局,我们可以发现一个类似于所提供结构的重复模式:
Offset _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ __________ _
0000: |c|h|a|r| |a|r|t|i|s|t|[|5|0|]| | | / .... / | | 50bytes
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+----------+-+
0032: |c|h|a|r| |t|i|t|l|e|[|5|0|]| | | | / .... / | | 50bytes
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+----------+-+
0064: | | | | | int num = 32bits = 4bytes
+-+-+-+-+
0068: | | | | | int minutes = 32bits = 4bytes
+-+-+-+-+
006C: | | | | | int seconds = 32bits = 4bytes
+-+-+-+-+
structure size=50+50+4+4+4=112 bytes
通过观察它,我们看到这些字段没有被任何填充交错(至少考虑到 的标准大小。int32_t
另一方面,在没有打包属性的情况下对结构进行编码将使编译器可以自由地最终添加依赖于实现的填充,如果内存布局与文件布局不一致,可能会导致失败。
绕过此类问题的另一种可能性是序列化输入,在这种情况下,您将使用其长度对每个字段进行二进制读取。
关于使用,再多说一句(在此处查看更多信息 http://www.cplusplus.com/reference/cstdio/fread/)。设置 和 意味着 fread()
必须为每个元素读取至少大小
的字节,并且只能读取一个元素。如果文件在可以读取该字节数之前到达末尾,则该函数将返回 0 个元素,即 false。如果可以读取完整元素,则该函数返回 1 个元素 read for true。size_t fread ( void * ptr, size_t size, size_t count, FILE * stream )
count=1
size=sizeof(track)
最后,要注意 endianess。在您的文件中,如果您的机器是结构成员的 big-endian 交换字节,则它是 little-endian 格式。int
评论
int
int32_t
这里有趣的是,程序只发出一条记录:这是因为(二进制)文件中没有换行符,因此尝试使用整个文件内容来处理格式的第一个指令,第一次被调用。%[^\n]
fscanf
不,有趣的是,尽管读取出了可怕的错误,但它发出的一条记录似乎是明智的。也就是说,由于前 50 个字符内没有换行符,因此扫描第一个字段会超出数组的边界,从而产生未定义的行为。song[0].artist
沉迷于对该 UB 表现形式的一些猜测,结果似乎是好像程序只是将文件的所有字节写入数组的表示中(该数组足够长以容纳它们),并且结构的布局恰好与文件的二进制格式相匹配(这并不奇怪)。结果,第一首曲目的所有字段似乎都已正确填充。song
track
此外,轨道结构的细节使得它不太可能使用任何尾部(或内部)填充进行布局,因此我们甚至可以推测,在循环之外打印一些其他轨道也可能会产生预期的数据。
有趣的是,在一个系统上,它与写入文件的系统具有相同的字节序和相同的结构布局约定,并且假设结构确实是没有任何填充的布局,读取数据的最简单正确方法模拟了上述可能的 UB 特征:fscanf
// no loop needed if all our assumptions are satisfied
size_t num_songs = fread(song, sizeof(song[0]), N, fp);
这只是将整个文件(直到轨道)直接读取到数组的表示中。然后,您可以循环打印结果:N
for (int i = 0; i < num_songs; i++) {
printf("Artist: %s\nTitle: %s\nNum: %d\nLength: %d:%d\n\n",
song[i].artist, song[i].title, song[i].num, song[i].minutes,
song[i].seconds);
}
这恰好对我有用。
评论
song[0]
sizeof
sizeof (int)
sizeof
上一个:二进制文件中的信息未正确显示
评论
fscanf
除了 !EOF
fscanf
fscanf
fgetc()