OSTEP学习 | 持久化的一个普通例子
持久化的一个例子
代码
#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/types.h>
int main(int argc, char *argv[])
{
int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
assert(fd > -1);
int rc = write(fd, "hello world\n", 13);
assert(rc == 13);
close(fd);
return 0;
}
解释
好的,这个程序是一个非常典型的 C 语言文件操作的例子,它演示了如何在 Linux/Unix-like 系统上创建一个文件,并向其中写入内容。
我们来一步一步地详细解释它:
程序的总体目标
这个程序的主要目标是:
- 在
/tmp目录下创建一个名为file的新文件。 - 向这个文件中写入字符串 "hello world\n"。
- 关闭文件。
- 如果在这个过程中发生任何错误(比如无法创建文件或写入失败),程序会立即终止并报错。
逐行解释
第 1-6 行:包含头文件
1 #include <stdio.h> // 标准输入/输出库,虽然本程序没有直接使用printf/fprintf,但它是C程序常用库
2 #include <unistd.h> // 提供了对 POSIX 操作系统 API 的访问,包括 write() 和 close()
3 #include <assert.h> // 提供了 assert() 宏,用于调试和断言检查
4 #include <fcntl.h> // 提供了文件控制选项,包括 open() 函数的标志位 (O_WRONLY, O_CREAT, O_TRUNC)
5 #include <sys/types.h> // 提供了定义系统数据类型的头文件,例如 off_t(文件偏移量),虽然本例没有直接使用,但常与文件操作一起包含
#include <stdio.h>: 提供了像printf,fprintf等标准输入输出函数。虽然在这个程序中没有直接调用这些函数,但它是 C 语言程序中非常常用的一个库。#include <unistd.h>: 这个头文件包含了许多 UNIX 标准的系统调用函数,比如write()(写入文件) 和close()(关闭文件)。#include <assert.h>: 包含了assert()宏。assert(expression)会检查expression是否为真。如果expression为假(即 0),程序会终止并打印一条错误消息,通常还会显示是哪个文件哪一行代码出了问题。这在调试时非常有用。#include <fcntl.h>: 这个头文件包含了文件控制(file control)相关的函数和常量。特别是,它定义了open()函数的各种标志位(例如O_WRONLY,O_CREAT,O_TRUNC)。#include <sys/types.h>: 包含了定义各种系统数据类型(如pid_t进程ID,off_t文件偏移量)的头文件。尽管本例中没有直接使用这些类型,但在进行系统编程(尤其是文件和进程操作)时,常常会包含它。
第 8-16 行:main 函数
8 int
9 main(int argc, char *argv[])
10 {
11 int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);
12 assert(fd > -1);
13 int rc = write(fd, "hello world\n", 13);
14 assert(rc == 13);
15 close(fd);
16 return 0;
17 }
-
第 9 行:
main(int argc, char *argv[])- 这是 C 程序的入口点。
argc(argument count) 表示命令行参数的数量。argv(argument vector) 是一个字符串数组,包含了命令行参数本身。- 在这个程序中,
argc和argv并没有被使用,但它们是main函数的标准签名。
-
第 10 行:
int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);- 这是核心的文件打开操作。
open()是一个系统调用,用于打开或创建文件。/tmp/file: 这是要操作的文件路径和名称。/tmp是一个通常用于存放临时文件的目录。O_WRONLY | O_CREAT | O_TRUNC: 这是文件的打开模式或标志位,它们通过|(位或) 操作符组合在一起。O_WRONLY: 以只写模式打开文件。你不能从这个文件中读取内容。O_CREAT: 如果文件不存在,就创建它。O_TRUNC: 如果文件已经存在,并且是以可写方式打开的(O_WRONLY或O_RDWR),那么文件的内容会被截断(清空)为 0 字节。
S_IRWXU: 这是新文件(如果O_CREAT生效并创建了文件)的权限位。它定义了文件所有者的读(R)、写(W)、执行(X)权限。S_IRWXU通常表示 用户(Owner) 具有读、写、执行权限。
- 返回值
fd:open()函数成功时返回一个非负整数,这个整数被称为文件描述符 (File Descriptor)。文件描述符是操作系统用来识别你打开的文件的整数 ID。后续所有对这个文件的操作(如读、写、关闭)都需要用到这个文件描述符。如果open()失败,它会返回-1。
-
第 11 行:
assert(fd > -1);- 这一行使用
assert()来检查open()函数是否成功。 - 如果
fd的值小于或等于-1(即open失败),assert()就会触发,导致程序异常终止,并打印错误信息。这有助于发现文件打开失败的问题。
- 这一行使用
-
第 12 行:
int rc = write(fd, "hello world\n", 13);- 这是向文件写入数据的操作。
write()是一个系统调用,用于将数据写入到文件描述符引用的文件中。fd: 之前open()返回的文件描述符,表示我们要向哪个文件写入。"hello world\n": 这是一个字符串字面量,是我们想要写入文件的内容。\n是一个换行符。13: 这是要写入的字节数。字符串 "hello world\n" 包含 12 个字符和一个换行符,总共 13 个字节。- 返回值
rc:write()函数成功时返回实际写入的字节数。如果失败,它返回-1。
-
第 13 行:
assert(rc == 13);- 这一行检查
write()函数是否成功地写入了所有预期的字节(13个)。 - 如果
rc不等于 13(例如,只写入了一部分或者写入失败),assert()将终止程序。
- 这一行检查
-
第 14 行:
close(fd);- 这是关闭文件的操作。
close()是一个系统调用,用于释放由文件描述符fd关联的文件资源。- 关闭文件描述符是非常重要的,它释放了系统资源,并确保所有缓存的数据都被写入到磁盘。如果不关闭文件,可能会导致数据丢失或资源泄露。
-
第 15 行:
return 0;main函数返回0表示程序成功执行并正常退出。非零值通常表示程序执行过程中发生了错误。
程序执行后会发生什么?
当你编译并运行这个程序后:
- 它会尝试在你的
/tmp/目录下创建一个名为file的文件。 - 如果
/tmp/file已经存在,它的内容会被清空。 - 然后,字符串 "hello world" 和一个换行符会被写入到这个
file中。 - 程序随后会关闭这个文件。
你可以通过在终端中运行以下命令来验证:
# 编译程序 (假设你保存为 file_write.c)
gcc file_write.c -o file_write
# 运行程序
./file_write
# 查看文件内容
cat /tmp/file
cat /tmp/file 的输出将是:
hello world
这是一个非常基础但关键的文件 I/O (输入/输出) 示例,它展示了如何在 UNIX/Linux 系统中直接使用系统调用来操作文件,而不是使用像 fopen, fprintf, fclose 这样的 C 标准库函数(这些标准库函数通常内部也会调用 open, write, close 等系统调用)。
用c的标准库的写法
好的,如果你想使用 C 标准库中的文件操作函数来完成相同的任务,程序会变得稍微简洁一些,并且通常更易于移植(在不同操作系统上编译和运行)。
C 标准库的函数是对底层系统调用的封装,它们提供了更高级别的抽象,例如缓冲机制。
以下是使用 C 标准标准库函数实现相同功能的程序:
#include <stdio.h> // 包含标准输入/输出库,提供了 fopen, fprintf, fclose 等函数
#include <stdlib.h> // 包含标准库,提供了 exit 函数
int
main(int argc, char *argv[])
{
// 使用 fopen 打开文件
// "w" 模式表示:
// - 以写入模式打开文件
// - 如果文件不存在,则创建它
// - 如果文件已存在,则截断(清空)其内容
FILE *fp = fopen("/tmp/file", "w");
// 检查文件是否成功打开
if (fp == NULL) {
fprintf(stderr, "Error: Could not open /tmp/file for writing.\n");
// perror("fopen"); // 也可以使用 perror 打印更详细的系统错误信息
exit(EXIT_FAILURE); // 退出程序,表示失败
}
// 使用 fprintf 写入字符串到文件
// 写入 "hello world\n"
int result = fprintf(fp, "hello world\n");
// 检查写入是否成功
// fprintf 返回写入的字符数,或者在出错时返回负值 (EOF)
// "hello world\n" 共有 13 个字符
if (result < 0) { // 或者直接 if (result != 13) 也可以
fprintf(stderr, "Error: Could not write to /tmp/file.\n");
fclose(fp); // 尝试关闭文件,即使写入失败
exit(EXIT_FAILURE); // 退出程序,表示失败
}
// 关闭文件
// fclose 刷新所有缓冲区并将数据写入磁盘,然后释放文件资源
if (fclose(fp) != 0) { // fclose 成功返回 0,失败返回 EOF (非零)
fprintf(stderr, "Error: Could not close /tmp/file.\n");
exit(EXIT_FAILURE); // 退出程序,表示失败
}
return 0; // 程序成功执行
}
详细解释和与之前程序的对比:
-
头文件:
#include <stdio.h>:这是最重要的变化。C 标准库的文件 I/O 函数都在这个头文件中声明。#include <stdlib.h>:用于exit()和EXIT_FAILURE。- 不再需要
unistd.h,fcntl.h,sys/types.h,assert.h:因为我们不再直接使用底层的系统调用或assert宏(这里我们用if语句和fprintf进行错误检查,这对于初学者来说可能更直观)。
-
打开文件:
fopen()- 之前:
int fd = open("/tmp/file", O_WRONLY | O_CREAT | O_TRUNC, S_IRWXU);- 返回一个整数文件描述符 (
fd)。 - 需要手动组合各种标志位来指定模式和行为。
- 权限 (
S_IRWXU) 是作为单独的参数传递的。
- 返回一个整数文件描述符 (
- 现在:
FILE *fp = fopen("/tmp/file", "w");- 返回一个指向
FILE结构体的指针 (fp)。FILE是一个更高级的数据结构,由标准库维护,包含了文件描述符、缓冲区等信息。 - 使用一个简单的模式字符串
"w"来指定写入模式。"w":如果文件不存在,则创建。如果文件已存在,则清空其内容(截断)。- 其他常见模式:
"r"(只读),"a"(追加),"wb"(写入二进制),"rb"(读取二进制) 等。
- 权限问题:
fopen不直接接受权限参数。新创建的文件的权限将由操作系统的umask设置和文件系统的默认权限决定。如果你需要非常精确的权限控制,你可能仍然需要使用底层的open函数,或者在fopen之后使用chmod函数来修改权限。对于这个简单的例子,"w"模式通常会创建一个用户可读写的文本文件。
- 返回一个指向
- 之前:
-
错误检查:
if (fp == NULL)- 之前:
assert(fd > -1); - 现在:
fopen在失败时返回NULL。我们通过检查fp是否为NULL来判断文件是否成功打开。如果失败,打印错误信息并使用exit(EXIT_FAILURE)终止程序。这比assert更友好,因为它允许你自定义错误消息并进行更优雅的退出。
- 之前:
-
写入数据:
fprintf()- 之前:
int rc = write(fd, "hello world\n", 13);- 直接写入原始字节。
- 需要显式指定要写入的字节数。
- 现在:
int result = fprintf(fp, "hello world\n");fprintf类似于printf,但它将输出写入到指定的文件流 (fp) 中。- 它会自动处理字符串的长度,直到遇到字符串的终止符
\0(尽管这里我们传入的是一个字符串字面量,它自带\0)。 fprintf提供了格式化输出的能力,例如fprintf(fp, "The number is: %d\n", 42);。fprintf返回写入的字符数,如果发生错误则返回负值(EOF)。
- 之前:
-
关闭文件:
fclose()- 之前:
close(fd);- 直接关闭文件描述符。
- 现在:
if (fclose(fp) != 0)fclose关闭FILE指针关联的文件流。- 它会先刷新(flush)任何在缓冲区中尚未写入磁盘的数据,然后释放所有相关的资源。
fclose在成功时返回0,在失败时返回EOF(一个非零值)。通常,在简单的程序中,对fclose的返回值检查不如对fopen和fprintf那么严格,但在健壮的程序中是推荐的。
- 之前:
总结:
使用 C 标准库函数 (fopen, fprintf, fclose) 进行文件操作通常更简单,更安全,因为它处理了底层的细节,例如文件缓冲。对于大多数常见的文件操作任务,它们是首选。只有在需要更精细控制(如特定权限、非阻塞 I/O)或极致性能时,才可能直接使用底层的系统调用 (open, read, write, close)。

浙公网安备 33010602011771号