Linux 应用编程
Linux Application Programming
<stdio.h>
英语:standard input/output header,标准输入/输出头文件)是C语言为输入输出提供的标准库头文件,其前身是迈克·莱斯克20世纪70年代编写的“可移植输入输出程序库”
类型
标识符 | 说明 |
---|---|
size_t | 用于表示大小的数据类型 |
FILE | 记录流的信息的数据结构 |
fpos_t | 用于在文件中定位的数据类型 |
宏
标识符 | 说明 |
---|---|
NULL | 空指针 |
_IOFBF | 用于控制缓冲方式的参数 |
_IOLBF | |
_IONBF | |
BUFSIZ | setbuf 函数使用的缓冲区大小 |
EOF | 文件结尾标志 |
FOPEN_MAX | 实现保证能够同时打开的文件数量 |
FILENAME_MAX | 实现保证支持的最长文件名的字符数 |
L_tmpnam | 存放tmpnam 函数生成的临时文件名的char 数组的大小 |
SEEK_CUR | 用于控制文件定位方式的参数 |
SEEK_END | |
SEEK_SET | |
TMP_MAX | 实现保证能生成的临时文件名个数 |
标准流
标识符 | 说明 | fd |
---|---|---|
stdin | 标准输入流 | 0 |
stdout | 标准输出流 | 1 |
stderr | 标准错误流 | 2 |
函数
类型 | 函数原型 |
---|---|
文件操作 | int remove(const char *filename); |
int rename(const char *old, const char *new); | |
FILE *tmpfile(void); | |
char *tmpnam(char *s); | |
int fclose(FILE *stream); | |
int fflush(FILE *stream); | |
FILE *fopen(const char * restrict filename, const char * restrict mode); | |
FILE *freopen(const char * restrict filename, const char * restrict mode, FILE * restrict stream); | |
void setbuf(FILE * restrict stream, char * restrict buf); | |
int setvbuf(FILE * restrict stream, char * restrict buf, int mode, size_t size); | |
格式化输入输出 | int fprintf(FILE * restrict stream, const char * restrict format, ...); |
int fscanf(FILE * restrict stream, const char * restrict format, ...); | |
int printf(const char * restrict format, ...); | |
int scanf(const char * restrict format, ...); | |
int snprintf(char * restrict s, size_t n, const char * restrict format, ...); | |
int sprintf(char * restrict s, const char * restrict format, ...); | |
int sscanf(const char * restrict s, const char * restrict format, ...); | |
int vfprintf(FILE * restrict stream, const char * restrict format, va_list arg); | |
int vfscanf(FILE * restrict stream, const char * restrict format, va_list arg); | |
int vprintf(const char * restrict format, va_list arg); | |
int vscanf(const char * restrict format, va_list arg); | |
int vsnprintf(char * restrict s, size_t n, const char * restrict format, va_list arg); | |
int vsprintf(char * restrict s, const char * restrict format, va_list arg); | |
int vsscanf(const char * restrict s, const char * restrict format, va_list arg); | |
字符输入输出 | int fgetc(FILE *stream); |
char *fgets(char * restrict s, int n, FILE * restrict stream); | |
int fputc(int c, FILE *stream); | |
int fputs(const char * restrict s, FILE * restrict stream); | |
int getc(FILE *stream); | |
int getchar(void); | |
int putc(int c, FILE *stream); | |
int putchar(int c); | |
int puts(const char *s); | |
int ungetc(int c, FILE *stream); | |
直接输入输出 | size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream); |
size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict stream); | |
文件内定位 | int fgetpos(FILE * restrict stream, fpos_t * restrict pos); |
int fseek(FILE *stream, long int offset, int whence); | |
int fsetpos(FILE *stream, const fpos_t *pos); | |
long int ftell(FILE *stream); | |
void rewind(FILE *stream); | |
错误处理 | void clearerr(FILE *stream); |
int feof(FILE *stream); | |
int ferror(FILE *stream); | |
void perror(const char *s); |
open()
open()调用既能打开一个业已存在的文件,也能创建并打开一个新文件。
若发生错误,则返回−1,并将 errno 置为相应的错误标志。
1 |
|
open 函数
open() 是一个系统调用函数,用于在 Linux 系统中打开或者创建一个文件。
1
2
3
4
5
6
#include <sys/types.h> // 使用这个函数需导入以下头文件
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);fopen函数
fopen() 函数是标准 C 库中用来打开文件的函数。
1
2
#include <stdio.h> // 使用此函数需导入此头文件
FILE *fopen(const char *filename, const char *mode);
fclose()
关闭流 stream。刷新所有的缓冲区(相当于做了fflush)。
fclose函数的作用
清空相关的缓冲区
以写文件为例,程序会先将文本信息存入缓冲区中,然后根据缓冲区刷新指令或者等缓冲区满了以后才向文件写入,那么我们如果不使用fclose()关闭流,就有可能有部分文本信息在缓冲区中没有写入到文本文件中,从而导致文本信息缺失.但是这东西看运气.
释放内存
我们知道,如果我们不手动关闭这个流,系统会维持打开的状态,维持当然需要消耗一定的内存资源,本着苍蝇再小也是块肉的原则当然是要把它关掉了,其实如果有很多流在使用后没有关闭,这部分内存资源也是十分可观的.
多次fclose()会发生什么
能不能多次对同一个文件描述符调用fclose?
答案是不行的。
为啥不行,我们需要看看fclose到底干了什么
fclose关闭的是一个文件流,当调用fclose时,将会使用户态(C库)缓冲区中的数据刷新到内核区域(或通过socket发送,或者仅仅写回硬盘)。
于此同时,fclose会调用free来释放fp所指向的用户态buffer。因此,如果多次调用fclose,会多次调用free来释放一块已经被释放的区域,而这个动作是危险的。
很多解释是说,因为一旦被释放之后,这块区域就属于未知区域。有可能系统已经把该区域分给了其他进程,所以不能多次fclose。但是我看过Linux关于close的内核源码,发现并不是这样,当第一次调用close时,将会把对应的文件描述符数组flip[fp] = NULL。如果再调用close时,会在前面判断该项是否为null,如果为null,return -EINVAL;因此,多次调用fclose时会报错,但不会影响系统其他进程。
*调用fclose之后,FILE fp会变成NULL吗?
不会,没有什么机制把fp置空的动作,只是这个时候fp所指向的区域已经不再有效。
fflush()
刷新流 stream 的输出缓冲区,将数据刷新到内核缓冲。
描述
C 库函数 int fflush(FILE *stream) 刷新流 stream 的输出缓冲区。
声明
下面是 fflush() 函数的声明。
1 |
|
参数
- stream – 这是指向 FILE 对象的指针,该 FILE 对象指定了一个缓冲流。
返回值
如果成功,该函数返回零值。如果发生错误,则返回 EOF,且设置错误标识符(即 feof)。
fsync()
fsync函数同步内存中所有已修改的文件数据到储存设备。
1 |
|
perror()
errno是一个错误编号,本质上是一个整形的变量,每个错误对应着一个错误编号,当你调用”某些“函数出错时,这个函数会更新errno的值。
也就是说,并不是所有的函数都会将错误编号输出到errno变量中。
描述
C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
声明
下面是 perror() 函数的声明。
1 |
|
参数
- str – 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。
<stdlib.h>
常量
stdlib.h
中定义的常量:
名字 | 值 | 描述 |
---|---|---|
NULL | 一般定义为0 , 或0L , 或(void*)0 | 表示空指针常量的宏; 换句话说,一个常量用来表示一个总是指向无效的内存地址的指针值。 |
EXIT_FAILURE | 一个非0 值 | 用来指示程序失败的结束,一般用于exit() . |
EXIT_SUCCESS | 0 | 用来指示程序成功的结束,一般用于exit() . |
RAND_MAX | >= 32767 | 函数rand() 所能返回的最大的值. |
MB_CUR_MAX | 当前locale中多字节字符的最大字节数目 |
数据类型
stdlib.h
中定义的数据类型:
名字 | 描述 |
---|---|
size_t | 算子sizeof 返回结果的数据类型,实际上是无符号整型. |
div_t , ldiv_t ,lldiv_t | 函数div , ldiv , lldiv 的返回结果的数据类型,实际上是包含两个整数的结构类型. |
函数
stdlib.h
中声明的库函数可分为六类:类型转换、伪随机数、动态内存分配与回收管理、进程控制、搜索及排序、简单数学。
名字 | 描述 |
---|---|
类型转换 | |
atof | 把字符串转换为双精度浮点数。相当于strtod(s, (char**)NULL) . |
atoi | 把字符串转换为整型. 相当于(int)strtol(s, (char**)NULL, 10) . |
atol | 把字符串转换为长整型. Equivalente a strtol(s, (char**)NULL, 10) . |
atoll | 把字符串转换为长长整型. Equivalente a strtol(s, (char**)NULL, 10) . 这是C99新增加的库函数。 |
strtod | 把字符串转换为双精度浮点数,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtof | 把字符串转换为单精度浮点数,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtold | 把字符串转换为长双精度浮点数,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtol | 把字符串转换为长整型,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtoll | 把字符串转换为long long int ,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtoul | 把字符串转换为无符号长整形,检查结果是否溢出,并返回字符串不能转换部分的地址. |
strtoull | 把字符串转换为unsigned long long int ,检查结果是否溢出,并返回字符串不能转换部分的地址. |
伪随机数序列生成 | |
rand | 返回在0到RAND_MAX之间的伪随机数. 不接受参数作为随机数种子,因此产生的伪随机数列相同,有利于程序调试。 |
srand | 初始化rand() 接受无符号整型参数作为伪随机数种子.如果种子相同,伪随机数列也相同。 |
内存的分配与释放 | |
aligned_alloc | 边界对齐的动态内存分配. |
calloc | 数组的动态内存分配,且初始化为全零 |
malloc | 动态内存分配,其内容不初始化 |
realloc | 释放老的动态内存块,按照给出的尺寸分配新的动态内存块,老的内存块的内容尽量复制到新的内存块 |
free | 系统释放动态分配的内存. 如果是空指针,则无动作发生;如果指针所指不是动态分配的内存块或者是已释放的内存块,则行为是未定义的。 |
进程控制/与运行环境的沟通 | |
abort | 导致程序非正常的结束,各种流缓冲区与临时文件直接放弃。实际上抛出raise(SIGABRT) ,缺省的信号处理行为是使用退出代码3执行终止(terminate)操作。如果SIGABRT 被捕捉且信号处理程序不返回,则程序将不终止. |
atexit | 登记一个函数,当程序使用exit 正常退出时被登记的函数自动被调用. |
exit | 程序正常终止。首先atexit() 登记的函数按照登记的逆序被调用;如果多次调用atexit 登记了多个函数,按照登记的逆序调用这些函数。如果一个函数被登记了多次,则程序正常退出时该函数也将被调用多次。然后所有缓冲区中的数据被写回(flushed);所有打开着的流被关闭;tmpfile 函数创建的文件被删除。最后,控制权返回给调用环境,返回数值表示程序返回时的状态,0表示EXIT_SUCCESS , 1表示EXIT_FAILURE . |
at_quick_exit | 登记一个函数,当程序使用quick_exit 正常退出时被登记的函数自动被调用. |
_Exit | 程序正常终止, 但atexit() , at_quick_exit() , signal() 登记的函数不被调用; 打开的流、文件是否被关闭,由编译器的实现者决定 |
getenv | 获得某一个环境变量的字符串值,如果该环境变量不存在,返回NULL . |
quick_exit | 程序正常终止, 但atexit() , 登记的函数不被调用; at_quick_exit() 登记的函数按登记顺序的逆序被调用。 |
system | 把参数作为外部环境的命令执行。 如果参数为空,则判断外部环境是否有命令解释器。 |
搜索与排序 | |
bsearch | 折半搜索. |
qsort | 快速排序. |
整数算术 | |
abs, labs, llabs | 计算整数的绝对值. |
div, ldiv, lldiv | 计算整数除法的商与余数. |
多字节字符/宽字符转换 | |
mblen | 计算多字节字符的长度并确定是否为有效字符 . |
mbtowc | 多字节字符转换为宽字符. |
wctomb | 宽字符转换为多字节字符. |
多字节字符串/宽字符串转换 | |
mbstowcs | 多字节字符串转换为宽字符串. |
wcstombs | 宽字符串转换为多字节字符串. |
atof() str 转 double
1 |
|
函数说明
atof()会扫描参数nptr字符串,跳过前面的空格字符,直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时(‘\0’)才结束转换,并将结果返回。参数nptr字符串可包含正负号、小数点或E(e)来表示指数部分,如123.456或123e-2。
相关函数
strtod(nptr,(char**)NULL)
gcvt
浮点型数转换为字符串,取四舍五入
atoi() str 转 int
1 |
|
将字符串参数str转换为整数(int 类型)。
在字符串的开头,atoi() 方法忽略所有空格,转换紧跟在空格之后的单词,然后在遇到第一个非数字单词时停止。atoi() 模块实现了字符串的整数描述。
参数
- str – 这是整数的字符串表示形式。
返回值
此函数将转换后的整数作为 int 值返回。如果无法执行有效的转换,则返回零。
实例
1 |
|
atoi 输入参数少,使用简单,在一些明确知道数值范围的情况下使用比较合适,转换范围相较于strtol(64位环境下)比较小,而且也不知道返回值为0时是转换失败还是转换成功。
strtol 输入参数相对较多,用法也更灵活支持转换成不同进制,能够明确的知道输入数据转换出错的位置和错误原因,相比较与atoi,对于0这种特殊的字符串,能更清楚的知道是转换成功的得到的还是转换失败得到的。
综上,strtol使用更灵活,功能更强大,且可以判断转换结果的正确性,建议字符串转换为整型时使用strtol
atol() str 转 long int
gcvt() double 转 str
1 |
|
strtod() str 转 double
1 |
|
strtol() str 转long int 不同进制
C 库函数 strtol()
把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base 必须介于 2 和 36(包含)之间,或者是特殊值 0。
strtol() 方法将具有字符串数据类型的值转换为长整数。strtol() 方法跳过字符串开头的所有空白字符,将连续的字符转换为部分的一部分,然后在到达第一个非数字字符时终止。
strtol() 函数对异常的返回可以设置errno,从而可以发现异常的返回。
声明
1 |
|
参数
- str – 要转换为长整数的字符串。
- endptr – 对类型为 char* 的对象的引用,其值由函数设置为 str 中数值后的下一个字符。
- base – 基数,必须介于 2 和 36(包含)之间,或者是特殊值 0。
返回值
该函数返回转换后的长整数,如果没有执行有效的转换,则返回一个零值。
实例
下面的实例演示了 strtol() 函数的用法。
1 |
|
strtoul() str 转 unsigned long int
toascii() int 转 ascii
1 |
|
tolower() 大写 转 小写
toupper() 小写 转 大写
strtok() 用分隔符分解字符串
描述
C 库函数 strtok()
分解字符串 str 为一组字符串,delim 为分隔符。
声明
下面是 strtok() 函数的声明。
1 |
|
参数
- str – 要被分解成一组小字符串的字符串。输入NULL则是从上次结束的位置继续查找。
- delim – 包含分隔符的 C 字符串。
返回值
该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。
实例
1 |
|
让我们编译并运行上面的程序,这将产生以下结果:
1 |
|
<netinet/in.h>
网络中的数据传输是按 大端 数据格式来传输的。
所以在一些设备上需要进行大小端转换,也就是所谓的网络字节序转换。
htonl() htons() ntohl() ntohs()
1 |
|
INET_ADDRSTRLEN IPv4 str len
1 |
|
<arpa/inet.h>
inet_addr() str 转 in_addr
1 |
|
如果参数char *cp无效,函数返回-1(INADDR_NONE),
这个函数在处理地址为255.255.255.255时也返回-1,
255.255.255.255是一个有效的地址,不过inet_addr无法处理;
建议使用 inet_pton()
替代此函数
inet_aton() str 转 in_addr
inet_ntoa
是一个用于将IPv4地址的二进制值转换为点分十进制格式的函数。它的函数原型如下:
1 |
|
其中,
cp
是一个指向以点分十进制格式表示的IPv4地址的字符串,inp
是一个指向in_addr
结构体的指针,该结构体包含了转换后的二进制值。如果转换成功,
inet_aton
返回非0值,否则返回0值。需要注意的是,如果提供的字符串地址不合法,那么inet_aton
返回0值,而不会设置errno
。
inet_aton
已经被弃用,推荐使用inet_pton
函数代替。
inet_ntoa() in_addr 转 str
inet_ntoa
是一个用于将IPv4地址的二进制值转换为点分十进制格式的函数。它的函数原型如下:
1 |
|
其中,
in
是一个指向in_addr
结构体的指针,该结构体包含了IPv4地址的二进制值。
inet_ntoa
返回一个指向以点分十进制格式表示的IPv4地址的字符串,如果转换失败,则返回NULL
。
inet_ntoa
已经被弃用,推荐使用inet_ntop
函数代替。
inet_pton() str 转 in_addr
1 |
|
函数中的p和n分别代表表达式(presentation)和数值(numeric)
这里的表达式一般是指字符串,数值对于IPv4就是uint32
inet_pton()
转换网络主机地址表达式(presentation)为二进制数值(numeric),支持IPv4 to IPv6,线程安全,可重入。
参数
af
– 仅支持AF_INET
或AF_INET6
,IPv4 IPv6src
– 表达式(presentation)字符串dst
– 数值(numeric)指针
返回值
inet_pton () 成功返回1(网络地址转换成功)。如果src不包含表示指定地址族中有效网络地址的字符串,则返回 0 。如果af不包含有效地址族,则返回 -1 并将errno设置为EAFNOSUPPORT。
示例
1 |
|
inet_ntop() in_addr 转 str
1 |
|
inet_ntop()
函数转换二进制数值(numeric)为网络主机地址表达式(presentation),支持IPv4 to IPv6,线程安全,可重入。
参数
af
– 仅支持AF_INET
或AF_INET6
,IPv4 or IPv6src
– 数值(numeric)指针dst
– 表达式(presentation)字符串size
– 此缓冲区可用字节数
返回值
成功时,inet_ntop() 返回一个指向 dst
的非空指针。如果有错误则返回NULL
,errno设置为指示错误。
示例
1 |
|
1 |
|
Socket
socket 是一种 IPC 方法,它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据。
概述
使用 socket()系统调用能够创建一个 socket,它返回一个用来在后续系统调用中引用该 socket 的文件描述符。
1 |
|
socket domain
socket 存在于一个通信 domain 中,它确定:
- 识别出一个 socket 的方法(即 socket“地址”的格式);
- 通信范围(即是在位于同一主机上的应用程序之间还是在位于使用一个网络连接起来的不同主机上的应用程序之间)。
现代操作系统至少支持下列 domain
- UNIX (AF_UNIX) domain 允许在同一主机上的应用程序之间进行通信。
- IPv4 (AF_INET) domain 允许在使用因特网协议第 4 版(IPv4)网络连接起来的主机上的应用程序之间进行通信。
- IPv6 (AF_INET6) domain 允许在使用因特网协议第 6 版(IPv6)网络连接起来的主机上的应用程序之间进行通信。
Domain | 执行的通信 | 应用程序间的通信 | 地址格式 | |
---|---|---|---|---|
AF_UNIX | 内核中 | 同一主机 | 路径名 | |
AF_INET | 通过 IPv4 | 通过 IPv4 网络连接起来的主机 | 32 位 IPv4 地址+16 位端口号 | |
AF_INET6 | 通过 IPv6 | 通过 IPv6 网络连接起来的主机 | 128 位 IPv6 地址+16 位端口号 |
socket type
每个 socket 实现都至少提供了两种 socket:STREAM (流)和 DGRAM (数据报)。这两种 socket 类型在 UNIX 和 Internet domain 中都得到了支持。
属性 | socket 类型 | socket 类型 |
---|---|---|
STREAM | DGRAM | |
可靠地递送? | yes | no |
面向连接? | yes | no |
消息边界保留? | no | yes |
SOCK_STREAM (流)
流 socket(SOCK_STREAM)提供了一个可靠的双向的字节流通信信道。
- 可靠的:表示可以保证发送者传输的数据会完整无缺地到达接收应用程序(假设网络链接和接收者都不会崩溃)或收到一个传输失败的通知。
- 双向的:表示数据可以在两个 socket 之间的任意方向上传输。
- 字节流:表示与管道一样不存在消息边界的概念(参见 44.1 节)。
一个流 socket 类似于使用一对允许在两个应用程序之间进行双向通信的管道,它们之间
的差别在于(Internet domain)socket 允许在网络上进行通信。流 socket 的正常工作需要一对相互连接的 socket,因此流 socket 通常被称为面向连接的。
SOCK_DGRAM (数据报)
允许数据以被称为数据报的消息的形式进行交换。在数据报 socket 中,消息边界得到了保留,但数据传输是不可靠的。消息的到达可能是无序的、重复的或者根本就无法到达。
数据报 socket 是更一般的无连接 socket 概念的一个示例。与流 socket 不同,一个数据报socket 在使用时无需与另一个 socket 连接。(在 56.6.2 节中将会看到数据报 socket 可以与另一个 socket 连接,但其语义与连接的流 socket 是不同的。)
在 Internet domain 中,数据报 socket 使用了用户数据报协议(UDP),而流 socket 则(通常)使用了传输控制协议(TCP)。
一般来讲,在称呼这两种 socket 时不会使用术语“Internet domain 数据报 socket”和“Internet domain 流 socket”,而是分别使用术语“UDP socket”和“TCP socket”。
socket 系统调用
关键的 socket 系统调用包括以下几种。
- **socket()**系统调用
- 创建一个新 socket。
- **bind()**系统调用
- 将一个 socket 绑定到一个地址上。
- 通常,服务器需要使用这个调用来将其 socket 绑定到一个众所周知的地址上使得客户端能够定位到该 socket 上。
- **listen()**系统调用
- 允许一个流 socket 接受来自其他 socket 的接入连接。
- **accept()**系统调用
- 在一个监听流 socket 上接受来自一个对等应用程序的连接,并可选地返回对等 socket 的地址。
- **connect()**系统调用
- 建立与另一个 socket 之间的连接。
socket I/O 可以使用传统的 read()和 write()系统调用或使用一组 socket 特有的系统调用(如send()、recv()、sendto()以及 recvfrom())来完成。
在默认情况下,这些系统调用在 I/O 操作无法被立即完成时会阻塞。通过使用 fcntl() F_SETFL 操作(5.3 节)来启用 O_NONBLOCK 打开文件状态标记可以执行非阻塞 I/O。
Note
Misc.
assert() 断言
assert()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句,它的作用是终止程序以免导致严重后果,同时也便于查找错误。
所需头文件:#include <assert.h>
报告错误的条件:
1 |
|
参数:Expression (including pointers) that evaluates to nonzero or 0.(表达式【包括指针】是非零或零)
原理:assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
示例程序:
1 |
|
总结
在函数开始处检验传入参数的合法性
每个assert只检验一个条件,因为同时检验多个条件时,如果断言失败,无法直观的判断是哪个条件失败
不能使用改变环境的语句,因为assert只在DEBUG个生效,如果这么做,会使用程序在真正运行时遇到问题
assert和后面的语句应空一行,以形成逻辑和视觉上的一致感
有的地方,assert不能代替条件过滤
ASSERT只有在Debug版本中才有效,如果编译为Release版本则被忽略掉。
何时需要使用断言
- 可以在预计正常情况下程序不会到达的地方放置断言:ASSERT( FALSE );
- 断言可以用于检查传递给私有方法的参数。(对于公有方法,因为是提供给外部的接口,所以必须在方法中有相应的参数检验才能保证代码的健壮性)
- 使用断言测试方法执行的前置条件和后置条件
- 使用断言检查类的不变状态,确保任何情况下,某个变量的状态必须满足。(如age属性应大于0小于某个合适值)
什么地方不要使用断言
断言语句不是永远会执行,可以屏蔽也可以启用
- 不要使用断言作为公共方法的参数检查,公共方法的参数合法性永远都要执行
- 断言语句不可以有任何边界效应,不要使用断言语句去修改变量和改变方法的返回值
Note
sync、fsync、fdatasync、fflush对比
c库缓冲 —–fflush—–> 内核缓冲 —–fsync—–> 磁盘
void sync(void) 函数只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。通常称为update的系统守护进程会周期性地(一般每隔30秒)调用sync函数。这就保证了定期冲洗内核的块缓冲区。命令sync(1)也调用sync函数。
int fsync(int fd) 函数是系统提供的系统调用。只对由文件描述符filedes指定的单一文件起作用,并且等待写磁盘操作结束,然后返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保将修改过的块立即写到磁盘上。fsync还会同步更新文件的属性,即inode部分。
int fdatasync(int fd) 函数类似于fsync,但它只影响文件的数据部分。它并不会更新文件的属性。
int fflush(FILE *stream):标准I/O函数(如:fread,fwrite)会在内存建立缓冲,该函数刷新内存缓冲,将内容写入内核缓冲,要想将其写入磁盘,还需要调用fsync(先调用fflush后调用fsync,否则不起作用)。fflush接受一个参数
FILE *
。它还可以刷新标准输入输出。fclose函数在关闭文件之前也会做flush操作
关于缓冲.
C标准库的I/O缓冲区有三种类型:全缓冲、行缓冲和无缓冲。当用户程序调用库函数做写操作时, 不同类型的缓冲区具有不同特性。
全缓冲:如果缓冲区写满了就写回内核。常规文件通常是全缓冲的。
行缓冲:如果用户程序写的数据中有换行符就把这一行写回内核,或者如果缓冲区写满了就写回内核。标准输入和标准输出对应终端设备时通常是行缓冲的。
无缓冲:用户程序每次调库函数做写操作都要通过系统调用写回内核。标准错误输出通常是无缓冲的,这样用户程序产生的错误信息可以尽快输出到设备。
虽然write系统调用位于C标准库I/O缓冲区的底层,被称为Unbuffered I/O函数,但在write的底层也可以分配一个内核I/O缓冲区,所以write 也不一定是直接写到文件的,也可能写到内核I/O缓冲区中。
可以使用fsync函数同步至磁盘文件,至于究竟写到了文件中还是内核缓冲区中对于进程来说是没有差别的,如果进程A和进程B打开同一文件,进程A写到内核I/O缓冲区中的数据从进程B也能读到,因为内核空间是进程共享的,而c标准库的I/O缓冲区则不具有这一特性,因为进程的用户空间是完全独立的。