当前位置: 首页 > news >正文

解码Linux文件IO之标准IO

标准 IO 基础

核心概念

  • POSIX 标准:统一不同操作系统的访问接口,让遵循该标准的程序能在 Linux、Windows 等系统间移植(比如标准 C 库的 IO 函数在多系统通用)。

  • 标准 C 库:遵循 POSIX 标准的函数集合,包含标准 IO 函数(如fopenfread),需通过头文件<stdio.h>调用。

  • <stdio.h>路径:Linux 系统下路径为/usr/include/stdio.h

  • FILE 结构体:标准 IO 库管理文件的核心结构体,本质是 “文件信息容器”,包含:

    • 文件描述符(_fileno):关联内核中的文件。
    • 缓冲区指针(如_IO_read_ptr读指针、_IO_write_ptr写指针):暂存读写数据,减少 IO 设备访问次数。
    • 状态标志(_flags):标记文件是否出错、是否到末尾等。
    • 链表指针(chain):内核用链表管理所有打开的文件,每个 FILE 结构体通过chain连接。
  • 默认打开的 3 个流:程序启动时,内核自动打开 3 个标准流,均为FILE*类型:

    • stdin:标准输入(默认对应键盘),行缓冲。
    • stdout:标准输出(默认对应屏幕),行缓冲。
    • stderr:标准出错(默认对应屏幕),无缓冲(错误信息需立即输出)。

    image

核心标准 IO 函数

文件打开:fopenfreopenfdopen

fopen(最常用,打开指定路径文件)

#include <stdio.h>
/*** 以指定模式打开文件,返回文件流指针* @param pathname 待打开文件的路径(如"demo.txt"、"/home/user/test.dat")* @param mode     打开模式(字符串形式,决定读写权限和文件处理方式):*                 - "r":只读,文件必须存在,不存在则打开失败*                 - "r+":读写,文件必须存在,不存在则打开失败*                 - "w":只写,文件不存在则创建,存在则清空内容*                 - "w+":读写,文件不存在则创建,存在则清空内容*                 - "a":追加写,文件不存在则创建,写入位置始终在文件末尾*                 - "a+":追加读写,文件不存在则创建,写入在末尾,读取位置默认在开头(Linux下)* @return         成功:返回指向FILE结构体的指针(文件流指针);失败:返回NULL(需判断,避免野指针)* @note           打开成功后,内核从堆内存分配FILE结构体空间;不可反复调用fclose关闭同一文件(会导致double free错误)*/
FILE *fopen(const char *pathname, const char *mode);

freopen(重定向流,如将 stdout 重定向到文件)

#include <stdio.h>
/*** 重定向文件流(将指定流关联到新文件)* @param pathname 新文件路径(如"log.txt")* @param mode     打开模式(同fopen的mode,如"w"、"a")* @param stream   待重定向的流(如stdout、stdin)* @return         成功:返回重定向后的stream;失败:返回NULL* @note           常用于将标准输出(stdout)重定向到文件,实现“日志写入”*/
FILE *freopen(const char *pathname, const char *mode, FILE *stream);

fdopen(将文件描述符转换为文件流指针)

#include <stdio.h>
/*** 将内核的文件描述符(fd)转换为FILE*流(适配标准IO函数)* @param fd    已打开的文件描述符(如open函数返回的fd)* @param mode  打开模式(同fopen,但需与fd的权限匹配,如fd是只读的,mode不能用"w")* @return      成功:返回FILE*指针;失败:返回NULL* @note        用于“系统调用IO”(如open)与“标准IO”(如fread)的衔接*/
FILE *fdopen(int fd, const char *mode);

二进制模式 vs 文本模式

  • 二进制模式:使用 "rb"(二进制只读)、"wb"(二进制只写)、"r+b"(二进制读写)作为打开模式,数据按 “原始字节” 读写,系统不对任何字符进行解释或转换。适用场景:图片(如 JPG/PNG)、音频(如 MP3/WAV)、视频、可执行文件(如 .exe/.out)等二进制文件,以及需要精确保留字节结构的文本文件(如配置文件)。例如,用二进制模式读取图片文件时,每个字节(包括 \n\r 等特殊字符)都会原样保留,避免文件损坏。
  • 文本模式:使用 "r"(文本只读)、"w"(文本只写)、"r+"(文本读写)作为打开模式,不同操作系统对 “换行符” 的处理存在差异:
    • Windows 系统:写入时会将文本中的 \n(换行)自动转换为 \r\n(回车 + 换行),读取时再将 \r\n 反向转换为 \n
    • Linux/macOS 系统:无任何字符转换(\n 直接表示换行)。适用场景:仅用于跨 Windows/Linux 且无需保留原始字节的纯文本文件(如 .txt 文档)。若跨系统处理文件,推荐优先使用二进制模式,避免因换行符转换导致数据错位(如 Linux 下用文本模式读取 Windows 生成的文件,会多出现 \r 字符)。

