C语言IO

C语言I/O

ANSI & POSIX

1. 定义与制定机构

  • ANSI

    • 全称:American National Standards Institute(美国国家标准协会)。
    • 职责:制定广泛的国家标准,涵盖工业、技术、安全等多个领域,如编程语言(ANSI C/C++)、字符编码(如ANSI转义码)、文件格式等。
    • 示例标准:ANSI C(C89/C90)、ANSI C++。
  • POSIX

    • 全称:Portable Operating System Interface(可移植操作系统接口)。
    • 制定机构:由IEEE(电气电子工程师协会)制定,后成为国际标准(ISO/IEC 9945)。
    • 目标:统一Unix-like操作系统的API,确保应用程序在不同系统(如Linux、macOS)间的可移植性。

2. 核心区别

维度ANSIPOSIX
适用范围编程语言、工业标准等广泛领域。操作系统接口和工具(如文件操作、进程管理)。
核心内容定义语言语法、基础库(如printf)。扩展系统级API(如fork(), pthread)。
依赖关系独立标准,如ANSI C是编程基础。依赖ANSI C实现接口,扩展系统功能。
应用场景确保代码在编译器间的兼容性。确保程序在Unix系统间的可移植性。

3. 功能示例

  • ANSI C

    • 标准库:stdio.hfopen, printf)、stdlib.hmalloc)。
    • 不涉及操作系统特性,如多线程或进程控制。
  • POSIX

    • 系统调用:open(), read(), fork(), exec()
    • 多线程支持:pthread库(pthread_create)。
    • Shell工具规范:grep, awk的行为标准化。

4. 实际应用中的关联

  • 互补性
    POSIX基于ANSI C实现系统接口。例如:
    // ANSI C的标准文件操作
    FILE *file = fopen("test.txt", "r");
    
    // POSIX的低级文件操作(依赖ANSI C但扩展功能)
    int fd = open("test.txt", O_RDONLY);
    

文件IO标准IO的异同

1. 核心定义

  • 文件I/O(低级I/O)

    • 直接调用操作系统提供的原生接口(如POSIX的open()read()write())。
    • 无缓冲或用户自定义缓冲,每次操作直接与内核交互。
    • 适用于需要精细控制底层行为的场景(如设备驱动、实时系统)。
  • 标准I/O(高级I/O)

    • 基于ANSI C标准库(如fopen()fread()fprintf())。
    • 自带缓冲区管理(全缓冲/行缓冲/无缓冲),减少系统调用次数。
    • 提供格式化输入输出、跨平台兼容性等高级功能。

2. 主要差异

维度文件I/O(系统级)标准I/O(标准库)
接口层级底层系统调用(如POSIX API)高层库函数(封装系统调用)
缓冲机制无缓冲(或需用户手动管理)自动缓冲(默认全缓冲,终端设备行缓冲)
性能频繁系统调用,效率较低缓冲减少系统调用,效率更高
可移植性依赖操作系统(如Windows无原生POSIX支持)跨平台(ANSI C标准)
功能扩展支持更底层操作(如O_DIRECT绕过页缓存)提供格式化输入输出(printf)、类型安全
错误处理通过errno和返回值判断错误通过返回值(如NULLEOF)和ferror()

3. 典型场景对比

(1) 文件操作示例
// 文件I/O(POSIX)
int fd = open("data.txt", O_RDWR | O_CREAT, 0644);
char buf[1024];
ssize_t n = read(fd, buf, sizeof(buf));
write(fd, "Hello", 5);
close(fd);

// 标准I/O(ANSI C)
FILE *fp = fopen("data.txt", "w+");
char buf[1024];
fread(buf, 1, sizeof(buf), fp);
fprintf(fp, "Hello");
fclose(fp);
(2) 缓冲机制差异
  • 标准I/O

    • 写入数据时,默认先存入内存缓冲区,满后调用write()批量写入。
    • 可通过fflush()强制刷新缓冲区。
    • 风险:程序崩溃时,未刷新的数据可能丢失。
  • 文件I/O

    • 每次write()直接触发系统调用,数据立即写入内核(不保证落盘,需fsync())。
    • 适合实时性要求高的场景(如日志记录)。
(3) 格式化输出能力
  • 标准I/O支持复杂格式化:
    fprintf(fp, "Value: %d, String: %s", 42, "text"); // 直接处理类型转换
    
  • 文件I/O需手动格式化:
    char str[100];
    sprintf(str, "Value: %d", 42); // 先格式化到内存
    write(fd, str, strlen(str));
    

