C 读取二进制 stdin

27
我正在尝试构建一个指令流水线模拟器,但是很难入手。我需要从stdin读取二进制数据,并以某种方式将其存储在内存中,同时进行数据操作。我需要一次读入32位的数据块。
如何才能每次读入恰好32位的数据块呢?其次,如何将其存储以便稍后操作?
这是我目前的情况,但检查我之前读取的二进制数据块,它看起来不正确,我认为我没有按照需要精确地读取32位数据块。
char buffer[4] = { 0 }; // initialize to 0
unsigned long c = 0;
int bytesize = 4; // read in 32 bits
while (fgets(buffer, bytesize, stdin)) {
  memcpy(&c, buffer, bytesize); // copy the data to a more usable structure for bit manipulation later
  // more stuff
  buffer[0] = 0; buffer[1] = 0; buffer[2] = 0; buffer[3] = 0; // set to zero before next loop
}
fclose(stdin);

我该如何每次读取32位(它们全部都是1/0,没有换行等),并将其存储在什么变量中?char[]可以吗?

编辑:我能够读取二进制数据,但是所有的答案产生的位顺序都不正确——它们都混乱了。我怀疑这涉及到字节序以及一次只能读取和移动8位(即1个字符),这需要在Windows和C上工作…?


实际上,您可以按字节或每次4个字节读取,因为您需要将数据存储到缓冲区中。一旦得到缓冲区存储任何长度的数据,您可以逐个处理缓冲区中的4个字节。由于stdin / stdout被视为流式传输,因此逐字节是自然的。与套接字I / O不同,通常可以忽略stdin等的字节顺序,当然如果需要,可以自行进行字节顺序转换。由于没有换行符(例如'\n'),使用read或fread会更好。请将其视为流。 - Test
这些解决方案都没有以正确的顺序读取二进制文件!请帮忙,截止日期快到了。 - rlb.usa
x86平台是小端序的,你不能期望在C语言中以正确的顺序获取位。 - ntd
+1 是的,我经常在想这个问题...问题是什么来着? - Dead account
如何读取二进制位,然后将这些位分成(可能是奇怪大小的)块,例如位掩码。 - rlb.usa
8个回答

31
你需要的是freopen()函数。从man手册可知:
如果文件名(filename)为空指针,则freopen()函数将尝试更改流的模式为由mode指定的模式,就好像使用与流当前关联的文件名一样。在这种情况下,如果freopen()调用成功,则与流相关联的文件描述符不需要关闭。允许更改模式(如果有任何更改)以及在什么情况下允许更改是由具体实现定义的。
基本上你能做到的最好的就是这样:
freopen(NULL, "rb", stdin);

这将重新打开stdin作为相同的输入流,但是以二进制模式。在正常模式下,在Windows上从stdin读取将会把\r\n(Windows换行符)转换为单个字符ASCII 10。使用"rb"模式禁用此转换,以便您可以正确地读取二进制数据。

freopen()返回一个文件句柄,但它是之前的值(在我们将其设置为二进制模式之前),因此不要将其用于任何事情。之后,使用如先前提到的fread()