文件读取:按字符、按行、按块

按字符读取:fgetcgetcgetchar

fgetc(从指定流读 1 个字符)

#include <stdio.h>
/*** 从指定文件流读取1个字符,同时移动文件光标(位置指示器)* @param stream 待读取的文件流(如fp、stdin)* @return       成功:返回读取字符的ASCII码(unsigned char转换为int);*               失败/到文件末尾:返回EOF(宏定义,值为-1,定义在<stdio.h>中)* @note         需通过feof()和ferror()区分“到末尾”和“读错误”;每次读取后,光标后移1字节*/
int fgetc(FILE *stream);

getcgetchar

  • getc:功能与fgetc完全一致,但getc是宏定义实现,多次计算stream参数(如getc(fp++)会出错,避免此类用法)。
  • getchar:等价于getc(stdin),仅从标准输入(键盘)读 1 个字符
/*** getc:同fgetc(宏实现,需注意参数副作用)* @param stream 待读取的流* @return       成功返回字符ASCII码,失败/末尾返回EOF*/
int getc(FILE *stream);/*** getchar:仅从stdin读1个字符(等价于getc(stdin))* @return  成功返回字符ASCII码,失败/末尾返回EOF*/
int getchar(void);

按行读取:fgets(安全读取一行)

#include <stdio.h>
/*** 从指定流读取一行字符,存储到缓冲区,避免缓冲区溢出* @param s     自定义缓冲区(用于存储读取的字符,需提前分配空间,如char buf[1024])* @param n     缓冲区大小(最大读取n-1个字符,最后1个字节自动存'\0',保证字符串结束)* @param stream 待读取的流(如fp、stdin)* @return      成功:返回缓冲区s的地址;失败/到末尾(且未读任何字符):返回NULL* @note        读取停止条件:读满n-1个字符、遇到'\n'(会保留'\n'在缓冲区)、遇到EOF;*              若返回NULL,需用feof()和ferror()区分原因;3. 避免用gets()(无缓冲区大小限制,会溢出)*/
char *fgets(char *s, int n, FILE *stream);

按块读取:fread(高效读取二进制 / 大量数据)

#include <stdio.h>
/*** 从流中读取指定数量的“数据块”(适合二进制文件,如图片、音频,或大量文本)* @param ptr    自定义缓冲区指针(存储读取的数据,需提前分配空间)* @param size   每个数据块的字节数(如读int类型,size=sizeof(int))* @param nmemb  要读取的数据块总数(如读10个int,nmemb=10)* @param stream 待读取的流* @return       成功读取的“数据块个数”:*               - 等于nmemb:读取完全成功;*               - 小于nmemb:可能是到文件末尾,或读错误;*               - 0:size/nmemb为0,或读取失败* @note         总读取字节数 = 成功读取的块数 × size;需用feof()和ferror()区分“末尾”和“错误”*/
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

文件写入:按字符、按行、按块

按字符写入:fputcputcputchar

#include <stdio.h>
/*** 将1个字符写入指定流,同时移动文件光标* @param c      待写入的字符(需传入ASCII码,如'A'或65)* @param stream 待写入的流(如fp、stdout)* @return       成功:返回写入的字符(ASCII码);失败:返回EOF* @note         若流是“追加模式”(a/a+),字符始终写入文件末尾*/
int fputc(int c, FILE *stream);
  • putc:功能与fputc一致,宏定义实现,注意参数副作用。
  • putchar:等价于putc(c, stdout),仅写入标准输出(屏幕)。

按行写入:fputs

#include <stdio.h>
/*** 将字符串写入指定流(不自动添加'\n',需手动加)* @param s     待写入的字符串(以'\0'结尾,'\0'不会被写入)* @param stream 待写入的流* @return      成功:返回非负整数;失败:返回EOF* @note        与puts()区别:puts()会自动在字符串后加'\n',且仅写入stdout*/
int fputs(const char *s, FILE *stream);

