首页 > 代码库 > 《unix环境高级编程》 读书笔记 (3)

《unix环境高级编程》 读书笔记 (3)

近来读书,做些笔记,来年好翻翻。
本文所使用的操作系统为 CentOS7.0,如果不想装双系统的可以装虚拟机,可以参考这里:
http://blog.csdn.net/alex_my/article/details/38142229


Standard I/O library


1 byte oriented or wide(multibyte) oriented

标准IO文件流可以是单字节或者是多字节字符集,流定向决定了字符串读写时是单字节还是多字节。当流建立的时候,没有定向。当一个多字节IO函数使用在流上面的时候,流被设置为宽(multibyte)定向;当一个字节IO函数使用在流上面的时候,流被设置为字节(single-byte)定向,这边所提到的流是尚未定义定向的流。

#include <wchar.h>

int fwide(FILE *stream, int mode);

如果mode为负数,则尝试设置流为字节定向。
如果mode为正数,则尝试设置流为宽定向。
如果mode为0,则不尝试设置流定向,当时仍会返回流的定向。

fwide不能改变已经定向的流定向,而且返回值并不是和以往一样,0表示成功,-1表示失败。在调用fwide的时候可以清楚errno值,并在调用结束之后检查errno,借此来检查是否发生了错误。


2 buffering

标准库中提供了三种类型的缓冲:全缓冲,行缓冲,不缓冲。

以下函数可以用于更改缓冲类型:

#include <stdio.h>

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

setbuf: 用于开关缓冲,当将buf设置为nullptr时,关闭缓冲,或者buf指向一个长度为BUFSIZ(stdio.h)的缓冲区。

setvbuf: 设置所需的缓冲类型

mode可以选择的值如下:

_IOFBF: 全缓冲
_IOLBF: 行缓冲
_IONBF: 不缓冲

如果指定一个不缓冲的流,则可以忽略buf和size参数。
如果指定全缓冲或者行缓冲,则可以指定一个缓冲区和长度,如果流的buf也设置为nullptr, 则标准IO库自动分配一个BUFSIZE长度的缓冲区。

#include <stdio.h>

int fflush(FILE* stream);

fflush: 强制刷新一个流,该流所有未写的数据都被传送到内核中,当stream为nullptr时,所有打开的输出流都被刷新。


3 opening a stream

#include <stdio.h>

FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);

fopen: 打开一个文件,并返回一个与之相关联的流
其中,mode可以选择以下字符串:

"r" : 打开一个读文件(可以读的文件,下同), 流从文件头开始。
"r+": 打开一个读写文件,流从文件头开始。
"w" : 打开一个写文件,并且文件长度截短为0,流从文件头开始。
"w+": 打开一个读写文件,如果文件不存在,则创建,如果存在,则文件长度截短为0,流从文件头开始。
"a" : 打开一个写文件,如果文件不存在,则创建,如果存在,流从文件尾开始。
"a+": 打开一个读写文件,如果文件不存在,则创建,如果存在,则读从文件头开始,写从文件尾开始。
 
经常可以看见添加符号‘b‘的,不过POSIX系统一般都忽略,包括Linux。

fdopen: 通过文件描述符返回一个与之关联的流。
其中,mode可选的字符串与fopen相同,但不会有"文件不存在,则创建"之类效果,也不会截短,因为有效的fd保证了文件已   经存在了。

当fdopen创建的流被关闭的时候,文件描述符fd也会被关闭。这边会有一些共享内存对象的问题。

另外,mode与获取fd时选择的mode要兼容,假设使用open获取fd时,设置为readonly,则此时设置"r+"会冲突,具体看示例 test5.1, 对应如下:

r  : O_RDONLY
r+ : O_RDWR
w  : O_WRONLY | O_CREAT | O_TRUNC
w+ : O_RDWR | O_CREAT | O_TRUNC
a  : O_WRONLY | O_CREAT | O_APPEND
a+ : O_RDWR | O_CREAT | O_APPEND

并不一定是完全对应的,应该根据实际情况来定,比如下边的示例:

r+ : O_RDWR | O_CREAT

程序用例:
// test5.1.cc

#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

int main()
{
     int fd = open("test5.1.log", O_RDWR | O_CREAT);
     if(fd == -1)
     {
         printf("fd open filed, error[%d].\n", errno);
         return 1;
     }

     printf("fd is %d.\n", fd);
 
     FILE* file = fdopen(fd, "r+");
     if(!file)
     {
          printf("fdopen failed, error[%d].\n", errno);
          return 1;
     }

     printf("fdopen success\n");
     return 0;
}
  
可以尝试将open时候的O_RDWR改为O_RDONLY或者修改fdopen中的r+为r。会报错为无效参数

#include <stdio.h>

int fileno(FILE* stream);

fileno: 返回它的文件描述符。


4 reading and writing a stream

