C中的open(), write(), close(), fopen(), exit(), _exit()
open() 函数
原型
#include <fcntl.h>
#include <unistd.h>
int open(const char *pathname, int flags, mode_t mode);
-
pathname:要打开的文件的路径。
-
flags:打开文件的模式(如只读、只写等)。常用的标志包括:
- O_RDONLY:只读模式。
- O_WRONLY:只写模式。
- O_RDWR:读写模式。
- O_CREAT:如果文件不存在,则创建新文件。
- O_TRUNC:如果文件已存在并且是以写入模式打开的,则截断文件为零长度。
- O_APPEND:将写入操作附加到文件末尾。
-
mode:文件权限,通常在创建文件时使用。它是一个八进制值(例如 0666),表示文件的读写权限(关于0666的解释请看这篇博客Linux中文件的权限)。
返回值
-
如果成功,返回一个非负整数(文件描述符),可以用来进行后续的读写操作。
-
如果失败,返回 -1,并且可以通过 errno(是一个全局变量,定义在
<errno.h>
头文件中,这篇博客有涉及到) 获取错误信息。
错误处理
常见的错误包括:
-
EACCES:权限被拒绝。
-
ENOENT:文件不存在。
-
EEXIST:试图创建一个已存在的文件(当使用 O_CREAT 时)。
举一个例子
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd = open("example.txt", O_RDWR, 0666);
if (fd == -1) {
perror("Error opening file");
close(fd);
return 1;
}
// 使用文件描述符 fd 进行读写操作...
close(fd); // 关闭文件
return 0;
}
输出如下:
补充:
-
<fcntl.h>
头文件主要包含用于操作文件描述符的函数和宏。 -
<unistd.h>
头文件包含与 POSIX 操作系统接口相关的一些函数和常量,主要用于处理系统调用和文件操作 -
<sys/wait.h>
头文件包含与进程状态相关的宏和函数,主要用于处理子进程的终止和状态
write()函数
原型
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
-
fd:要写入的文件描述符,通常是 open() 返回的值。
-
buf:指向要写入的数据的指针。
-
count:要写入的字节数。
返回值
-
如果成功,返回实际写入的字节数。
-
如果失败,返回 -1,并且可以通过 errno 获取错误信息。
错误处理
常见的错误包括:
-
EFAULT:buf 指针无效。
-
EBADF:文件描述符无效。
-
EIO:I/O 错误。
举一个例子
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd == -1) {
perror("Error opening file");
close(fd);
return 1;
}
const char *data = "Hello, World!\n";
ssize_t bytes_written = write(fd, data, 14);
if (bytes_written == -1) {
perror("Error writing to file");
close(fd);
return 1;
}
printf("Wrote %zd bytes to file.\n", bytes_written);
close(fd); // 关闭文件
return 0;
}
输出如下:
close()函数
close() 函数用于关闭一个已打开的文件描述符。在 C/C++ 编程中,关闭文件描述符是一个重要的步骤,以确保资源被正确释放。
原型
#include <unistd.h>
int close(int fd);
- fd:要关闭的文件描述符,通常是之前通过
open()
、socket()
、pipe()
等函数获取的值。
返回值
-
如果成功,close() 返回 0。
-
如果失败,返回 -1,并且可以通过 errno 获取错误信息。
工作原理
-
释放资源:关闭文件描述符后,操作系统会释放与该文件描述符相关的所有资源。这包括文件的缓冲区、文件锁和其他相关信息。
-
防止资源泄漏:如果不调用
close()
,程序在结束时可能会造成资源泄漏,因为操作系统可能会保留这些资源,直到程序终止。 -
同步数据:在某些情况下,关闭文件描述符会导致缓冲区中的数据被强制写入磁盘。对于写入模式打开的文件,关闭操作可能会确保所有待写入的数据都已被写入。
错误处理
常见的错误包括:
-
EBADF:文件描述符无效或未打开。
-
EIO:发生 I/O 错误。
举一个例子
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
int main() {
int fd = open("example.txt", O_WRONLY | O_CREAT, 0666);
if (fd == -1) {
perror("Error opening file");
return 1;
}
// 执行写操作...
if (close(fd) == -1) {
perror("Error closing file");
return 1;
}
printf("File closed successfully.\n");
return 0;
}
输出如下:
fopen()函数
fopen()
是 C 标准库中的一个函数,用于打开一个文件并返回一个文件指针,以便后续进行读写操作。它通常在 <stdio.h>
头文件中声明。
原型
FILE *fopen(const char *filename, const char *mode);
-
filename:要打开的文件的名称(字符串)。
-
mode:打开文件的模式,指示将如何访问该文件。常用的模式包括:
-
"r":只读模式,文件必须存在。
-
"w":只写模式,若文件存在则清空内容,不存在则创建新文件。
-
"a":附加模式,数据会被写入到文件末尾。
-
"r+":读写模式,文件必须存在。
-
"w+":读写模式,若文件存在则清空内容,不存在则创建新文件。
-
"a+":附加读写模式,可以读文件内容,写入数据到文件末尾。
-
返回值
-
返回一个指向 FILE 结构的指针,表示打开的文件。
-
如果打开失败,返回 NULL,并且可以通过 errno 获取错误信息。
举一个例子
#include <stdio.h>
int main() {
FILE *file = fopen("example.txt", "r"); // 尝试以只读模式打开文件
if (file == NULL) {
perror("Error opening file"); // 打印错误信息
fclose(file); // 关闭文件
return 1;
}
// 进行文件操作...
fclose(file); // 关闭文件
return 0;
}
输出如下:
重要注意事项
-
错误检查:在使用
fopen()
后,始终检查返回值,以确保文件成功打开。 -
关闭文件:使用
fclose()
函数关闭打开的文件,以释放系统资源。
fopen()和open()有什么不同
fopen()
更适合一般的文件处理需求,提供了较高的抽象层次,而 open()
则适合需要低级文件控制的情况。
-
语言和库
fopen()
:- 是 C 标准库中的函数,通常用于 C 和 C++ 编程。 - 提供更高层次的文件操作接口。
open()
:- 是 POSIX 系统调用,主要用于 C 语言(也可用于C++),适用于 UNIX/Linux 系统。 - 提供低级别的文件操作。
-
返回值
fopen()
:-
返回一个指向 FILE 结构的指针,用于后续的文件操作。
-
如果打开失败,返回 NULL。
open()
:-
返回一个文件描述符(非负整数),用于标识打开的文件。
-
如果打开失败,返回 -1,并且可以通过 errno 检查错误。
-
-
文件模式
fopen()
:-
使用字符串来指定打开模式(如 "r"、"w"、"a" 等)。
-
提供更方便的读写功能。
open()
:-
使用整数标志来指定打开模式(如 O_RDONLY、O_WRONLY、O_RDWR 等)。
-
适合底层文件操作,允许更多的细粒度控制。
-
-
错误处理
fopen()
:- 错误处理通常依赖于返回值(检查指针是否为 NULL)。
open()
:- 需要检查返回值并结合 errno 获取具体错误信息。
-
文件操作
fopen()
:- 提供了
fread
、fwrite
、fprintf
、fscanf
等高级文件操作函数,适合格式化输入输出。
open()
:- 主要与
read()
、write()
、lseek()
、close()
等系统调用一起使用,适合底层的文件操作。
- 提供了
exit()函数
exit() 函数用于终止当前进程并返回一个状态码。它的声明在 <stdlib.h>
头文件中。
原型
#include <stdlib.h>
void exit(int status);
- status:一个整数值,表示进程的退出状态。通常,返回 0 表示正常终止,非零值表示异常终止或错误状态。
功能
-
清理资源:
exit()
会执行所有已注册的清理函数(如通过atexit()
注册的函数),并关闭所有打开的文件流。 -
通知父进程:调用
exit()
后,父进程可以使用wait()
或waitpid()
来获取子进程的退出状态。
补充:
exit()
会导致程序终止时,操作系统自动清理资源,包括关闭所有打开的文件描述符。这意味着:
-
当进程通过
exit()
终止时,所有与该进程关联的文件描述符会被关闭。 -
不过,推荐在程序中显式地关闭文件描述符,尤其是在长时间运行的程序中,以确保资源被及时释放。
因此,虽然不需要手动关闭文件描述符,但为了良好的编程实践,建议在不再需要时显式关闭它们。
举一个例子
#include <stdio.h>
#include <stdlib.h>
int main() {
// 执行某些操作
if (/* some error condition */) {
fprintf(stderr, "Error occurred\n");
exit(1); // 以非零状态码退出,表示错误
}
printf("Program completed successfully\n");
exit(0); // 以零状态码退出,表示正常完成
}
_exit函数
_exit()
函数是一个系统调用,用于立即终止当前进程。
原型
#include <unistd.h>
void _exit(int status);
- status:一个整数值,表示进程的退出状态。通常,返回 0 表示正常终止,非零值表示异常终止或错误状态
主要特点
-
立即退出:
_exit()
会立即终止进程,不执行任何清理操作,如关闭打开的文件流或调用通过atexit()
注册的函数。 -
使用场景:
-
通常在子进程中使用,尤其是在调用
fork()
后,以避免影响父进程的状态或输出。 -
适合用于需要快速退出的场合,而不需要执行任何清理任务的场景。
-
不刷新的缓冲区:
使用
_exit()
时,任何标准输出缓冲区(如printf()
的输出)不会被刷新,这意味着未写入的内容可能会丢失。因此,在子进程中使用_exit()
可以避免对父进程输出的影响。
举一个例子
输出如下:
exit和_exit有什么不同
_exit()
和 exit()
都是用于终止进程的函数,但是有一些不同。
- 头文件
-
exit()
:声明在<stdlib.h>
中。 -
_exit()
:声明在<unistd.h>
中。
- 行为
-
exit()
:-
调用
exit()
时,会执行以下操作:-
调用已注册的清理函数(如通过
atexit()
注册的函数)。 -
刷新并关闭所有打开的标准输出流(如文件流)。
-
返回控制给操作系统,并传递一个状态码。
-
-
-
_exit()
:-
调用
_exit()
时,直接终止进程,而不执行任何清理操作。这包括:-
不调用已注册的清理函数(如通过
atexit()
注册的函数)。 -
不刷新输出缓冲区,直接关闭文件描述符。
-
-
通常用于子进程在调用
fork()
后立即退出,以避免影响父进程的输出或状态。
-
- 使用场景
-
exit()
:适用于普通程序退出场景,允许程序在退出前进行必要的清理操作(如保存数据、释放资源等)。
-
_exit()
:适用于子进程退出时,特别是在调用
fork()
后。使用_exit()
可以确保不会执行任何可能影响父进程的操作,保持进程状态的独立性。