按块写入:fwrite(高效写入二进制 / 大量数据)

#include <stdio.h>
/*** 将指定数量的“数据块”写入流(适合二进制文件或大量数据)* @param ptr    存储待写入数据的缓冲区指针* @param size   每个数据块的字节数(如写int类型,size=sizeof(int))* @param nmemb  要写入的数据块总数* @param stream 待写入的流* @return       成功写入的“数据块个数”:等于nmemb为完全成功,小于则为写入错误* @note         总写入字节数 = 成功写入的块数 × size;二进制写入需用"wb"模式,避免换行符转换*/
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

文件关闭:fclose

#include <stdio.h>
/*** 关闭文件流,释放资源* @param stream 待关闭的文件流(如fp)* @return       成功:返回0;失败:返回EOF(如流已关闭仍调用)* @note         关闭前会自动刷新流的缓冲区(未写入的数据会被写入文件);*               关闭后stream指针变为野指针,不可再使用;*               禁止多次关闭同一流(会导致double free错误,堆内存重复释放)*/
int fclose(FILE *stream);

缓冲区刷新:fflush

标准 IO 的缓冲区需 “刷新” 后,数据才会从缓冲区写入文件 / 设备。fflush用于手动触发刷新。

#include <stdio.h>
/*** 手动刷新流的缓冲区(将未写入的数据写入文件/设备)* @param stream 待刷新的流(如fp、stdout);若为NULL,刷新所有可刷新的流* @return       成功:返回0;失败(如写错误):返回EOF* @note         行缓冲流(如stdout)遇'\n'自动刷新;全缓冲流(如普通文件)满了自动刷新;*               无缓冲流(如stderr)无需刷新,直接输出;关闭流(fclose)时会自动刷新*/
int fflush(FILE *stream);

文件位置控制:fseekftellrewind

文件光标(位置指示器)默认在文件开头,读写时会自动后移。通过以下函数可手动控制光标位置。

fseek(设置光标位置)

#include <stdio.h>
/*** 设置文件流的光标位置(仅支持二进制流;文本流限制较多,建议用二进制流)* @param stream 待设置的流* @param offset 偏移量(可正可负:正表示向后移,负表示向前移)* @param whence 偏移基准点:*               - SEEK_SET:基准点为文件开头(offset必须≥0)*               - SEEK_CUR:基准点为当前光标位置*               - SEEK_END:基准点为文件末尾(offset负表示向前移,如offset=-10表示末尾前10字节)* @return       成功:返回0;失败:返回非0(如offset超出文件范围)* @note         二进制流可用所有基准点;文本流仅支持offset=0且whence=SEEK_SET/SEEK_END*/
int fseek(FILE *stream, long int offset, int whence);

ftell(获取当前光标位置)

#include <stdio.h>
/*** 获取文件流当前的光标位置(距离文件开头的字节数)* @param stream 待获取的流* @return       成功:返回光标位置(字节数,long类型);失败:返回-1L(L表示long)* @note         常用于计算文件大小(光标移到末尾,用ftell获取位置即为文件大小)*/
long int ftell(FILE *stream);

rewind(光标移回文件开头)

#include <stdio.h>
/*** 将文件流的光标移回文件开头(等价于fseek(stream, 0, SEEK_SET))* @param stream 待操作的流* @note         无返回值,无法判断是否成功;若需判断,建议用fseek*/
void rewind(FILE *stream);

错误与末尾判断:feofferror

fgetcfread等函数返回 EOF 时,可能是 “到文件末尾”,也可能是 “读写错误”,需用以下函数区分。

#include <stdio.h>
/*** 判断流是否到达文件末尾* @param stream 待判断的流* @return       是:返回非0值;否:返回0* @note         仅在“读取操作返回EOF后”调用才有意义(提前调用可能误判)*/
int feof(FILE *stream);/*** 判断流是否发生读写错误* @param stream 待判断的流* @return       是:返回非0值;否:返回0* @note         错误标志需用clearerr()清除,否则后续操作仍会认为有错误*/
int ferror(FILE *stream);/*** 清除流的“末尾标志”和“错误标志”* @param stream 待清除的流* @note         清除后,可重新对流转发读写操作*/
void clearerr(FILE *stream);

格式化 IO:fprintffscanfsprintfsnprintf

fprintf(格式化写入流)