当我们打开一个流之后,可以选择三种unformatted I/O类型进行读写。
-1:每次一个字符的I/O,每次可以读或者写一个字符,如果流是带缓冲的,I/O标准库会处理所有的缓冲。
-2:每次一行的I/O,可以使用fgets/fputs来读/写一行,每行以一个换行符终止。
-3:直接I/O,可以使用fread/fwrite支持这种I/O。

-1:character-at-a-time I/O

reading:

#include <stdio.h>

int getc(FILE* fp);
int fgetc(FILE* fp);
int getchar();
          return: next character if OK, EOF on end of file or error.

getchar()相当于getc(stdin)
getc: getc被ISO C实现为宏,因此,不能将其作为函数指针传递(或许有编译器将其实现为函数),书上称其不能成为有副作用的表达式(稍后解释)。
fgetc:被实现为一个函数。

副作用的表达式:

#define SQRT(x) (x)*(x)
int sqrt(int x) { return x * x; }

如果x的值为++5,我们想取得的值为 6 * 6 = 36.
那么宏SQRT不能正确表达我们的想法,称为有副作用的表达式。

需要注意的是当文件到达末尾或者发生错误时,都是返回EOF,为了分辨使用以下函数:

#include <stdio.h>

int feof(FILE* stream);
int ferror(FILE* stream);

如果为真,则返回非零整数表示true,返回零表示false

在大部分的实现中,流内部维持着一个状态,通过这个状态来判断是那种错误。
可以通过以下函数来清除这种状态:

#include <stdio.h>
void clearerr(FILE* stream);

还有一种函数时用来将字符塞回到缓冲区的,相当于push back,从后头塞进去。
假设缓冲中有"abcd", 当我们塞入f的时候,它出现在末尾"abcdf"。
一次只能塞入一个字符,且不能塞入EOF,这个字符并不是真正的写到底层文件或者设备中,而是流的缓冲区中。

#include <stdio.h>
int ungetc(int c, FILE* stream);

writing:

#include <stdio.h>

int putc(int c, FILE* stream);
int fputc(int c, FILE* stream);
int putchar(int c);
          return c if OK, EOF on error.

putc通常被实现成宏,而fputc实现为函数。      

-2: line-at-a-time I/O

reading:

#include <stdio.h>
char* gets(char* s);
char* fgets(char* s, int size, FILE* stream);
          return buf if OK, null on end of file or error.

fgets指定读取的长度,如果一行超过这个长度,则只读取部分,然后接着读取这一行。
一行的结束以null字符做为标志。
gets是不建议使用的,因为没有指定长度,很容易overflow。

writing:

#include <stdio.h>

int fputs(const char* s, FILE* stream);
int puts(const char* s);
          return non-negative value if OK, EOF on error

puts到没有像gets()那样强调不要使用,但是,还是最好使用fputs()吧。

程序用例:

#include <stdio.h>
#include <errno.h>

int main(int argc, char* argv[])
{
     int c;
     while((c == getc(stdin)) != EOF)
     {
          if(putc(c, stdout) == EOF)
          {
               printf("putc error[%d].\n", errno);
               return 1;
          }
     }    

     const int MAXLINE = 4096;
     char buf[MAXLINE];
     while(fgets(buf, MAXLINE, stdin) != NULL)
     {
          if(fputs(buf, stdout) == EOF)
          {
               printf("putc error[%d].\n", errno);
               return 1;
          }
     }

     printf("All success.\n");
     return 0;
}


fgetc和fgets效率,依赖于其实现。


5 binary I/O

#include <stdio.h>

size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);

fread: ptr指向欲存放读取进来的数据空间,读取字符数以参数size * nmemb决定。
fwrite: ptr指向欲下乳的数据地址,写入的字符书以参数size * nmemb决定。

程序用例:

#include <stdio.h>
#include <errno.h>
#include <string.h>

const int _name_size = 32;
struct Data
{
     int id;
     int money;
     int imageId;
     char name[_name_size];
};

void FillData(Data* dataArray)
{
     Data* data1 = &dataArray[0];
     data1->id = 100;
     data1->money = 1000;
     data1->imageId = 1000010;
     memcpy(data1->name, "AlexZ", _name_size);

     Data* data2 = &dataArray[1];
     data2->id = 101;
     data2->money = 2000;
     data2->imageId = 1000011;
     memcpy(data2->name, "Hey", _name_size);
}

void PrintfData(const Data* read)
{
     if(!read)
     return;

     printf("id: %d\t money: %d\t imageId: %d\t name: %s\n", 
                         read->id, read->money, read->imageId, read->name);
}