4. 共同点

  1. 目标一致:均用于数据的输入输出操作。
  2. 底层依赖:标准I/O最终通过文件I/O实现(如fwrite()内部调用write())。
  3. 文件描述符关联:可通过fileno(fp)获取标准I/O的底层文件描述符。

标准IO

1. 文件流指针的本质

  • 在 C 语言中,文件流指针(通常称为 FILE 指针)是操作文件的核心工具,它通过标准库 <stdio.h> 提供了一系列函数来实现文件的读写和管理。以下是关键概念和使用方法的详细说明:
  • FILE 结构体:文件流指针是一个指向 FILE 结构体的指针,该结构体封装了文件的元数据(如文件位置、缓冲区状态等)。
  • 不透明性FILE 的内部结构是平台相关的,开发者无需直接操作其成员,应通过标准库函数间接管理。

2. 文件操作流程

(1) 打开文件:fopen
FILE *fopen(const char *filename, const char *mode);
  • 功能:打开文件并返回关联的 FILE 指针。
  • 模式
    • "r":只读(文件必须存在)。
    • "w":只写(创建新文件或清空已有文件)。
    • "a":追加写(文件不存在则创建)。
    • "r+":读写(文件必须存在)。
    • "w+":读写(创建新文件或清空已有文件)。
    • "b":二进制模式(如 "rb", "wb+"),需在模式字符串中添加。
(2) 关闭文件:fclose
int fclose(FILE *stream);
  • 功能:关闭文件流,释放资源。
  • 返回值:成功返回 0,失败返回 EOF

3. 读写操作

(1) 文本读写
int fprintf(FILE *stream, const char *format, ...);  // 格式化写入
int fscanf(FILE *stream, const char *format, ...);   // 格式化读取
char *fgets(char *str, int n, FILE *stream);         // 读取一行文本
int fputs(const char *str, FILE *stream);            // 写入字符串
(2) 二进制读写
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
  • 参数
    • ptr:数据缓冲区。
    • size:单个元素的大小(如 sizeof(int))。
    • count:元素数量。
    • stream:文件指针。
  • 返回值:实际读/写的元素数量。

4. 文件定位

int fseek(FILE *stream, long offset, int origin);  // 移动文件指针
long ftell(FILE *stream);                          // 获取当前指针位置
void rewind(FILE *stream);                         // 重置指针到文件开头
  • 参数说明
  • stream:指向 FILE 结构体的指针(文件流)。
  • offset:偏移量(字节数),可以是正数(向后移动)或负数(向前移动)。
  • origin:基准位置,决定从何处开始计算偏移。取以下值之一:
    • SEEK_SET:文件开头。
    • SEEK_CUR:当前位置。
    • SEEK_END:文件末尾。
典型应用场景
(1) 随机访问文件内容
// 读取文件的第 10 个字节
fseek(fp, 9, SEEK_SET);  // 索引从 0 开始
char c = fgetc(fp);
(2) 跳过固定长度的文件头
// 跳过文件头部 1024 字节
fseek(fp, 1024, SEEK_SET);
(3) 动态定位读写位置
// 在文件中追加数据后回到原位置
long pos = ftell(fp);     // 记录当前位置
fseek(fp, 0, SEEK_END);  // 移动到文件末尾
fputs("Appended data", fp);
fseek(fp, pos, SEEK_SET); // 回到原位置

大文件限制
  • offset 的类型为 long,在 32 位系统中可能无法处理超过 2GB 的文件。
  • 解决方案:
    • 使用 fseeko(POSIX 标准,offset 类型为 off_t)。
    • 使用 _fseeki64(Windows 特有,支持 64 位偏移)。
错误处理
  • 始终检查 fseek 的返回值:
    if (fseek(fp, offset, origin) != 0) {
        perror("fseek failed");
        exit(EXIT_FAILURE);
    }
    
ftell 配合使用
  • ftell 返回当前指针位置(相对于文件开头的字节偏移量):
    long pos = ftell(fp);  // 获取当前位置
    fseek(fp, pos, SEEK_SET);  // 回到该位置
    

4.1 fgetposfsetpos

对于更复杂的文件位置操作(尤其是跨平台或超大文件),建议使用 fgetposfsetpos