#include <stdio.h>
/*** 按指定格式将数据写入流(如文件、屏幕)* @param stream 待写入的流(如fp、stdout)* @param format 格式控制字符串(如"%d %s",指定后续参数的类型和格式)* @param ...    可变参数(需与format的格式匹配,如int、char*等)* @return       成功:返回写入的字符总数;失败:返回负数* @note         与printf()区别:printf()固定写入stdout,fprintf()可指定流*/
int fprintf(FILE *stream, const char *format, ...);

fscanf(格式化读取流)

#include <stdio.h>
/*** 按指定格式从流中读取数据(需注意缓冲区残留问题)* @param stream 待读取的流* @param format 格式控制字符串(如"%d %s")* @param ...    可变参数(需传入变量地址,如&int_var、char_var)* @return       成功读取的“数据项个数”;失败/到末尾:返回EOF* @note         读取时会跳过空白字符(空格、换行、制表符);读取字符串时遇空白字符停止*/
int fscanf(FILE *stream, const char *format, ...);

sprintfsnprintf(格式化写入缓冲区)

  • sprintf:将格式化数据写入字符串缓冲区(无大小限制,可能溢出)。
  • snprintf:指定缓冲区大小,避免溢出(推荐使用)。
#include <stdio.h>
/*** 将格式化数据写入字符串缓冲区(不安全,可能溢出)* @param s      目标缓冲区(存储格式化后的字符串)* @param format 格式控制字符串* @param ...    可变参数* @return       成功:返回写入的字符数(不含'\0');失败:返回负数*/
int sprintf(char *s, const char *format, ...);/*** 将格式化数据写入字符串缓冲区(安全,指定最大写入字节数)* @param s      目标缓冲区* @param n      缓冲区大小(最大写入n-1个字符,最后加'\0')* @param format 格式控制字符串* @param ...    可变参数* @return       成功:返回应写入的字符数(若n足够);失败:返回负数* @note         若应写入的字符数≥n,仅写入n-1个字符,保证缓冲区安全*/
int snprintf(char *s, size_t n, const char *format, ...);

缓冲区机制(关键)

缓冲区的作用

IO 设备(如硬盘、键盘)速度远慢于 CPU,缓冲区暂存数据,减少 CPU 等待 IO 的时间(比如写文件时,先存缓冲区,满了再一次性写硬盘)。

image

三种缓冲区类型

缓冲类型 适用场景(含典型对象) 刷新时机(数据写入设备的触发条件)
全缓冲 普通文件(如文本文件 demo.txt、二进制文件 data.bin),非实时交互类数据存储场景 - 缓冲区被数据填满(默认大小通常为 4KB/4096 字节);
- 调用 fflush 函数手动强制刷新;
- 关闭文件流(fclose),关闭前自动刷新未写入数据;
- 程序正常结束(如 return 0);
- 对同一文件流调用读操作(如 fread)或修改缓冲区类型
行缓冲 标准输出流(stdout,如 printf 终端输出),需按行实时反馈的交互场景(如终端打印日志) - 缓冲区中检测到换行符 \n(核心触发条件,无 \n 则暂存);
- 缓冲区被数据填满(默认 4KB);
- 调用 fflush 函数手动强制刷新;
- 关闭流(fclose)或程序正常结束;
- 对关联流调用读操作(如对 stdinfgetc 读键盘输入,会触发 stdout 刷新)
无缓冲 标准错误流(stderr,如 perror 错误提示),需即时输出紧急信息的场景(如程序报错) 数据生成后直接写入设备,无需暂存到缓冲区;触发写操作(如 perror("error"))后立即输出,无延迟

标准 IO 缓冲区设置函数详解

以下函数均用于自定义标准 IO 流的缓冲区(类型、大小、存储地址),需在流打开后、首次读写操作前调用,否则可能导致缓冲区状态混乱或设置失效,相关功能在 C 标准及文档中均有约束。

int setvbuf(FILE *stream, char *buf, int mode, size_t size);

功能

最灵活的缓冲区设置函数,支持自定义缓冲区地址、类型(全缓冲 / 行缓冲 / 无缓冲)和大小,是后三个函数的 “底层实现依赖”()。

参数解析与返回值

