C 为什么文件大小与读取1字节文件的次数不同?

C 为什么文件大小与读取1字节文件的次数不同?,c,file,io,C,File,Io,我正在学习C的文件处理,我遇到了一些问题。 我写的代码如下: # include <stdio.h> # include <stdlib.h> int main(void) { FILE * file; errno_t err = fopen_s(&file,"f.txt","r"); fseek(file, 0, SEEK_END); int size = ftell(file); fseek(file, 0, SEE

我正在学习C的文件处理,我遇到了一些问题。 我写的代码如下:

# include <stdio.h>
# include <stdlib.h>

int main(void)
{
    FILE * file;
    errno_t err = fopen_s(&file,"f.txt","r");

    fseek(file, 0, SEEK_END);
    int size = ftell(file);
    fseek(file, 0, SEEK_SET);

    char *tmp;
    tmp = malloc(size);
    printf("%d\n", size);

    for (int i = 0; !feof(file); i++)
    {
        fread(tmp + i, 1, 1, file);
        size = i + 1;
    }

    printf("%d\n", size);

    fclose(file);
    free(tmp);
    return 0;
#包括
#包括
内部主(空)
{
文件*文件;
errno_t err=fopen_s(&file,“f.txt”,“r”);
fseek(文件,0,SEEK_END);
int size=ftell(文件);
fseek(文件,0,搜索集);
char*tmp;
tmp=malloc(尺寸);
printf(“%d\n”,大小);
对于(int i=0;!feof(文件);i++)
{
fread(tmp+i,1,1,文件);
尺寸=i+1;
}
printf(“%d\n”,大小);
fclose(文件);
免费(tmp);
返回0;

但是,大小的输出不一样(1:78,2:76),这背后的原因是什么?

我怀疑您使用的是Microsoft Windows。在Microsoft的C/C++实现中,二进制流和文本流是不同的。如果您使用
“rb”打开文件
传递给
fopen_s
作为其第三个参数,文件将以二进制流打开,
fread
将返回文件中的实际字节

因为您使用
“r”打开了文件
,它是作为文本流打开的。在此模式下,在读取和写入文件时会执行一些处理。值得注意的是,Windows在每行末尾使用两个字符,一个新行
'\n'
和一个回车符
'\r'
。当作为文本流读取文件时,这两个字符会减少为一个
'\n'
。相反,在写入文本流时,写入
'\n'
会在文件中生成
'\n'
'\r'

对于二进制流,
ftell
给出文件开头的字节数。对于文本流,C标准仅规定
ftell
可用于使用
fseek
重置流位置-不一定是字节数(在实际文件中)或字符数(出现在流中)C实现可能会实现
ftell
,这样它就给出了文件开头的字节数(这就是您看到的78个字节),但是,即使是这样,您也无法轻松地使用它来知道文本流中有多少个字符

此外,正如其他人在评论中指出的,此代码是错误的:

for (int i = 0; !feof(file); i++)
    {
        fread(tmp + i, 1, 1, file);
        size = i + 1;
    }
标准库例程不知道文件的结尾已经到达,直到您尝试读取并且由于到达文件的结尾而失败。例如,如果文件中有一个字符,并且您读取了它,
feof(file)
仍然为false未遇到文件结尾。直到您尝试读取第二个字符并且
fread
失败,
feof(文件)
才变为true

因此,上面的循环最终将
size
设置为比读取的字符数多一个,因为在文件迭代开始时,
!feof(file)
为true,因此尝试了
fread
,但失败了,然后将
size
设置为
i+1
,即使没有读取任何字节

因为这就是
feof
的工作方式,所以您不能使用它来控制这样的循环。相反,您应该编写循环,以便它测试
fread
的结果,如果它无法读取任何字符,代码将退出循环。该代码可能类似于:

int i = 0;
do
{
    size_t result = fread(tmp + i, 1, 1, file);
    if (result == 0)
        break;
    i++;
}
size = i;
(请注意,如果使用
fread
一次读取多个字节,则需要额外的代码来处理读取的字节数介于零和请求的字节数之间的情况。)


修复该循环后,您应该会看到流中报告的字符数为75。很可能,您的文件f.txt包含三行文本,总共72个字符,不包括行尾。当作为文本流读取时,有三个
'\n'
字符,因此总数为75。当作为二进制流读取时,有三个
'\n'
字符和三个
'\r'
字符,因此总数为78个。

我怀疑您使用的是Microsoft Windows。在Microsoft的C/C++实现中,二进制流和文本流是不同的。如果您使用
rb打开文件
传递给
fopen_s
作为其第三个参数,文件将以二进制流打开,
fread
将返回文件中的实际字节

因为您使用
“r”打开了文件
,它是作为文本流打开的。在此模式下,在读取和写入文件时会执行一些处理。值得注意的是,Windows在每行末尾使用两个字符,一个新行
'\n'
和一个回车符
'\r'
。当作为文本流读取文件时,这两个字符会减少为一个
'\n'
。相反,在写入文本流时,写入
'\n'
会在文件中生成
'\n'
'\r'

对于二进制流,
ftell
给出文件开头的字节数。对于文本流,C标准仅规定
ftell
可用于使用
fseek
重置流位置-不一定是字节数(在实际文件中)或字符数(出现在流中)C实现可能会实现
ftell
,这样它就给出了文件开头的字节数(这就是您看到的78个字节),但是,即使是这样,您也无法轻松地使用它来知道文本流中有多少个字符

此外,正如其他人在评论中指出的,此代码是错误的:

for (int i = 0; !feof(file); i++)
    {
        fread(tmp + i, 1, 1, file);
        size = i + 1;
    }
标准库例程不知道文件的结尾已经到达,直到您尝试读取并且由于到达了文件的结尾而失败。例如,如果文件中有一个字符,并且您读取了它,
feof(file)
仍然为false,则文件的结尾没有b