int fgetpos(FILE *stream, fpos_t *pos);  // 获取位置
int fsetpos(FILE *stream, const fpos_t *pos); // 设置位置
  • 优势
    • fpos_t 类型可存储任意文件位置(包括多字节编码文件的位置)。
    • 适用于非二进制模式下的随机访问。
  • 示例
    fpos_t pos;
    if (fgetpos(fp, &pos) != 0) {
        perror("fgetpos failed");
        return;
    }
    
    if (fsetpos(fp, &pos) != 0) {
        perror("fsetpos failed");
        return;
    }
    

5. 错误处理

  • 检查文件是否成功打开
    FILE *fp = fopen("file.txt", "r");
    if (fp == NULL) {
        perror("Error opening file");
        exit(EXIT_FAILURE);
    }
    
  • 检查读写错误
    if (ferror(fp)) {
        printf("Read/Write error occurred.\n");
    }
    
  • 清除错误标志
    clearerr(fp);
    

6. 缓冲与同步

  • 手动刷新缓冲区
    fflush(fp);  // 将缓冲区数据立即写入文件
    
  • 缓冲模式设置
    setvbuf(fp, buffer, _IOFBF, 1024);  // 全缓冲
    setvbuf(fp, buffer, _IOLBF, 1024);  // 行缓冲
    setvbuf(fp, buffer, _IONBF, 1024);  // 无缓冲
    

7. 注意事项

  1. 资源泄漏:务必使用 fclose 关闭文件,避免文件句柄泄漏。
  2. 二进制模式:在 Windows 中,文本模式会自动转换换行符(\r\n\n),处理非文本文件时应使用二进制模式。
  3. 跨平台问题:路径分隔符在 Windows 中是 \,在 Linux/macOS 中是 /,建议使用 /\\(转义)。
  4. 文件权限:确保程序对目标文件有读写权限。

文件IO

1. 打开与关闭文件

(1) open - 打开文件
int open(const char *pathname, int flags, mode_t mode);
  • 功能:打开或创建文件,返回文件描述符(int 类型)。
  • 参数
    • pathname:文件路径。

    • flags:打开模式(必选 O_RDONLY, O_WRONLY, O_RDWR 之一,可选 O_CREAT, O_APPEND, O_TRUNC 等)。

    • 在这里插入图片描述

    • mode:文件权限(mode & ~umask)(仅在 O_CREAT 时有效,如 0644)。

  • 示例
    int fd = open("data.txt", O_RDWR | O_CREAT, 0644);
    if (fd == -1) {
        perror("open failed");
        exit(EXIT_FAILURE);
    }
    
(2) close - 关闭文件
int close(int fd);
  • 功能:关闭文件描述符,释放资源。
  • 返回值:成功返回 0,失败返回 -1

2. 读写操作

(1) read - 读取数据
ssize_t read(int fd, void *buf, size_t count);
  • 功能:从文件描述符 fd 读取 count 字节到 buf
  • 返回值
    • 成功:返回实际读取的字节数(可能小于 count)。
    • 失败:返回 -1,设置 errno
    • 文件末尾:返回 0
(2) write - 写入数据
ssize_t write(int fd, const void *buf, size_t count);
  • 功能:将 buf 中的 count 字节写入文件描述符 fd
  • 返回值:实际写入的字节数(可能小于 count)。
示例
char buffer[1024];
ssize_t bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {
    perror("read failed");
}

char *data = "Hello, POSIX I/O";
ssize_t bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
    perror("write failed");
}

3. 文件定位

lseek - 移动文件指针
off_t lseek(int fd, off_t offset, int whence);
  • 功能:设置文件偏移量(类似 fseek)。
  • 参数
    • whence:基准位置(SEEK_SET, SEEK_CUR, SEEK_END)。
  • 返回值:新的文件偏移量(失败返回 -1)。
  • 示例
    off_t pos = lseek(fd, 0, SEEK_END);  // 获取文件大小
    

4. 文件控制

fcntl - 控制文件属性
int fcntl(int fd, int cmd, ... /* arg */);
  • 功能:修改文件描述符属性(如获取/设置文件状态标志、文件锁等)。
  • 常用命令
    • F_GETFL:获取文件状态标志。
    • F_SETFL:设置文件状态标志(如 O_NONBLOCK 非阻塞模式)。
  • 示例
    int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK); // 设置为非阻塞模式
    

5. 文件同步

(1) fsync - 强制同步到磁盘
int fsync(int fd);
  • 功能:将文件数据及元数据(如修改时间)刷入磁盘。
  • 适用场景:确保数据持久化(如数据库操作)。
