在C语言中,如何通过fseek函数定位二进制文件的某个位置,并从该位置处开始使用fread函数读取文件内容?

8
我在想这是否是解决我的问题的最佳方式。
我知道二进制文件中特定偏移量的值,这些值包含我想要的信息......我想要偏移到这些偏移量,然后从该位置开始读取一定数量的字节。
使用谷歌之后,我得出结论,最好使用fseek()将偏移量移动到指定位置,然后使用fread()从该位置读取一定数量的字节。
我的想法正确吗?如果是,最好如何处理?即如何将这两个函数结合起来使用。
如果不正确,您有什么其他建议?
非常感谢您的帮助。
Matt
编辑:
我按照教程学习了fread()并进行了调整,代码如下:
    `#include <stdio.h>
    int main()
    {
      FILE *f;
      char buffer[11];
      if (f = fopen("comm_array2.img", "rt"))
      {
        fread(buffer, 1, 10, f);
        buffer[10] = 0;
        fclose(f);
        printf("first 10 characters of the file:\n%s\n", buffer);
      }
      return 0;
    }`

所以我使用了文件'comm_array2.img'并读取了文件中的前10个字符。

但是从我的理解来看,这是从文件开头开始的,我想从文件中的某个位置(偏移量)开始。

这样说是否更清晰了?

第二次编辑:

看起来我有些愚笨,而且似乎唯一需要的就是将fseek()放在上面代码中的fread()之前,它会定位到该位置,然后从那里读取。


2
是的。使用fseek()和fread()函数。你尝试过什么代码? - erisco
1
是的,你说得对,但你有什么问题或疑问吗? - Kerrek SB
或者使用pread。请告诉我们您想要什么以及您尝试过什么。 - pilcrow
好的,pread听起来值得一试。我正在使用Linux(确切地说是Ubuntu)。我将添加更多细节。 - user1291631
我已经查看了fseek()和fread()的手册,但是由于我的经验有限,我不确定如何将它们结合起来使用。我尝试通过谷歌搜索如何将两者结合使用,但没有结果(可能是因为我的搜索技巧不够熟练?)。因此,我想要一个简单的方法来解释如何将它们结合使用。我本打算通过试错法来解决问题,但同时也想把我的问题公开提出。如果我的问题看起来有些奇怪或者不符合规范,请见谅,这是我第一次在这个网站提问。 - user1291631
显示剩余7条评论
3个回答

3
如果您使用文件流而不是文件描述符,则可以编写类似于 POSIX pread() 系统调用的(简单)函数。您可以很容易地使用流来模拟它1。也许您应该编写一个这样的函数(其接口与我在评论中建议的略有不同):
size_t fpread(void *buffer, size_t size, size_t mitems, size_t offset, FILE *fp)
{
     if (fseek(fp, offset, SEEK_SET) != 0)
         return 0;
     return fread(buffer, size, nitems, fp);
}

这是pread()和fread()约定的一个合理折衷方案。
“函数调用的语法是什么?例如,从文件开始处的偏移量732和432处(都是从文件开始)读取,并将filestream称为f。”“由于您没有说明要读取多少字节,我假设每次读取100个字节。我假设目标变量(缓冲区)是buffer1buffer2,并且它们足够大。”
if (fpread(buffer1, 100, 1, 732, f) != 1)
    ...error reading at offset 732...
if (fpread(buffer2, 100, 1, 432, f) != 1)
    ...error reading at offset 432...

返回计数是完整的每100字节单位的数量;要么是1(获取了所有内容),要么是0(出现了问题)。
还有其他编写该代码的方法:
if (fpread(buffer1, sizeof(char), 100, 732, f) != 100)
    ...error reading at offset 732...
if (fpread(buffer2, sizeof(char), 100, 432, f) != 100)
    ...error reading at offset 432...

这会每次读取100个单独的字节;测试确保您已按预期获取全部100个字节。如果在第二个示例中捕获返回值,则可以知道您实际获取了多少数据。如果第一次读取成功,第二次失败会非常令人惊讶;在两次调用fpread()之间,其他程序(或线程)必须截断文件,但更有趣的事情已经发生过了。

1模拟不会完美; pread()调用提供了保证原子性的功能,而fseek()fread()的组合则无法提供。 但在实践中很少出现问题,除非您有多个进程或线程同时更新文件,而您正在尝试定位和读取它。


谢谢你,我正要请你进一步解释你的评论,你比我先了一步,所以谢谢。所以如果我理解正确,我会创建那个函数,并像最初使用pread一样使用它,并传递文件名(而不是文件描述符)和偏移量? - user1291631
有点像……您的评论提到了文件名,但是fread()fseek()都不使用文件名;这是fopen()的工作。fpread()函数几乎可以直接替换fseek()fread()调用。显然,如果您想让它使用文件名,您必须在函数内部fopen()并(可能)fclose()文件,并且您不会传递fp参数。您可能会将文件名作为第一个参数。您将使用"rb"打开二进制文件以进行读取;在Unix上是否存在b并不重要,但在其他平台上是重要的。 - Jonathan Leffler
啊,是的,不好意思,我的意思是传递称为“fp”的参数(也就是使用fopen()函数时通常返回的参数)。然而,从您的评论中我发现实际上我不需要传递“fp”参数? - user1291631
所展示的代码期望使用FILE *,我希望你传递文件流(file stream)。(我被你的第一条评论迷惑/误导了。)你可以编写一个函数来打开、定位、读取、关闭给定文件名的文件,然后你将传递一个文件名而不是一个文件流。但是,如果你要一次读取文件的多个部分,我不建议这样做;fopen()是一个相对较慢的函数,应该尽可能少地调用。因此,如果你原始的评论意味着文件流而不是文件名,那么是的:你可以像你想的那样使用fpread() - Jonathan Leffler
好的,非常感谢您的帮助和澄清。最后一个问题,为了我能够完全理解您创建的函数,函数调用的语法会是什么样子?例如,从偏移量732读取,然后再从偏移量432(两者都是从文件开头开始),并且文件流称为“f”。如果这个问题看起来很简单,我很抱歉,但我想确保我完全理解它。再次感谢您的时间,非常感谢。 - user1291631
非常感谢!这非常有帮助,十分感激。 - user1291631

1

这通常取决于您关心的部分之间的距离。如果您只是在关心的部分之间跳过/忽略了几个字节,通常更容易的方法是读取该数据并忽略您读取的内容,而不是使用fseek跳过它。一种典型的方法是定义一个结构体,其中包含您关心的数据和您不关心的占位符,读取结构体,然后仅使用您关心的部分:

struct whatever {
   long a;
   long ignore;
   short b;
} w;

fread(&w, 1, sizeof(w), some_file);

// use 'w.a' and 'w.b' here.

如果您关心的部分之间有很大的距离,那么使用fseek来到达重要部分的原始想法可能会更简单。


0

你的理论听起来是正确的。打开(Open)、查找(seek)、读取(read)和关闭(close)。

创建一个结构体(struct),用于存储你想要读取的数据,并传递指向该结构体分配的内存的指针给read()函数。你可能需要在结构体上使用#pragma pack(1)或类似的内容,以避免对齐问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接