#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未读写)* @param buf    缓冲区地址:*               - 非NULL:使用用户提供的缓冲区(大小≥size)*               - NULL:使用系统自动分配的缓冲区(大小由size指定)* @param mode   缓冲模式(必须是以下三者之一):*               - _IONBF:无缓冲(数据直接输出,类似stderr,)*               - _IOLBF:行缓冲(遇'\n'或缓冲区满刷新,类似stdout,)*               - _IOFBF:全缓冲(缓冲区满或关闭流时刷新,类似普通文件,)* @param size   缓冲区大小(单位:字节):*               - 若mode为_IONBF:size无意义(可传0)*               - 若mode为_IOLBF/_IOFBF:size需≥1(系统自动分配时,size为最小缓冲大小)* @return       成功返回0;失败返回非0(如mode无效、流已读写、size过小)* @note         标准C函数(文档中明确列为文件访问函数,);*               关闭流时,系统分配的缓冲区会自动释放(用户提供的buf需手动释放);*               若buf非NULL,其内存需持续有效(全局/静态/堆内存,避免栈内存释放)*/
int setvbuf(FILE *stream, char *buf, int mode, size_t size);

关键特性

  • 覆盖所有缓冲需求:例如 “无缓冲 + 用户缓冲区”“行缓冲 + 系统分配缓冲区” 等场景;
  • 文档中明确其与setbuf共同管理缓冲区关联关系:关闭流时,通过这两个函数设置的缓冲区会与流解除关联(若为系统分配则释放,若为用户主动分配(如用malloc/calloc分配内存,再通过setbuf/setbuffer/setvbuf绑定到流)时,标准 IO 库仅使用缓冲区,不负责释放,需用户在缓冲区不再使用后手动释放(free))。

void setbuf(FILE *stream, char *buf);

功能

为指定文件流stream设置缓冲区,或禁用缓冲区,是setvbuf的简化版本(默认缓冲区大小通常为 4KB,与文档中行缓冲 / 全缓冲默认大小一致)。

参数解析

#include <stdio.h>
/*** @param stream 目标文件流(如fp、stdout,需已通过fopen打开)* @param buf    缓冲区地址:*               - 若为非NULL:使用用户提供的缓冲区(建议大小≥4096字节,匹配系统默认缓冲大小)*               - 若为NULL:禁用该流的缓冲区(等效于无缓冲模式,类似stderr默认特性,)* @return       无返回值* @note         必须在流首次读写前调用;用户提供的buf需为全局/静态变量(避免栈内存释放后失效)*/
void setbuf(FILE *stream, char *buf);

关键特性

  • 本质是setvbuf(stream, buf, (buf==NULL)?_IONBF:_IOFBF, BUFSIZ)的封装(BUFSIZ是系统定义的默认缓冲大小,通常为 4096);
  • 若设置非 NULL 缓冲区,关闭流时该缓冲区会与流解除关联(但不会自动释放,需用户手动管理,)。

void setbuffer(FILE *stream, char *buf, size_t size);

功能

扩展setbuf的功能,允许自定义缓冲区大小(不再固定为 4KB),更灵活适配不同读写场景(如大文件读写需更大缓冲区)。

参数解析

#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未进行读写)* @param buf    缓冲区地址:*               - 非NULL:使用用户提供的缓冲区(大小由size指定)*               - NULL:禁用缓冲区(无缓冲模式)* @param size   缓冲区大小(单位:字节):*               - 若buf非NULL,size需≥1(建议为2的幂,如1024、8192,提升IO效率)*               - 若buf为NULL,size无意义(可传0)* @return       无返回值* @note         非标准C函数(但POSIX系统普遍支持);其他约束同setbuf(调用时机、缓冲区内存属性)*/
void setbuffer(FILE *stream, char *buf, size_t size);

关键特性

  • 相比setbuf,核心优势是自定义size,例如处理大文件时可设置 8KB/16KB 缓冲区,减少 IO 次数;
  • buf非 NULL 但size过小(如 < 100 字节),可能导致频繁缓冲刷新,反而降低效率。

void setlinebuf(FILE *stream);

功能

强制将指定流stream设置为行缓冲模式,等效于 “设置行缓冲 + 默认缓冲区大小”,适配需按行刷新的场景(如模拟 stdout 默认行为)。

参数解析

#include <stdio.h>
/*** @param stream 目标文件流(需已打开且未读写)* @return       无返回值* @note         本质是`setvbuf(stream, NULL, _IOLBF, BUFSIZ)`的封装;*               设置后,缓冲区遇'\n'或满时自动刷新(符合行缓冲特性);*               若流原本为全缓冲/无缓冲,调用后会切换为行缓冲*/
void setlinebuf(FILE *stream);

