fread、fwrite、fdopen

本文介绍了作者在工作中遇到的Linux串口编程问题及解决方法,包括串口参数配置错误导致无法发送指令、如何使用fdopen函数实现文件描述符与文件指针间的转换,以及fwrite和fread函数的应用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.今晚学习了Github的入门,公司需要用到Github,正好自己也想学习这方面。里面应该有很多的开源项目,等熟悉以后,可以在里面找找与自己课题有关的工程。学习网址就是Github的官方网址:
https://guides.github.com/activities/hello-world/
2.总结一下今天在公司出现的问题:
(1)打开串口的时候,由于参数设置为:
fd = open( port, O_RDONLY|O_NOCTTY);
其中O_RDONLY 是只能读的意思,所以在我写的SEND()函数中,无法对串口发送报文指令,所以首先将该指令修改为O_RDWR,这样就允许我们可以对串口进行读写操作。port是我们的串口,linux中,通常为ttyUSB0
(2)fdopen函数:文件描述词转为文件指针
头文件:

 #include <stdio.h>
定义函数:
FILE * fdopen(int fildes, const char * mode);
函数说明:fdopen()会将参数fildes 的文件描述符, 转换为对应的文件指针后返回.

举例:
FILE *aurt=fdopen(fd,”wb+”)

该函数的mode有一下几种情况:
mode有下列几种形态字符串:
r 打开只读文件,该文件必须存在。
r+ 打开可读写的文件,该文件必须存在。
rb+ 读写打开一个二进制文件,允许读数据。
w 打开只写文件,若文件存在则文件长度清为0,即该文件内容会消失。若文件不存在则建立该文件。
wb+ 读写打开或建立一个二进制文件,允许读和写。
w+ 打开可读写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。

(3)fwrite函数:
C语言函数,向文件写入一个数据块 。
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件
返回值:返回实际写入的数据块数目
(1)buffer:是一个指针,对fwrite来说,是要输出数据的地址;
(2)size:要写入内容的单字节数;
(3)count:要进行写入size字节的数据项的个数;
(4)stream:目标文件指针;
(5)返回实际写入的数据项个数count。

举例:
#include <stdio.h>
#include <string.h>
int main()
{
     int fd;    
     fd = open( port, O_RDONLY|O_NOCTTY);
      if(fd<0)
      {
    printf("open error");
      }
    FILE *aurt;
    int error;
    unsigned char send_buf[15]={};
    aurt=fdopen(fd,"rb+");
    error=fwrite(send_buf,sizeof(char),15,aurt);//返回实际写入的数据项个数。
    if(error<0)
    {
        printf("fwrite error");
    }
}

(4)fread 函数:size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
注意:这个函数以二进制形式对文件进行操作,不局限于文本文件
返回值:返回实际读到的数据块数目
(1)buffer:是一个指针,对fread来说,是用于接收数据的地址;
(2)size:要读取内容的单字节数;
(3)count:要进行读取size字节的数据项的个数;
(4)stream:目标文件指针;
(5)返回实际读取的数据项个数count。
FILE *aurt;
int err;
aurt=fdopen(fd,”wb+”);
err=fread(recv_buf,sizeof(char),15,uart);
if (err<0)
{
printf(“fread error”);
}

(6)注意:在向串口发送报文指令的时候,打开了一次fd,在读取的时候,不需要再次打开,不然容易出错,并且在fdopen以后,最后要加上fclose(uart),释放数据流。
(7)最后转载一篇,写的挺好的:

