由于时间不多,来不及更新帖子,现已将部分C++资料上传github(因为平时习惯不好,归档分类有点乱)
https://github.com/liuguoqingdq/C-Line
内容中有不正确的地方还望指正
月仁不吃五饼
-
C++学习资料开源 -
Linux系统编程(2)文件IO@sunrisepeak 在 Linux系统编程(2)文件IO 中说:
感觉可以把这个系列整理成一个GitHub 仓库 包含文档和对应的练习或演示代码
让读者上手实操/改改东西 用 strace 等一些工具 追一追 系统调用 观察真实机器上的效果 之类的
体验感应该能增强不少时间比较少,有空可以整理一下
-
Linux系统编程(3)文件读写操作Linux系统编程(4)文件读写操作
通过read()读文件
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);每个参数分别是什么
参数 1:
int fd-
fd是文件描述符,一般来自:-
open()打开文件 -
或者标准输入
0(STDIN_FILENO)
-
-
内核用
fd找到你打开的“文件对象 + 当前偏移量”等状态。
参数 2:
void *buf-
你提供的缓冲区地址,用来装读出来的数据。
-
必须保证这块内存可写并且至少有
count个字节容量。 -
例如:
char buf[4096]; read(fd,buf,sizeof(buf));参数 3:
size_t count-
你希望这次最多读取多少字节。
-
注意:这是“最多”,不是“必须读满”。
每次调用read()函数,会从fd指向的文件的当前偏移开始读取len字节到buf所指向的内存中。执行成功时,返回写入buf中的字节数;出错时,返回-1,并设置errno值。fd的文件位置指针会向前移动,移动的长度由读取到的字节数决定。如果fd所指向的对象不支持seek操作(比如字符设备文件),则读操作总是从“当前”位置开始。
基本用法很简单。下面这个例子就是从文件描述符fd所指向的文件中读取数据并保存到word中。读取的字节数即unsigned long类型的大小,在Linux的32位系统上是4字节,在64位系统上是8字节。成功时,返回读取的字节数;出错时,返回-1:**usigned long world; ssize_t nr nr = read(fd,&world,sizeof(world)); if(nr==-1)......返回值三种情况(必须背熟)
情况 A:返回值
> 0-
表示实际读取到的字节数,比如返回 1000,就说明
buf[0..999]有效。 -
不保证等于
count:读文件也可能因为系统缓冲、信号等原因“读不满”。
情况 B:返回值
== 0-
表示 EOF(文件结束):已经读到文件末尾,再读就没有数据了。
-
对普通文件,这是“读完”的标志。
情况 C:返回值
== -1-
表示出错,具体原因在全局变量
errno里。 -
最常见的你需要认识两个:
-
errno == EINTR:被信号打断,通常重试即可 -
errno == EAGAIN / EWOULDBLOCK:非阻塞读时没数据(普通文件很少遇到)
-
读入所有字节
诚如前面所描述的,由于调用read()会有很多不同情况,如果希望处理所有错误并且真正每次读入len个字节(至少读到EOF),那么之前简单“粗暴”的read()调用并不合理。要实现这一点,需要有个循环和一些条件语句,如下:
ssize_t ret;//声明返回值 size_t length;//声明需要读取的字节长度 while(len>0 && ret=read(fd,buf,length) ){ if(ret==-1){ if(error==EINTR) continue; perror("read"); break; } len-=(size_t)ret; buf+=ret; }read()读出来的是原始字节,它只返回“实际读了多少字节”,不会自动在末尾补'\0'。所以:
- 如果把
read的结果当作 C 字符串来用,必须自己加终止符,并且要预留一个字节空间。
实例:
char buf[1024]; ssize_t n = read(fd, buf, sizeof(buf) - 1); // 预留 1 字节给 '\0' if (n > 0) { buf[n] = '\0'; // 手动补 '\0' printf("%s", buf); }补充两点常见坑:
-
read读到的内容里可能本来就包含'\0'(比如二进制文件),这时即使你手动补了终止符,printf("%s")也会在中间的'\0'提前截断。 -
如果
n == sizeof(buf)-1,上面这种写法仍然安全,因为你预留了 1 字节。
阻塞IO(先了解即可)
有时,开发人员不希望read()调用在没有数据可读时阻塞在那里。相反地,他们希望调用立即返回,表示没有数据可读。这种方式称为非阻塞I/O,它支持应用以非阻塞模式执行I/O操作,因而如果是读取多个文件,以防错过其他文件中的可用数据。
因此,需要额外检查errno值是否为EAGAIN。正如前面所讨论的,如果文件描述符以非阻塞模式打开(即open()调用中指定参数为O_NONBLOCK,open()调用的参数”),并且没有数据可读,read()调用会返回-1,并设置errno值为EAGAIN,而不是阻塞模式。当以非阻塞模式读文件时,必须检查EAGAIN,否则可能因为丢失数据导致严重错误。你可能会用到如下代码:char buf[MAX_SIZE]; usigned int len; start: ssize_t ret = read(fd,buf,len); if (ret==-1){ if(error==ENTER) goto satrt; else if(error==EAGAIN)....; else...; }怎么打开非阻塞设置
方法 A:
open时带O_NONBLOCKint fd = open("a.txt",O_RDONLY | O_NONBLOCK);方法 B:对已有 fd 用
fcntl打开非阻塞#include <fcntl.h> int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK);非阻塞读的正确姿势:配合 select/poll/epoll
非阻塞不是让你疯狂
while(read...)空转(那会 CPU 100%),而是:1)先等“可读事件”(有数据了再读)
2)再read(),并且通常要一直读到 EAGAIN 才停(把缓冲区读空)#include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdio.h> #include <poll.h> int main() { int fd = STDIN_FILENO; int flags = fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); char buf[1024]; while (1) { struct pollfd pfd = { .fd = fd, .events = POLLIN }; int r = poll(&pfd, 1, 5000); // 等 5 秒 if (r == 0) { puts("timeout..."); continue; } if (r < 0) { perror("poll"); break; } // 可读了:把当前能读的都读出来(直到 EAGAIN) while (1) { ssize_t n = read(fd, buf, sizeof(buf)); if (n > 0) { write(STDOUT_FILENO, buf, (size_t)n); continue; } if (n == 0) return 0; // EOF if (errno == EINTR) continue; if (errno == EAGAIN || errno == EWOULDBLOCK) break; perror("read"); return 1; } } return 0; } -
-
Linux系统编程(2)文件IOLinux系统编程(2)文件IO
最基本的文件访问方法是系统调用read()和write()。但是,在访问文件之前,必须先通过open()或creat()打开该文件。一旦完成文件读写,还应该调用系统调用close()关闭该文件。
大致流程如下open/create->read/write->closeopen系统调用
open系统调用整个流程如下:
┌──────────────┐ │ Your Code │ │ open(...) │ └──────┬───────┘ ▼ ┌──────────────────┐ │ glibc (libc.so) │ ← 用户态库 │ open() wrapper │ └──────┬───────────┘ ▼ ┌──────────────────┐ │ syscall entry │ ← 用户态 / 内核态边界 │ SYS_openat │ └──────┬───────────┘ ▼ ┌──────────────────┐ │ Linux Kernel │ │ sys_openat() │ └──────────────────┘而在使用上,我们需要先包含下述头文件
#include <unistd.h> // read, write, close, lseek #include <fcntl.h> // open, O_RDONLY/O_CREAT/... flags #include <sys/stat.h> // mode_t, 权限位 S_IRUSR 等(创建文件常用) #include <sys/types.h> // ssize_t, off_t 等(有时可省,现代系统常被间接包含)通过系统调用open(),可以打开文件并获取其文件描述符:
open系统调用有两个函数签名int open(const char *name, int flags); int open(const char *name, int flags, mode_t mode);它们的区别是 第三个参数
mode要不要传(后面补充这第三个参数的作用)open的返回值是文件描述符,linux下一切皆文件,如果返回-1代表发生错误
open()的flags参数
flags参数是由一个或多个标志位的按位或组合。它支持三种访问模式:O_RDONLY、O_WRONLY或O_RDWR,(其实是单词的缩写,不用死记硬背)这三种模式分别表示以只读、只写或读写模式打开文件。
举个例子,以下代码以只读模式打开文件/home/kidd/madagascar:int fd =open("/home/kidd/madagascar",O_RDONLY); if(fd==-1){ //这里是错误处理,要养成好习惯,随手处理错误 }使用open打开文件之后,我们就获得了这个文件的句柄。用这个句柄就可以对这个文件施加操作了,但是要尤为注意的是,对这个文件能够施加怎样的操作,取决于打开方式,即flags的参数。
那么问题来了,如果文件不存在怎么办? 这就涉及到flags其他可选参数:1) 创建/覆盖类
-
O_CREAT:文件不存在就创建(这时必须提供第三个参数mode)open("a.txt", O_WRONLY | O_CREAT, 0644); -
O_EXCL:配合O_CREAT使用;如果文件已存在则失败(避免覆盖)
当和标志位O_CREAT一起使用时,如果参数name指定的文件已经存在,会导致open()调用失败。用于防止创建文件时出现竞争。如何没有和标志位O_CREAT一起使用,该标志位就没有任何含义。
open("a.txt", O_WRONLY | O_CREAT | O_EXCL, 0644); // 存在则报 EEXIST上面涉及到O_CREAT的操作就需要第三个参数了0644,这个参数的意义如下:
0 6 4 4 ↑ ↑ ↑ │ │ └─ 其他用户 (other) │ └── 同组用户 (group) └──── 文件所有者 (owner)0644 的三位分别代表谁?
权限格式是:
[ owner ][ group ][ other ]
每一位是 0~7 的八进制数:
数字 二进制 含义
4 100 读 (r)
2 010 写 (w)
1 001 执行 (x)
所以0644代表:文件所有者rw权限,同组用户和其他用户有r权限
使用O_CREAT必须使用第三个权限参数
-
O_TRUNC:文件已存在且以写方式打开时,把文件长度截断为 0(清空重写)open("a.txt", O_WRONLY | O_TRUNC);
2) 追加/定位类
-
O_APPEND:每次write都追加到文件末尾(内核保证追加定位的原子性)open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644);
3) 阻塞行为/同步类
-
O_NONBLOCK:非阻塞 I/O(对 pipe/终端/socket 影响大,对普通磁盘文件通常意义不大,后面需要时再讲)open("/dev/tty", O_RDONLY | O_NONBLOCK); -
O_SYNC:更强的同步写语义(write 返回前尽量保证数据落到存储设备,性能会下降) -
O_DSYNC:只保证数据(不强制元数据)同步(比 O_SYNC 轻一些) -
O_RSYNC: O_RSYNC标志位指定读请求和写请求之间的同步。该标志位必须和O_SYNC或O_DSYNC一起使用。 -
O_LARGEFILE
文件偏移使用64位整数表示,可以支持大于2GB的文件。64位操作系统中打开文件时,默认使用该参数。
fd = open("a.txt",O_LARGEFILE | O_RDONLY);4) 安全/继承类
-
O_CLOEXEC:执行exec()时自动关闭该 fd,防止泄漏到子进程open("a.txt", O_RDONLY | O_CLOEXEC);
(close execute的缩写)
5) 其他你可能会见到的
-
O_NOCTTY:打开终端设备时不把它变成进程的控制终端 -
O_NOFOLLOW:不跟随符号链接(用于安全场景,避免被 symlink 攻击)
由前面的例子可以明白,这些参数都是位操作符
是可以做|运算把不同操作拼接在一起的小练习
- 只读打开
int fd=open("a.txt",O_RDONLY); if(fd==-1){ perror("open"); }- 覆盖写(不存在就创建,存在就清空)
int fd =open("a.txt",O_WRONLY | O_CREAT | O_TRUNC,0644); if(fd==-1){ perror("open"); }- 追加写日志
int fd=open("a.txt",O_WRONLY | O_APPEND |O_CREAT,0644); if(fd==-1){ perror("open"); }- 只允许“新建”,不允许覆盖
int fd=open("a.txt",O_WRONLY |O_CREAT |O_EXCL,0644);这里额外讲一下,其实是有creat()系统调用的,但是用起来方便,支持的操作也比较少,一般用open就可以
creat函数签名#include <fcntl.h> int creat(const char *pathname, mode_t mode); -
-
Linux系统编程(1)文件Linux系统编程(1)文件操作的层次结构
我将在此持续更新Linux系统编程的教学,有不妥,不正确的地方望看到的人指正
首先对于IO的操作,或者各种IO函数,他应当可以被分为下述的几个部分,我将采用总分的结构,在系统描述每一个部分之前,先对整个linux IO有一个大致的认识
第 0 层:用户态缓冲 I/O(不是系统调用,但最“上层”)
特点:你操作的是
FILE*,数据先进入 用户态缓冲区,再由 libc 在合适时机调用write/read等进入内核。
典型 API:fopen/fclose/fread/fwrite/fgets/fputs/fgetc/fputc/printf/scanf,setvbuf/fflush/ferror/feof
要点:这层决定“行缓冲/全缓冲/无缓冲”,以及很多“看起来一次写入”,底下可能拆成多次系统调用。第 1 层:文件描述符(fd)上的“无缓冲”I/O 系统调用(VFS 入口)
特点:你提供用户缓冲区地址,内核负责把数据在 用户内存
内核 间拷贝,并通过 VFS/页缓存/文件系统把它落到磁盘或读出来。1.1 基础数据读写
-
open/openat/creat,close -
read,write -
lseek(改变文件偏移)
1.2 更“语义化/性能化”的读写
-
定位读写(不改文件偏移):
pread,pwrite -
向量 I/O(scatter/gather):
readv,writev -
按大小截断:
truncate,ftruncate
这一层是“系统编程”的主干:大多数 I/O 都能归结为这些 fd syscalls 的组合。
第 2 层:内核缓冲/页缓存语义相关(同样还是 fd 接口,但语义更“接近内核缓存”)
特点:你不只是“读写字节”,还在控制“什么时候算写成功、什么时候必须落盘、缓存如何使用”。
2.1 同步与持久化
fsync,fdatasync,sync,syncfs
解决“写进页缓存了但还没真正落盘”的一致性/崩溃恢复问题。
2.2 访问模式与预取/淘汰建议(属于“提示/策略”)
-
posix_fadvise(libc 封装,底层可能用fcntl/内核建议路径) -
readahead(Linux 专有,提示预读)
这类接口不一定“立刻改变数据”,但会影响页缓存、预读、回写策略,从而影响性能与时延。
第 3 层:绕开“显式 read/write”的 I/O 路径
3.1 内存映射 I/O(mmap 路径)
特点:文件数据映射到进程虚拟内存,通过缺页异常把页拉进来;写回由回写机制处理。
mmap,munmap,msync(以及mprotect/mlock等相关)
直觉:
read/write是“你显式搬运”,mmap是“让内核分页系统替你搬运”。3.2 零拷贝/内核内搬运(减少 user<->kernel 拷贝)
-
sendfile(文件→socket 常用) -
splice,tee,vmsplice(在 fd 之间搬运/管道相关)
第 4 层:I/O 多路复用与事件驱动(“等 I/O”也是 I/O 的一部分)
特点:面向大量 fd 时,不是忙等,而是让内核告诉你“哪个 fd 可读/可写/出错”。
-
select,pselect -
poll,ppoll -
epoll_create/epoll_ctl/epoll_wait
(扩展:
inotify用于文件系统事件通知,也常被归到“事件 I/O”这一层。)第 5 层:异步 I/O(发起后不阻塞当前线程)
特点:读写请求提交给内核,之后再取完成事件;适合高并发、减少线程阻塞。
-
POSIX AIO:
aio_read/aio_write/aio_error/aio_return(多为库接口,底层实现依系统而异) -
Linux AIO:
io_setup/io_submit/io_getevents(更“Linux 内核味”)
更现代的
io_uring也属于这一类(但它比 Love 那本书年代新很多,书里大概率没有重点展开)。第 6 层:控制类系统调用(不直接搬运数据,但决定“怎么 I/O”)
6.1 文件描述符控制与状态
-
fcntl:设置/获取 fd 标志(如O_NONBLOCK)、记录锁(F_SETLK等)、复制 fd 等 -
dup/dup2/dup3
6.2 设备/驱动专用控制面(“像 I/O,但本质是控制”)
ioctl:字符设备/块设备/终端/网络设备等大量控制都走它
第 7 层:命名空间与元数据 I/O(“文件系统操作”,不等于数据读写)
特点:改的是目录项、inode 元数据、权限、链接关系;不直接等价于读写文件内容。
-
元数据查询:
stat/fstat/lstat -
权限属主:
chmod/fchmod,chown/fchown,umask,access -
链接与重命名:
link/unlink/symlink/readlink,rename -
目录与节点:
mkdir/rmdir,mknod -
目录读取:
getdents(系统调用层),上层常用opendir/readdir(库封装)
总体结构如下
┌───────────────────────────────────────────────────────────────────────────┐ │ USER SPACE │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Level 0: User-mode Buffering (glibc FILE*) │ │ │ │ │ │ │ │ fopen / fread / fwrite / printf │ │ │ │ ┌──────────────────────────┐ ┌──────────────────────────────┐ │ │ │ │ │ User Buffer (glibc) │ │ Line / Full Buffering │ │ │ │ │ └──────────────────────────┘ └──────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ ├──────────────────── SYSTEM CALL INTERFACE / BOUNDARY ─────────────────────┤ │ │ │ KERNEL SPACE │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Level 1: FD Standard Syscalls & VFS Entry │ │ │ │ │ │ │ │ open / read / write │ │ │ │ ┌───────────────────────┐ ┌───────────────────────────────┐ │ │ │ │ │ User → Kernel Copy │ │ VFS (file / inode / dentry) │ │ │ │ │ └───────────────────────┘ └───────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌───────────────────────┐ ┌──────────────────────────┐ ┌─────────┐ │ │ │ Level 3: Zero-copy │ │ Level 2: Page Cache │ │ Level 4 │ │ │ │ mmap / sendfile / │ │ fsync / fdatasync │ │ epoll │ │ │ │ splice │ │ readahead │ │ poll │ │ │ └───────────────────────┘ └──────────────────────────┘ └─────────┘ │ │ │ │ │ │ │ │ ▼ ▼ │ │ ┌──────────────────────────┐ ┌───────────────┐ │ │ │ Level 5: Async I/O │ │ Level 6: │ │ │ │ AIO / io_uring │ │ fcntl / ioctl │ │ │ └──────────────────────────┘ └───────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Level 7: Metadata & Namespace Ops │ │ │ │ │ │ │ │ stat / chmod / rename │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ Block Device Drivers & I/O Scheduler │ │ │ │ (bio / request / elevator) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ │ ↓ ↓ ↓ │ │ │ │ ┌─────────────────────────────────────────────────────────────────────┐ │ │ │ HARDWARE STORAGE (Disk / SSD / NVMe) │ │ │ └─────────────────────────────────────────────────────────────────────┘ │ │ │ └───────────────────────────────────────────────────────────────────────────┘用户操作缓冲IO结构
┌──────────────────────────────────────────────────────────────────────────────┐ │ USER SPACE │ │ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ Level 0: User-mode Buffering (glibc FILE*) │ │ │ │ │ │ │ │ Application calls: │ │ │ │ fopen(path, mode) │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ glibc fopen implementation │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ │ │ FILE struct │ │ │ │ │ │ │ │ │ │ │ │ int fd ────────────────┐ │ │ │ │ │ │ User-mode Buffer │ char* base / ptr / size │ │ │ │ │ │ Flags (mode, buffering) │ line / full / none │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ └── returns FILE* fp │ │ │ │ │ │ │ │ Application I/O: │ │ │ │ │ │ │ │ fwrite(ptr, size, nmemb, fp) │ │ │ │ fread (ptr, size, nmemb, fp) │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ │ │ User-mode Buffer (glibc memory) │ │ │ │ │ │ │ │ │ │ │ │ [ data ][ data ][ data ][ .... ][ free space ] │ │ │ │ │ │ │ │ │ │ │ │ Data copied between application buffer and libc buffer │ │ │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ (buffer full / fflush) │ │ │ │ ▼ │ │ ├──────────────────────── SYSTEM CALL INTERFACE / BOUNDARY ────────────────────┤ │ │ │ KERNEL SPACE │ │ │ │ ┌───────────────────────────────┐ ┌───────────────────────────────┐ │ │ │ open(path, flags, mode) │ │ write(fd, buffer, count) │ │ │ │ System Call │ │ read (fd, buffer, count) │ │ │ └───────────────────────────────┘ └───────────────────────────────┘ │ │ │ │ │ │ ▼ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ Kernel Page Cache (VFS) │ │ │ │ │ │ │ │ - file / inode / dentry │ │ │ │ - page cache │ │ │ │ - readahead / writeback │ │ │ └────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ Block Device Driver │ │ │ │ │ │ │ │ - bio / request │ │ │ │ - I/O scheduler │ │ │ └────────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌────────────────────────────────────────────────────────────────────────┐ │ │ │ HARDWARE STORAGE (Disk / SSD / NVMe) │ │ │ └────────────────────────────────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────────────────┘其大致结构基本如下,后续,将对每一个模块进行详细系统的论述
-
-
C++中的initializer_list获取列表长度并用于模板参数的编译期计算小示例@FrozenLemonTee 在 C++中的initializer_list获取列表长度并用于模板参数的编译期计算小示例 中说:
clang最小可编译的版本和标准是clang3.4.1+cpp11:

这样
-
C++中的initializer_list获取列表长度并用于模板参数的编译期计算小示例我觉得应该写template<typename T,size_t t>好一点,如果这个数组非常大,那被转换到int有符号数可能溢出
-
C++中的initializer_list获取列表长度并用于模板参数的编译期计算小示例这个在C++17后的版本是可以通过的,但是C++14以前应该是没法编译通过的吧,对于兼容性来讲还是有一些欠缺