关键特性

  • 典型用途:将普通文件流设为行缓冲(如日志文件,需逐行写入以便实时查看);
  • 与 stdout 默认行缓冲的区别:stdout 仅在关联终端时为行缓冲,关联文件时为全缓冲,而setlinebuf可强制文件流也为行缓冲。

实战案例

案例 1:计算文件大小(命令行传参)

需求:通过命令行传入文件路径,计算并输出文件大小(单位:字节)。

#include <stdio.h>
#include <stdlib.h> // 包含exit
int main(int argc, char const *argv[]) {// 步骤1:判断命令行参数是否正确(需传入1个文件路径,argc应为2)if (argc != 2) {printf("用法:%s <文件路径>\n", argv[0]); // 提示正确用法,如"./file_size demo.txt"exit(1); // 退出程序,1表示错误退出}// 步骤2:以二进制只读模式打开文件FILE *fp = fopen(argv[1], "rb");if (fp == NULL) {perror("fopen error");exit(1);}// 步骤3:计算文件大小fseek(fp, 0, SEEK_END); // 光标移到末尾long file_size = ftell(fp); // 获取末尾位置(即文件大小)// 步骤4:输出结果printf("文件[%s]大小:%ld 字节\n", argv[1], file_size);// 步骤5:关闭文件fclose(fp);return 0;
}

编译运行:

gcc file_size.c -o file_size
./file_size demo.txt # 输出:文件[demo.txt]大小:123 字节

案例 2:文件拷贝(命令行传参)

需求:通过命令行传入源文件(A)和目标文件(B),将 A 的内容完整拷贝到 B(B 不存在则创建)。

#include <stdio.h>
#include <stdlib.h>
#define BUF_SIZE 1024 // 定义1KB缓冲区(平衡效率和内存)
int main(int argc, char const *argv[]) {// 步骤1:判断参数(需传入源文件和目标文件,argc=3)if (argc != 3) {printf("用法:%s <源文件> <目标文件>\n", argv[0]); // 如"./copy demo.txt demo_copy.txt"exit(1);}// 步骤2:打开源文件(只读)和目标文件(只写,不存在则创建)FILE *src_fp = fopen(argv[1], "rb"); // 源文件:二进制只读FILE *dest_fp = fopen(argv[2], "wb"); // 目标文件:二进制只写if (src_fp == NULL || dest_fp == NULL) {perror("fopen error");// 若已打开一个流,需关闭if (src_fp != NULL) fclose(src_fp);if (dest_fp != NULL) fclose(dest_fp);exit(1);}// 步骤3:循环读取源文件,写入目标文件char buf[BUF_SIZE];size_t read_cnt; // 每次读取的字节数while ((read_cnt = fread(buf, 1, BUF_SIZE, src_fp)) > 0) {// 读取多少,写入多少fwrite(buf, 1, read_cnt, dest_fp);}// 步骤4:判断拷贝是否成功if (ferror(src_fp)) {perror("读取源文件错误");} else if (ferror(dest_fp)) {perror("写入目标文件错误");} else {printf("拷贝成功:%s → %s\n", argv[1], argv[2]);}// 步骤5:关闭文件fclose(src_fp);fclose(dest_fp);return 0;
}

案例 3:定时写入系统时间到日志

需求:每隔 1 秒获取当前系统时间,按 “yy 年 mm 月 dd 日 星期 x hh:mm:ss” 格式写入 log.txt(不存在则创建),无限循环。