<think>我们正在讨论流指针(FILE*)和文件描述符(int fd)之间的关系。根据引用内容,我们可以总结如下: 1. **文件描述符**: - 是操作系统级别的抽象,代表一个打开的文件、管道、套接字等I/O资源。 - 在Linux中,每个进程都有一个文件描述符表(files_struct数组),文件描述符就是这个数组的下标(索引)[^1][^2]。 - 分配规则:当前未被使用的最小下标[^1][^2]。 2. **流指针(文件流指针)**: - 是C标准库(如glibc)提供的高层抽象,类型为`FILE*`。 - 本质是一个结构体,其中包含了一个文件描述符(通常为`_fileno`成员)以及一个用户空间的缓冲区等[^4][^5]。 - 通过流指针可以使用C标准库函数(如`fread`, `fwrite`, `fprintf`等)进行I/O操作。 3. **关系**: - 流指针内部封装了文件描述符。也就是说,当我们使用流指针进行I/O操作时,最终是通过其内部的文件描述符来调用系统调用(如read, write)[^4]。 - 一个进程可以同时使用文件描述符和流指针操作同一个文件,但需要注意缓冲区的同步问题。 - 每个流指针都对应一个文件描述符,但一个文件描述符可以被多个流指针封装(尽管通常不这样做,因为可能导致混乱)。 4. **转换**: - 从流指针获取文件描述符:使用`fileno`函数,例如:`int fd = fileno(fp);`[^4] - 从文件描述符创建流指针:使用`fdopen`函数,例如:`FILE *fp = fdopen(fd, "r");`[^2] 5. **区别**: - 抽象层次:文件描述符是系统级,流指针是库级(更高层)[^1][^4]。 - 缓冲:流指针有用户空间缓冲区,而文件描述符直接操作没有缓冲(或使用内核缓冲区)[^1][^4]。 - 操作函数:文件描述符使用系统调用(如read, write),流指针使用C标准库函数(如fread, fwrite)[^1][^4]。 6. **图示关系**: ``` 应用程序 | | 使用流指针(FILE*)调用C标准库函数(如fread, fwrite) | C标准库(包含缓冲区管理) | | 通过流指针内部的文件描述符(_fileno)调用系统调用(如read, write) | 操作系统内核 ``` 因此,流指针和文件描述符是不同层次的抽象,流指针建立在文件描述符之上,提供了缓冲和更易用的接口。 回答结构: 1. 先介绍两者各自是什么。 2. 再说明它们之间的关系(封装、转换等)。 3. 总结区别与联系。 注意:使用引用标记[^1][^2][^4]等来支持我们的描述。</think>### 流指针(FILE*)与文件描述符(int fd)的关系解析 #### 1. **核心关系:封装与层级** 流指针(`FILE*`)是**对文件描述符的高层封装**,两者形成层次化关系: ```mermaid graph TD A[应用程序] --> B[标准IO库函数<br>fread/fwrite/fprintf] B --> C[流指针 FILE*] C -->|封装| D[文件描述符 int fd] D -->|系统调用| E[内核文件对象] ``` - **文件描述符**:内核层抽象,直接对应操作系统的文件对象(如磁盘文件、管道等)[^1][^2] - **流指针**:应用层抽象,是包含文件描述符和缓冲区的结构体[^4][^5]: ```c struct _IO_FILE { int _flags; // 状态标志 int _fileno; // 文件描述符 ← 核心关联点 char* _IO_buf_base; // 缓冲区基址 // ... 其他成员 }; ``` #### 2. **映射关系实现** 两者可通过标准库函数相互转换: | **转换方向** | **函数原型** | **作用** | **场景示例** | |--------------------|----------------------------------|-----------------------------------|----------------------------------| | 流指针→文件描述符 | `int fileno(FILE *stream);` | 提取流指针中的文件描述符[^4] | 需要混合使用标准IO和系统调用时 | | 文件描述符→流指针 | `FILE *fdopen(int fd, const char *mode);` | 为已有描述符创建流指针[^2] | 将管道/套接字转为可缓冲的流对象 | > **典型操作**: > ```c > FILE *fp = fopen("file.txt", "r"); > int fd = fileno(fp); // 从流指针获取描述符 > > int sock_fd = socket(...); > FILE *sock_stream = fdopen(sock_fd, "r+"); // 描述符转为流指针 > ``` #### 3. **关键差异对比** | **特性** | 流指针 (`FILE*`) | 文件描述符 (`int fd`) | |------------------|----------------------------------|----------------------------------| | **抽象层级** | C标准库封装(用户空间)[^1][^4] | 操作系统原生接口(内核空间)[^1] | | **包含关系** | 包含文件描述符成员 `_fileno`[^4] | 独立存在(数组下标)[^1][^3] | | **缓冲机制** | 自带用户空间缓冲区[^1][^4] | 无缓冲(直接系统调用)[^1] | | **线程安全** | 需额外同步(如`flockfile`)[^5] | 原子操作更易实现 | | **操作粒度** | 字符/行/格式化数据[^4] | 原始字节块[^1] | #### 4. **协同工作流程** 当通过流指针操作文件时: 1. 调用 `fread()` 等库函数 2. 流指针检查缓冲区数据状态 3. 若缓冲区不足,通过内部的 `_fileno` 调用 `read()` 系统调用[^4] 4. 内核填充流指针的缓冲区 5. 返回请求数据给应用 > **设计本质**:流指针是**带缓冲区的文件描述符包装器**,通过减少系统调用次数提升效率[^1][^4] #### 5. **进程中的关系模型** ```mermaid graph BT subgraph 进程 F[FILE* stdin] --> G1[fd=0] H[FILE* stdout] --> G2[fd=1] I[FILE* stderr] --> G3[fd=2] J[自定义FILE*] --> G4[fd=3] end G1 --> K[内核文件表] G2 --> K G3 --> K G4 --> K ``` - 每个进程默认打开三个流指针:`stdin/stdout/stderr`,对应描述符0/1/2[^3] - 新增文件会分配最小可用描述符(如3,4,...),并可通过`fdopen`转为流指针[^1][^2] #### 6. **典型协作场景** - **混合编程**:通过`fileno()`获取描述符后调用`fcntl()`设置非阻塞模式 - **重定向实现**:先关闭描述符1,再打开新文件(自动分配fd=1),所有输出流指针自动重定向[^2] - **错误处理**:流指针操作失败时,可通过`ferror()`检查错误,或通过`fileno()`获取描述符后检查`errno` > **关键结论**:文件描述符是操作系统的核心资源标识,流指针是C标准库在其基础上构建的**带缓冲的高级抽象接口**,两者通过`_fileno`字段紧密关联[^4][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值