int main(int argc, char* argv[])
{
     // test fwrite

     FILE* fp = fopen("test5.2.log", "w+");
     if(!fp)
     {
          printf("fp fopen failed, error[%d].\n", errno);
          return 1;
     }

     struct Data dataArray[2];
     memset(dataArray, 0, sizeof(Data) * 2);
     FillData(dataArray);

     int ret = fwrite(dataArray, sizeof(Data), 2, fp);
     if(ret != 2)
     {
          printf("fwrite falied. ret[%d]\t error[%d].\n", ret, errno);
          return 1;
     }

     fclose(fp);

     printf("fwrite success.\n");

     // test fread

     FILE* readFp = fopen("test5.2.log", "r");
     if(!readFp)
     {
          printf("readFp fopen failed, error[%d].\n", errno);
          return 1;
     }

     struct Data readArray[2];
     ret = fread(readArray, sizeof(Data), 2, readFp);
     if(ret != 2)
     {
          printf("fread failed. ret[%d]\t error[%d].\n", ret, errno);
          return 1;
     }

     // printf

     printf("print whatever read:\n");
     PrintfData(&readArray[0]);
     PrintfData(&readArray[1]);

     printf("test Over.\n");

     return 0;
}

执行结果:

fwrite success.
print whatever read:
id: 100 money: 1000 imageId: 1000010 name: AlexZ
id: 101 money: 2000 imageId: 1000011 name: Hey
test Over.

fread和fwrite涉及到一个读写不在相同系统上的问题,在一中类型的系统写,在另一种系统读。
这种情况暂时未碰见,不过后边socket倒是也会遇到这个问题。一般都是改变结构体对齐方式来解决。或者是自行填充一个缓冲区。



6 formatted I/O

格式化输出:

#include <stdio.h>

int dprintf(int fd, const char* format, ...);
int vdprintf(int fd, const char* format, va_list ap);

int printf(const char* format, ...);
int fprintf(FILE* stream, const char* format, ...);
int sprintf(char* str, const char* format, ...);
int snprintf(char* str, size_t size, const char* format, ...);

#include <stdarg.h>

int vprintf(const char* format, va_list ap);
int vfprintf(FILE* stream, const char* format, va_list ap);
int vsprintf(char* str, const char* format, va_list ap);
int vsnprintf(char* str, size_t size, const char* format, va_list ap);

转换类型:

d, i: 有符号十进制
o   : 无符号八进制
u   : 无符号十进制
x, X: 无符号十六进制
f, F: double精度浮点数
e, E: 指数格式的double精度浮点数
g, G: 解释为f, F, e, E,取决于被转换的值
a, A: 十六进制指数格式的double精度浮点数
c   : 字符(若带有长度修饰符l,则为宽字符)
s   : 字符串(若带有长度修饰符l,则为宽字符)
p   : 指向void的指针
n   : 将到目前为止,把缩写的字符数写入到指针所指向的无符号整型中
C   : 相当于 lc
S   : 相当于 ls

格式化输出:

#include <stdio.h>

int scanf(const char* format, ...);
int fscanf(FILE* stream, const char* format, ...);
int sscanf(const char* str, const char* format, ...);

#include <stdarg.h>

int vscanf(const char* format, va_list ap);
int vsscanf(const char* str, const char* format, va_list ap);
int vfscanf(FILE* stream, const char* format, va_list ap);

转换类型:

d : 有符号十进制,基数为10
i : 有符号十进制,技术由输入格式决定
o : 无符号八进制(输入可选的有符号) 
u : 无符号十进制,技术为10(输入可选的有符号)
x : 无符号十六进制(输入可选的有符号)
a, A, e, E, f, F, g, G: 浮点数
c : 字符(若带有长度修饰符l,则为宽字符)
s : 字符串(若带有长度修饰符l,则为宽字符)
[ : 匹配列出的字符序列,以]终止
[^: 匹配除列出的字符序列以外的序列,以]终止
p : 指向void的指针
n : 将到目前为止读取的字符数写入到指针所指向的无符号整型中
C : 宽字符,相当于 lc
S : 宽字符串,相当于 ls


7 memory stream

#include <stdio.h>

FILE* fmemopen(void*buf, size_t size, const char* mode);
FILE* open_memstream(char**ptr, size_t* sizeloc);

#include <wchar.h>

FILE* open_wmemstream(wchar_t** ptr, size_t* sizeloc);

相当于把一段内存映射成一个文件来操作。

fmemopen: mode与fopen相同

#include <stdlib.h>
#include <string.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while(0)

static char buffer[] = "Hello world";

int main(int argc, char* argv[])
{
    FILE* in = fmemopen(buffer, strlen(buffer), "r");
    if(!in)
        handle_error("fmemopen failed.");

    int ch;
    while((ch = fgetc(in)) != EOF)
        printf("%c ", ch);

    fclose(in);
    printf("\ntest Over\n");

    return 0;
}

/////////////////////
open_memstream:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while(0)


int main(int argc, char* argv[])
{
    char* buf;
    size_t len;

    FILE* out = open_memstream(&buf, &len);
    if(!out)
        handle_error("open_memstream is failed.");

    printf("len: %d\n", len);
    fprintf(out, "Hello World\n");
    fflush(out);

    printf("out: %s len: %d\n", buf, len);

    fclose(out);
    free(buf); // 需要主动释放

    return 0;
}



《unix环境高级编程》 读书笔记 (3)