至于您的顾虑,您可能并未读取“32位”,但如果您使用fread(),您将读取4个char(这是您在C中能做的最好的 - char保证至少为8位,但一些历史和嵌入式平台具有16位的char(甚至有18或更糟糕的)。如果您使用fgets(),您将永远不会读取4个字节。您将至少读取3个(取决于它们是否有换行符),第四个字节将是'\ 0',因为C字符串是以空字符结束的,并且fgets()会将其读取的内容以空字符结束(像一个好的函数)。显然,这不是您想要的,因此您应该使用fread()


1
不需要尝试将freopen的返回值分配给stdin - freopen要么返回NULL,要么返回先前的stdin值(它更改指向的FILE但不更改FILE *值本身)。 - caf
啊,我没有意识到它返回了旧值。已编辑以修复。 - Chris Lutz
4
一如既往,Windows 与众不同。它不允许 path 参数为 NULL。请查看 此处 的评论。 - schieferstapel
3
很遗憾,这在Windows上行不通,原因由@schieferstapel给出:http://msdn.microsoft.com/en-us/library/wk2h68td.aspx。-1。 - j_random_hacker

22

考虑使用 SET_BINARY_MODE 宏和 setmode 函数:

#ifdef _WIN32
# include <io.h>
# include <fcntl.h>
# define SET_BINARY_MODE(handle) setmode(handle, O_BINARY)
#else
# define SET_BINARY_MODE(handle) ((void)0)
#endif

关于SET_BINARY_MODE宏的更多详细信息在这里:通过标准I/O处理二进制文件

关于setmode的更多详细信息在这里:"_setmode"


感谢您提到了_setmode。它对我实际上很有用(我正在尝试以二进制方式打开stdout)。 - Aliza
# define SET_BINARY_MODE(handle) ((void)0) 对我没用。我正在使用 Atmega 128A1,似乎无法将 stdout 设置为二进制模式。有人可以帮忙吗? - WPFGermany

3
我不得不从上面善良人们的各种评论中拼凑出答案,所以这里是一个完全可用的示例 - 仅适用于Windows,但您可能可以将Windows特定内容翻译成适用于您的平台的内容。
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "windows.h"
#include <io.h>
#include <fcntl.h>

int main()
{
    char rbuf[4096];
    char *deffile = "c:\\temp\\outvideo.bin";
    size_t r;
    char *outfilename = deffile;
    FILE *newin;

    freopen(NULL, "rb", stdin);
    _setmode(_fileno(stdin), _O_BINARY);

    FILE *f = fopen(outfilename, "w+b");
    if (f == NULL)
    {
        printf("unable to open %s\n", outfilename);
        exit(1);
    }

    for (;; )
    {
        r = fread(rbuf, 1, sizeof(rbuf), stdin);
        if (r > 0)
        {
            size_t w;
            for (size_t nleft = r; nleft > 0; )
            {
                w = fwrite(rbuf, 1, nleft, f);
                if (w == 0)
                {
                    printf("error: unable to write %d bytes to %s\n", nleft, outfilename);
                    exit(1);
                }
                nleft -= w;
                fflush(f);
            }
        }
        else
        {
            Sleep(10); // wait for more input, but not in a tight loop
        }
    }

    return 0;
}

2

对于Windows系统,这个Microsoft _setmode示例特别演示了如何将stdin更改为二进制模式:

// crt_setmode.c
// This program uses _setmode to change
// stdin from text mode to binary mode.

#include <stdio.h>
#include <fcntl.h>
#include <io.h>

int main( void )
{
   int result;

   // Set "stdin" to have binary mode:
   result = _setmode( _fileno( stdin ), _O_BINARY );
   if( result == -1 )
      perror( "Cannot set mode" );
   else
      printf( "'stdin' successfully changed to binary mode\n" );
}

0

fgets()在这里完全不适用。它针对的是以换行符终止的可读ASCII文本,而不是二进制数据,并且无法获取您所需的内容。

最近我使用read()调用完全实现了您想要的功能。除非您的程序已经显式关闭了stdin,否则对于第一个参数(文件描述符),您可以使用stdin的常量值0。或者,如果您在POSIX系统上(Linux、Mac OS X或其他现代Unix变体),您可以使用STDIN_FILENO。


1
当然,这对于非POSIX系统是行不通的,因为在POSIX系统上,文件句柄的二进制读取和文本读取之间没有区别,这就失去了意义。 - Chris Lutz

-1
我不知道你正在运行什么操作系统,但通常你不能“以二进制方式打开stdin”。你可以尝试一些类似的方法。
int fd = fdreopen (fileno (stdin), outfname, O_RDONLY | OPEN_O_BINARY);

尝试强制它。然后使用

uint32_t opcode;
read(fd, &opcode, sizeof (opcode));

但是我自己实际上没有尝试过。:)

您不必执行fdreopen()。 stdin始终处于打开状态 - 请查看我的答案。 - Bob Murphy
1
WTF??我知道它是开放的,那是你可以FEED到fdreopen中的唯一类型的文件。但它有错误的MODE。stdin被打开为TEXT文件。这个人想要读取原始二进制数据,而文本不会起作用。 - Paul Hsieh

-1

fread() 最适合读取二进制数据。

如果您计划按字节处理它们,那么 char 数组是可以的。


10
这不是答案。stdin是一个带缓冲区的输入流,而fread()函数将读取缓冲数据,在Windows系统中,它会以文本模式进行读取并将\r\n转换为单个字符,这对于二进制数据来说是不好的。 - Chris Lutz

-2

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