• 1. Linux环境高级编程
  • 2. 第三讲 标准I/O
  • 3. 库的编写静态库的编写 动态库的编写 接口的注意事项3
  • 4. 静态库的编写假设库文件包含a.h、a1.cpp、a2.cpp(示例3.1) 创建库 #g++ -c a1.cpp a2.cpp #ar –rc libtest.a a1.o a2.o 使用库 #g++ -o statictest statictest.cpp –L. -ltest4
  • 5. 动态库的编写示例(3.2) 动态库的编写 #g++ -fpic –shared –o libtest.so a1.cpp a2.cpp 生成libtest.so5
  • 6. 动态库的使用打开动态链接库 #include void *dlopen(const char *file, int mode); 参数 file:动态链接库的文件名,包括路径信息 mode:动态链接库的使用方式,例如RTLD_LAZY:动态的加入动态链接库中的函数 返回值:引用动态链接库的句柄;出错返回NULL6
  • 7. 动态库的使用映射动态链接库中的函数 #include void *dlsym(void *handle, const char *FuncName); 参数 handle:dlopen的返回值 FuncName:动态链接库中的函数名 返回值:FuncName函数被加载后,在进程地址空间中的地址;出错返回NULL7
  • 8. 动态库的使用查看出错原因 #include char *dlerror(); 返回值 当dlopen、dlsym等函数出错时,dlerror返回字符串说明这些函数出错的原因8
  • 9. 动态库的使用卸载动态链接库 #include int dlclose(void *handle); 参数 handle:dlopen的返回值 动态库使用者的编译 #g++ -o test test.cpp –ldl #test 出错?9
  • 10. 运行出错的原因动态库导出函数的变形 查看动态库导出的函数 #nm libtest.so f函数实际上在动态库中的名字是: _Z1fv10
  • 11. 库的编写注意事项导出函数的名称 函数调用约定 结构体对齐 谁分配谁释放 11
  • 12. 函数的导出名在动态库的实现文件中函数的名称,与动态库导出的函数名称可能不同 使用extern “C”使的导出的函数名称和实际名称一致(示例3.3) extern “C”:告诉编译器按C语言的方式设定函数的导出名 不同的编译器、不同的语言,对函数名的修改都有可能不同12
  • 13. 函数调用约定C语言调用约定 void __cdecl f(int a, int b); VC环境 void f(int a, int b) __attribute__((cdecl)) g++环境 f被表示成_f 从右至左,将参数压入堆栈 函数调用者负责压入参数和堆栈平衡13
  • 14. 函数调用约定标准调用约定 void __stdcall f(int a, int b); VC环境 f被表示成_f@8;8表示参数的字节数 从右至左,将参数压入堆栈 函数内负责堆栈平衡14
  • 15. 函数调用约定快速调用约定 void __fastcall f(int a, int b); VC环境 由寄存器传送参数,用ecx和edx传送参数列表中前两个双字或更小的参数,剩下的参数仍然从右至左压入堆栈 函数内负责堆栈平衡15
  • 16. 函数调用约定C++类成员函数的调用约定:thiscall this指针存放于ecx寄存器中 参数从右至左压入堆栈16
  • 17. 结构体大小struct A { int i; };17
  • 18. 结构体大小struct A { char j; int i; };18
  • 19. 结构体大小struct A { char j; short k; int i; };19
  • 20. 结构体大小struct A { short k; int i; char j; };20
  • 21. 结构体大小#pragma(4) struct A { char i; short j; char k; };21
  • 22. 结构体大小#pragma(4) struct A { char i; short j; char k; virtual void f(){} };22
  • 23. 结构体大小#pragma(4) struct A { char i; short j; char k; virtual void f(){} virtual void g(){} };23
  • 24. 库的编写注意事项导出函数的名称 函数调用约定 结构体对齐 谁分配谁释放 24
  • 25. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 26. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 27. 标准I/O库为什么要设计标准I/O库? 直接使用API进行文件访问时,需要考虑许多细节问题 例如:read、write时,缓冲区的大小该如何确定,才能使效率最优 标准I/O库封装了诸多细节问题,包括缓冲区分配
  • 28. 打开流fopen等函数 流的概念 缓冲 流的定向
  • 29. fopen函数用于打开一个指定的文件 函数原型 #include FILE *fopen(const char *restrict pathname, const char *restrict type); 参数 pathname:要打开的文件名
  • 30. fopen函数参数 type:指定文件的读、写方式 Type说明r或rb为读而打开w或wb使文件长度为0,或为写而创建a或ab添加;为在文件尾写而打开,或为写而创建r+或r+b或rb+为读和写而打开w+或w+b或wb+使文件长度为0,或为读和写而打开a+或a+b或ab+为在文件尾读和写而打开或创建
  • 31. fopen函数限制rwar+w+a+文件必须存在√√删除文件以前内容√√流可以读√√√√流可以写√√√√√流只在尾端处写√√31
  • 32. fopen函数返回值 FILE结构指针 (TC2.0的stdio.h) open函数返回的是文件描述符typedef struct { .............................. char fd; /* File descriptor */ short bsize; /* Buffer size */ unsigned char *buffer; /* Data transfer buffer */ .................................... } FILE; 标准I/O库 管理的缓冲区
  • 33. 标准I/O库缓冲标准I/O库提供缓冲的目的:尽可能减少使用read、write调用的次数,以提高I/O效率。 通过标准I/O库进行的读写操作,数据都会被放置在标准I/O库缓冲中中转。 何时进行实际的读写操作?
  • 34. 缓冲类型全缓冲 在填满标准I/O缓冲区后,才进行实际I/O操作(例如调用write函数) 调用fflush函数也能强制进行实际I/O操作 行缓冲 在输入和输出遇到换行符时,标准I/O库执行I/O操作 因为标准I/O库用来收集每一行的缓存的长度是固定的,所以,只要填满了缓存,即使没有遇到新行符,也进行I/O操作
  • 35. 缓冲类型行缓冲 终端(例如标准输入和标准输出),使用行缓冲 不带缓冲 标准I/O库不对字符进行缓冲存储 标准出错是不带缓冲的,为了让出错信息尽快显示出来
  • 36. setbuf函数用于设置缓冲类型 函数原型 void setbuf(FILE *fp, char *buf); 参数和返回值 fp:fopen函数的返回值 buf:用户提供的文件缓冲区,其长度为BUFSIZ 若buf为NULL,则为无缓冲 若buf不为NULL,则为全缓冲
  • 37. setvbuf函数用于设置缓冲类型 函数原型 void setvbuf(FILE *fp, char *buf, int mode, size_t size); 参数和返回值 fp:fopen函数的返回值
  • 38. setvbuf函数参数和返回值函数modebuf缓存及长度缓存的类型setbufnonnull长度为BUFSIZ的用户缓存全缓存NULL(无缓存)不带缓存setvbuf_IOFBFnonnull长度为size的用户缓存全缓存NULL合适长度的系统缓存_IONBFnonnull长度为size的用户缓存不带缓存NULL合适长度的系统缓存忽略无缓存
  • 39. 缓冲实验演示实验(3.4)
  • 40. 刷新一个流强制标准I/O库进行系统调用,以将数据传递到内核 函数原型 int fflush(FILE *fp); 参数和返回值 fp:文件指针;若fp=NULL,则刷新所有输出流 成功返回0,出错返回EOF
  • 41. 各种缓冲物理硬盘内核缓存(大小固定)应用程序通过系统调用 如:read等用户缓存IO库缓存标准I/O库速度最慢速度慢速度快通过标准I/O函数通过磁盘 驱动程序
  • 42. 打开流fopen等函数 流的概念 缓冲 流的定向
  • 43. 流的定向对于ASCII字符集,一个字符用一个字节表示 对于国际字符集,一个字符可用多个字节表示 流的定向决定了所读、写的字符是单字节还是多字节的
  • 44. fwide函数用于改变流的定向 函数原型 int fwide(FILE *fp, int mode); 参数与返回值 mode<0,字节定向 mode>0,宽定向 mode=0,返回当前流的定向
  • 45. 打开流fopen函数 freopen函数 fdopen函数 fclose函数
  • 46. freopen函数在一个特定的流上打开一个指定的文件,如若该流已经打开了,则先关闭该流 函数原型 FILE *freopen(const char *pathname, const char *type, FILE *fp); 参数 pathname:要打开的文件名 type:指定流的读写方式 fp:特定的流
  • 47. freopen函数freopen函数主要用于重定向 即将fp重定向到pathname指定的文件中 例如: freopen(“a.txt”, “w”, stdout); 将标准输出stdout重定向到a.txt文件中 程序显示(3.5)
  • 48. fdopen函数取一个现存的文件描述符,并使一个标准I/O流与该描舒符相结合 函数原型 FILE *fdopen(int filedes, const char *type); 参数和返回值 filedes:文件描述符 type:指定流的读写方式 返回文件指针
  • 49. fdopen函数fdopen常用于由创建管道和网络通信通道函数返回的描述符。 这些特殊类型的文件,不能用fopen打开 因此必须先调用设备专用函数以获得一个文件描述符 然后再用fdopen使一个标准I/O流与该描述符相关联49
  • 50. fdopen函数对于fdopen函数,type参数的意义稍由区别 因为该描述符已被打开,所以fdopen为写而打开并不截短该文件 标准I/O添写方式,也不能用于创建该文件 (因为如若一个描述符引用一个文件,则该文件一定已经存在)50
  • 51. fclose函数关闭一个打开了的流 函数原型 int fclose(FILE *fp); 参数和返回值 fp:要关闭的流对应的文件指针 成功返回0,出错返回EOF 在该文件被关闭之前,刷新缓存中的输出数据。缓存中的输入数据被丢弃,如果标准I/O库已经为该流自动分配了一个缓存,则释放此缓存。
  • 52. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 53. 定位流类似于lseek函数,即指定从文件的什么地方开始进行读写 通常有两种方法定位标准I/O流 ftell、fseek函数。 fgetpos、fsetpos函数。 后者是ANSI C引入的。程序要移植到非unix类操作系统,应使用后者。
  • 54. ftell函数用于获取当前文件偏移量 函数原型 long ftell(FILE *fp); 参数和返回值 fp:文件指针 返回值:成功返回当前文件偏移量 出错返回-1
  • 55. fseek函数用于设置当前文件偏移量 函数原型: int fseek(FILE *fp, long offset, int whence) 返回值和参数 成功返回0,出错返回非0 第一个参数fp:文件指针 第二个参数offset: 相对偏移量:需结合whence才能计算出真正的偏移量
  • 56. fseek函数参数 第三个参数Whence:该参数取值是三个常量之一 SEEK_SET: 当前文件偏移量为: 距文件开始处的offset个字节SEEK_CUR: 当前文件偏移量为: 当前文件偏移量+offset(可正可负)SEEK_END: 当前文件偏移量为: 当前文件长度+offset(可正可负)
  • 57. rewind函数用于将文件偏移量设置到文件的起始位置 函数原型 void rewind(FILE *fp); 参数和返回值 fp:文件指针
  • 58. 定位流类似于lseek函数,即指定从文件的什么地方开始进行读写 通常有两种方法定位标准I/O流 ftell、fseek函数。 fgetpos、fsetpos函数。 后者是ANSI C引入的。程序要移植到非unix类操作系统,应使用后者。
  • 59. fgetpos函数获取当前的文件偏移量 函数原型 int fgetpos(FILE *fp, fpos_t *pos); 参数和返回值 fp:文件指针 pos:fgetpos函数将文件偏移量填入pos中 成功返回0,出错返回非0
  • 60. fsetpos函数设置文件偏移量 函数原型 int fsetpos(FILE *fp, fpos_t *pos); 参数和返回值 fp:文件指针 pos:存储了要设置的文件偏移量 成功返回0,出错返回非0
  • 61. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 62. 读写流对流有三种读写方式 每次读写一个字符 每次读写一行 每次读写任意长度的内容
  • 63. 每次读写一个字符输入函数 int getc(FILE *fp); int fgetc(FILE *fp); int getchar(); 返回值 成功返回欲读字符,若已处于文件尾或出错返回EOF getchar()等同于getc(stdin) getc通常是宏,fgetc是函数
  • 64. 出错Or文件尾不管出错还是到达文件尾,都是返回EOF。如何区分? 调用ferror或feof int ferror(FILE *fp); int feof(FILE *fp); 当遇到文件结束符时,feof返回真,ferror返回假 当出错时,feof返回假,ferror返回真 64
  • 65. 出错Or文件尾在大多数实现中,为每个流在FILE对象中维持了两个标志 出错标志 文件结束标志 调用clearerr清楚这两个标志 void clearerr(FILE *fp);65
  • 66. 每次读写一个字符输出函数 int putc(int c, FILE *fp); int fputc(int c, FILE *fp); int putchar(int c); 返回值 成功返回c,出错返回EOF putchar(c)等同于putc(c, stdout) putc通常是宏,fputc是函数
  • 67. 每次读写一行输入函数 char* fgets(char *buf, int n, FILE *fp); 参数和返回值 buf:存放从fp读出的数据 n:buf的大小 fp:文件指针 返回值:成功返回buf,出错或读到文件尾则为EOF
  • 68. fgets函数fgets函数一直读到下一个新行符为止,但是不超过n-1个字符 buf缓存以null字符结尾 若读到下一个新行符,会超过n-1个字符,则只会返回一个不完整的行,缓存总是以null字符结尾。 下一次的fgets调用会继续读该行。 程序演示(3.6)
  • 69. 每次读写一行输出函数 char* fputs(const char *str, FILE *fp); 参数和返回值 str:以null为结尾的字符串 fp:文件指针 返回值:成功返回非负值,出错则为EOF null符不写入文件
  • 70. 直接I/O每次I/O操作读写某种数量的对象,而每个对象具有指定的长度 例如,可读写一个二进制数组、结构 函数原型 size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp); size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);
  • 71. 直接I/O参数 第一个参数ptr:用于读写的缓冲区 第二个参数size:每个对象的大小 第三个参数nobj:要读写的对象个数 第四个参数fp:文件指针 返回值 返回读/写的对象数
  • 72. 直接I/O读写一个二进制数组 float data[10]; fwrite(data, sizeof(float), 4, fp); 读写一个结构 struct{ int a; int b; }item; fwrite(&item, sizeof(item), 1, fp);
  • 73. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 74. 格式化I/O格式化输出函数 int printf(const char* format, ....); int fprintf(FILE *fp, const char *format, ...); int sprintf(char *buf, const char *format, ...); printf将格式化数据写到标准输出 fprintf写至指定的流 sprintf写入数组buf中 前两者返回输出字符数,后者返回写入数组的字符数
  • 75. 格式化输出sprintf将格式化的字符送入数组buf中,并在该数组的尾端自动加入一个null字符,但该字节不包括在返回值中 sprintf函数可能会造成由buf指向的缓冲区的溢出,调用者要确保缓冲区足够大 int snprintf(char* buf, size_t n, const char* format, ……); 缓冲区长度为n,超过缓冲区尾端的任何字符都会被丢弃75
  • 76. 格式化I/O格式化输入函数 int scanf(const char* format, ....); int fscanf(FILE *fp, const char *format, ...); int sscanf(char *buf, const char *format, ...);
  • 77. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 78. 创建临时文件创建临时文件可以用以前介绍的creat,然后立即unlink,也可以用以下方法: char *tmpnam ( char *ptr ); FILE *tmpfile ( void ); /* 返回文件指针 *、/ char *tempnam ( const char *directory, const char *prefix ); 第一个函数产生一个与现在文件名不同的有效路径名字符串(每次调用均不同)。第二个函数创建一个临时二进制文件(wb+),在关闭该文件或程序结束时,该文件自动删除。第三个函数是第一个函数的变体,在产生路径名时,指定其目录和文件名前缀。
  • 79. 标准I/O的替代软件效率不高 当使用每次一行函数fgets和fputs时,需要复制两次数据。哪两次? 快速I/O库fio避免了这一点:使读一行的函数返回指向该行的指针,而不是将该行复制到另一个缓冲区 sfio、uClibc、newlibc79
  • 80. 标准I/O标准I/O库 打开流 定位流 读写流 格式化输出 临时文件
  • 81. 系统数据文件-概述Unix系统中许多系统数据文件以文本文件的形式存在,如口令文件、主机信息文件、协议数据文件、网络服务文件等等。 系统提供了一系列读取这些数据文件的系统调用,方便对这些系统数据进行查询。81
  • 82. 口令文件(3.7)早期的UNIX系统中,所有用户的注册信息均存储于一个文件passwd中,后来,专门将口令信息单独存放在另一个文件中shadow文件中。这两个文件中主要存放以下信息说明struct passwd成员用户名char *pw_name加密口令char *pw_passwd用户IDuid_t pw_uid用户组IDgid_t pw_gid注释字段char *pw_gecos初始工作目录char *pw_dir初始shellchar *pw_shell82
  • 83. 口令读取函数 struct passwd *getpwuid( uid_t uid); struct passwd *getpwnam ( const char *name); struct passwd *getpwent (void); void setpwent( void ); /* rewind passwd file */ void endpwent (void); /* close passwd file */ 前一组函数根据用户ID或用户名获取某一用户的口令信息;而后一组则可以获取这个口令文件中所有用户的口令信息。 struct passwd { char *pw_name; /* user name */ char *pw_passwd; /* user password */ uid_t pw_uid; /* user id */ gid_t pw_gid; /* group id */ char *pw_gecos; /* real name */ char *pw_dir; /* home directory */ char *pw_shell; /* shell program */ };83
  • 84. 其他数据文件UNIX系统中还有许多其他数据文件,一般情况下,每个数据文件至少有三个函数可以操纵,如果还支持关键搜索,则提供关键字搜索函数: get函数:读下一个记录,如果需要还打开该文件; set函数:打开相应数据文件,然后反绕该文件; end函数:关闭相应数据文件。说明数据文件头文件结构附加的关键字搜索函数主机/etc/hostshostentgethostbyname, gethostbyaddr网络/etc/networksnetentgetnetbyname, getnetbyaddr协议/etc/protocolsprotoentgetprotobyname,getprotobynumber服务/etc/servicesserventgetservbyname, getservbyport84
  • 85. 系统标识 int uname ( struct utsname *name ); int gethostname (char *name, int namelen ); 第一个函数返回主机和操作系统的有关信息,而第二个函数只返回系统主机名。 #include struct utsname { char sysname[]; /* name of the operating system */ char nodename[] /* name of this node */ char release[]; /* current release of operating system */ char version[]; /* current version of this release */ char machine[]; /* name of hardware type */ #ifdef _GNU_SOURCE char domainname; /* name of domain */ #endif }85
  • 86. 系统日期、时间 struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }time_tstruct tm字符串格式化字符串time(), gettimeofday()内核ctimeasctimestrftimemktimelocaltimegmtime以年、月、日、时、分、秒表示的时间Exp.: "Wed Jun 30 21:49:08 1993\n"like printf86