fwrite和write有哪些主要区别?

21

我目前正在编写一个 C 回调函数:

static size_t writedata(void *ptr, size_t size, size_t nmemb, void *stream){

        size_t written = fwrite(ptr, size, nmemb, (FILE)*stream);
        return written;
}

这个函数将在另一个函数中使用,该函数执行HTTP请求,检索请求并将其写入本地机器。稍后将使用writedata函数。整个操作必须是多线程的,因此我在writefwrite之间犹豫不决。有人能帮我概述一下Cwrite()fwrite()之间的区别,以便我可以选择最适合我的问题的方法吗?

3个回答

29

fwrite函数将数据写入FILE*,即(可能)缓冲的stdio流中。它是由ISO C标准指定的。此外,在POSIX系统上,fwrite在一定程度上是线程安全的

write函数是基于文件描述符的较低级别API,它在POSIX标准中有描述。它不知道缓冲。如果你想在FILE*上使用它,则需要使用fileno函数获取其文件描述符,但在尝试进行write操作之前确保手动锁定和刷新流。

除非你知道自己在做什么,否则请使用fwrite函数。


3
需要注意的一点是,您可能不应该从多个线程访问FILE*结构(其中包括但不限于调用fread/fwrite),而使用低级文件描述符通常更安全...但仍然不建议在没有同步的情况下这样做 :) - snemarch
@fred-foo 我很好奇如果我在没有锁定或刷新的情况下使用1个线程多次调用write()会发生什么。考虑到fwrite()是线程安全的,而write()不是,我想当写入到ramdrive时,write()会比fwrite()更快。但是,我的测试结果表明相反 -- 大约慢了20倍。你有什么想法吗? - Hei
write 实际上是线程安全的。性能差异可能来自缓冲:fwrite 将减少对 write 的调用次数,从而减少系统调用开销。请注意,许多实现将优化在仅有一个线程的程序中锁定 fwrite - Nate Eldredge
根据ISO C,fwrite()的一个缺点是无法获取写入失败的原因,例如基于errno的良好错误消息,如no space left on devicebroken pipe或类似的消息。 - undefined

7

一个非常明显的区别是write操作是原子性的,而fwrite不是。

https://yarchive.net/comp/linux/wakekill.html

使用:

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

int main() {
  if (fork() == 0) {
    FILE* h = fopen("file.txt", "a");
    char* line =
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n";
    for (int i = 0; i < 10000; i++) {
      if (write(fileno(h), line, strlen(line)) != strlen(line)) {
        perror("Could not append line to file");
        exit(1);
      }
    }
    if (fclose(h) != 0) {
      perror("Could not close file");
      exit(1);
    }
  } else {
    FILE* h = fopen("file.txt", "a");
    char* line =
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n";
    for (int i = 0; i < 10000; i++) {
      if (write(fileno(h), line, strlen(line)) != strlen(line)) {
        perror("Could not append line to file");
        exit(1);
      }
    }
    if (fclose(h) != 0) {
      perror("Could not close file");
      exit(1);
    }
  }
  return 0;
}

您将获得以下输出,没有交错的行:
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

使用:

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

int main() {
  if (fork() == 0) {
    FILE* h = fopen("file.txt", "a");
    char* line =
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n";
    for (int i = 0; i < 10000; i++) {
      if (fwrite(line, 1, strlen(line), h) != strlen(line)) {
        perror("Could not append line to file");
        exit(1);
      }
    }
    if (fclose(h) != 0) {
      perror("Could not close file");
      exit(1);
    }
  } else {
    FILE* h = fopen("file.txt", "a");
    char* line =
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n";
    for (int i = 0; i < 10000; i++) {
      if (fwrite(line, 1, strlen(line), h) != strlen(line)) {
        perror("Could not append line to file");
        exit(1);
      }
    }
    if (fclose(h) != 0) {
      perror("Could not close file");
      exit(1);
    }
  }
  return 0;
}

您将会得到类似下面交替出现的输出结果:
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

write()的原子性不能保证,这是由具体实现决定的。write()可能会一次性将给定的缓冲区传递给底层操作系统调用,但之后发生的事情取决于操作系统。例如,如果write()的大小足够大,你可能仍然会在写入磁盘的文件或写入终端的输出中观察到交错的输出。 - undefined
如果实施的是Linux或Unix系统,那就是这样,不代表世界上的每个操作系统。 - undefined
在Unix(和Linux)上,只有对普通文件进行O_APPEND写入时才是真实的,而对于写入管道则不是,因为管道的保证较少,可以参考这里这里(同一个问题的答案)。因此,这是被写入位置的属性,而不是write()本身的属性。此外,write()可能会返回一个短写入;在您的示例中,它被处理为错误,但通常需要“重试剩余部分”。 - undefined

0

write 函数是程序调用操作系统的函数,因此比 fwrite 慢。它也缺乏缓冲,这使得它更加,因为缓冲的哲学建议:“处理许多小文件部分比一个大文件更快。” 还要注意的是,write 不是 C 标准的一部分,因此在非 POSIX 系统中可能找不到它,而且适当的使用方式也会有所不同。您还应该知道,fwrite 和 fread 有时使用 writeread 实现(可以在 K&R 的 Unix 章节中找到简单的实现)。

另一个值得注意的事情是,readwrite 使用 文件描述符,但 fread 和 fwrite 使用文件指针,它们实际上是包含文件描述符和其他有关打开文件的信息的指针。


8
缺少缓冲区并不会使它变得更慢 - 如果你正在写入固定大小的内存块(或已经在管理自己的缓冲区),那么使用缓冲可能会带来不必要的开销。更好的做法是,如果你需要缓冲写入,使用fwrite是更可取的。 - ideasman42

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