#include <stdio.h>
#include <time.h>   // 包含时间相关函数
#include <unistd.h> // 包含sleep函数(Linux下)
// 星期转换:tm_wday是0(周日)~6(周六),转换为中文
const char *get_weekday(int tm_wday) {const char *week[] = {"日", "一", "二", "三", "四", "五", "六"};return week[tm_wday];
}int main() {// 以追加模式打开log.txt(不存在则创建,存在则追加)FILE *fp = fopen("log.txt", "a");if (fp == NULL) {perror("fopen error");return -1;}while (1) { // 无限循环// 步骤1:获取当前系统时间(秒数,从1970-01-01 00:00:00 UTC开始)time_t now = time(NULL);if (now == (time_t)-1) {perror("time error");fclose(fp);return -1;}// 步骤2:将秒数转换为本地时间(年、月、日等)struct tm *local_tm = localtime(&now);if (local_tm == NULL) {perror("localtime error");fclose(fp);return -1;}// 步骤3:按指定格式写入log.txt// 格式:yy年mm月dd日 星期x hh:mm:ss(注意:tm_year是年份-1900,tm_mon是0~11)fprintf(fp, "%02d年%02d月%02d日 星期%s %02d:%02d:%02d\n",local_tm->tm_year % 100, // yy(取年份后两位)local_tm->tm_mon + 1,    // mm(加1,因为tm_mon是0~11)local_tm->tm_mday,       // ddget_weekday(local_tm->tm_wday), // 星期xlocal_tm->tm_hour,       // hhlocal_tm->tm_min,        // mmlocal_tm->tm_sec);       // ss// 步骤4:手动刷新缓冲区(避免日志延迟写入)fflush(fp);// 步骤5:等待1秒sleep(1);}// 注:循环不会退出,实际使用中需按Ctrl+C终止,终止后会自动关闭流fclose(fp);return 0;
}

注意事项

  • 避免重复关闭文件fclose多次调用同一流会导致 double free 错误,建议关闭后将指针置为NULL(如fclose(fp); fp=NULL;)。
  • 二进制与文本模式区分:读写图片、音频等二进制文件必须用"rb"/"wb",避免系统转换字符导致文件损坏。
  • 缓冲区刷新:用 “追加模式” 写日志时,需手动调用fflush,否则日志会暂存缓冲区,不及时写入文件。
  • 命令行参数判断:接收命令行参数(如argv[1])时,必须先判断argc是否正确,避免访问空指针。
  • 内存释放:用malloc分配的缓冲区(如fread的缓冲区),必须用free释放,避免内存泄漏。
http://www.hskmm.com/?act=detail&tid=31094

相关文章:

  • 10.14 CSP-S模拟31 改题记录
  • 高级程序语言第一次作业
  • 安装devc++过程的分享以及问题的记录
  • Linux之线程池 - 指南
  • zlog1
  • LlamaIndex检索调优实战:分块、HyDE、压缩等8个提效方法快速改善答案质量
  • 动火作业风险早预警!AI + 热成像技术筑牢防火安全线
  • 解题报告-P5664 [CSP-S2019] Emiya 家今天的饭
  • object类
  • Day 10
  • 2025 年生态格宾网厂家推荐榜:格宾网石笼/格宾网护坡/格宾网挡墙/格宾网网箱厂家推荐,聚焦工程安全与生态保护,助力基建项目高效落地
  • 时序博弈算法荣获时间检验奖
  • 背叛 仇恨 消极 如刀子刺穿了铁心 嘲笑 嗤之以鼻 漠然后只剩下孤寂
  • STM32主控芯片硬件设计总结
  • 亚马逊因暗黑模式订阅设计支付25亿美元和解金
  • P6645 [CCO 2020] Interval Collection
  • 2025年排烟风机厂家推荐榜:混流风机|管道风机|排烟风机|离心风机|轴流风机|轴流风机厂家,专注高效消防与节能,助力多行业绿色升级
  • 【通达信L2黑科技】 用 DLL 把 10 年机构大单净额 1 秒拖进本地,选股、排序、回测快到飞起!
  • 详细介绍:iCloud照片共享:在家庭内外分享iCloud照片
  • 对static新的认识
  • 2025年氧化镁厂家最新推荐排行榜,电工级/高温/低温/中温/防火电缆/矿物绝缘/熔盐加热器/电热管用/单头管用/合成云母用氧化镁公司推荐!
  • 智能体分析
  • 2025 年玄武岩厂家推荐榜:玄武岩/0-3mm/3-5mm/5-10mm/10-15mm/10-20mm/石子厂,聚焦基建升级与高端化需求,山东展飞建筑材料有限公司成优选
  • 2025 佛山铝合金/系统/断桥铝/耐用/推拉/封阳台/别墅/静音门窗厂家品牌实力推荐:聚焦技术与服务的五大优选标杆
  • Ubuntu22.04 server网络配置
  • 完整教程:深度学习优化器全面指南:核心参数选择与实战策略
  • 说说新版畅联云的一些重要约定
  • App.vue(完整可运行示例)
  • Windows MySQL 报错
  • agents.md和codex.md的关系:codex本尊直接答复