(2) fdatasync - 仅同步数据
int fdatasync(int fd);
  • 功能:仅同步数据(不强制更新元数据),性能优于 fsync

6. 文件内存映射

mmap - 内存映射文件
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  • 功能:将文件映射到内存,直接通过指针访问文件内容。
  • 优势:避免频繁的 read/write 调用,适合大文件随机访问。
  • 示例
    int fd = open("data.bin", O_RDONLY);
    void *mapped = mmap(NULL, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd); // 映射后可以立即关闭文件描述符
    // 使用 mapped 指针访问文件内容...
    munmap(mapped, file_size); // 解除映射
    

7. 原子操作

1. pread - 原子性读取
#include <unistd.h>

ssize_t pread(int fd, void *buf, size_t count, off_t offset);
  • 参数
    • fd:文件描述符。
    • buf:存储读取数据的缓冲区。
    • count:要读取的字节数。
    • offset:读取的起始偏移量(相对于文件开头)。
  • 返回值
    • 成功:返回实际读取的字节数。
    • 失败:返回 -1,并设置 errno
    • 文件末尾:返回 0
2. pwrite - 原子性写入
#include <unistd.h>

ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
  • 参数:同上,但 buf 为待写入数据的缓冲区。
  • 返回值
    • 成功:返回实际写入的字节数。
    • 失败:返回 -1,并设置 errno

3.核心特性
1. 原子性操作
  • preadpwrite 的读写操作是原子的,即:
    • 不会与其他线程/进程的 pread/pwrite 操作发生偏移量冲突
    • 不受文件指针位置影响,操作完成后文件指针仍保持原位置。
  • 对比普通 read/write
    // 非原子操作:需要 lseek + read/write
    lseek(fd, offset, SEEK_SET);
    read(fd, buf, count);  // 其他线程可能在此间修改文件指针
    
2. 无状态性
  • 操作完全依赖传入的 offset,与文件描述符的当前偏移量无关。
  • 适用于需要多次随机访问的场景,无需频繁调用 lseek
3. 线程安全
  • 多线程环境下,无需加锁即可安全使用(前提是操作不重叠)。

4.使用示例
1. 读取文件的特定块
int fd = open("data.bin", O_RDONLY);
if (fd == -1) {
    perror("open failed");
    exit(EXIT_FAILURE);
}

char buffer[1024];
ssize_t bytes_read = pread(fd, buffer, sizeof(buffer), 4096); // 从第4096字节处读取1KB
if (bytes_read == -1) {
    perror("pread failed");
}
close(fd);
2. 并发写入不同偏移量
// 线程1:写入文件头部
void *thread1(void *arg) {
    int fd = *(int *)arg;
    const char *data = "HEADER";
    pwrite(fd, data, strlen(data), 0); // 写入偏移量0处
    return NULL;
}

// 线程2:写入文件尾部
void *thread2(void *arg) {
    int fd = *(int *)arg;
    const char *data = "FOOTER";
    off_t end = lseek(fd, 0, SEEK_END); // 获取文件末尾偏移量
    pwrite(fd, data, strlen(data), end); // 写入末尾
    return NULL;
}

4. 适用场景
1. 多线程/多进程并发访问
  • 多个线程/进程同时读写文件的不同区域,无需锁保护文件指针。
2. 随机访问大文件
  • 如数据库索引、日志文件解析等,直接跳转到指定位置读写。
3. 避免竞争条件
  • 确保读写操作的偏移量精确性,防止其他操作修改文件指针。

8. 文件锁

flockfcntl - 文件锁定
// 使用 fcntl 实现文件锁
struct flock lock = {
    .l_type = F_WRLCK,  // 写锁
    .l_whence = SEEK_SET,
    .l_start = 0,
    .l_len = 0,         // 锁定到文件末尾
};
fcntl(fd, F_SETLK, &lock); // 非阻塞加锁
fcntl(fd, F_SETLKW, &lock); // 阻塞加锁

9.注意事项

  1. 错误处理:所有 POSIX 函数在失败时返回 -1,需检查 errno(通过 perrorstrerror)。
  2. 文件描述符限制:进程可打开的文件描述符数量有限(通过 ulimit -n 查看)。
  3. 大文件支持:使用 lseek64O_LARGEFILE 标志处理超过 2GB 的文件。
  4. 原子性O_APPEND 模式保证多进程追加写的原子性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值