GLIBC中的库函数fflush究竟做了什么?
目录
目录 1
1. 库函数fflush原型 1
2. FILE结构体 1
3. fflush函数实现 2
4. fclose函数实现 4
附1:强弱函数名 5
附2:属性__visibility__ 6
1. 库函数fflush原型
先瞧瞧fflush的原型:
#include <stdio.h> int fflush(FILE *stream); |
可看到fflush操作的是FILE,这里的FILE又长什么样?如果参数传入NULL,则对所有已打开的有效。
2. FILE结构体
直接查看源码,或者在GDB上执行ptype,即可看到FILE的庐山真面目如下:
// The value returned by fgetc and similar functions to indicate the end of the file. // #define EOF (-1) type = struct _IO_FILE { int _flags; char *_IO_read_ptr; char *_IO_read_end; char *_IO_read_base; char *_IO_write_base; // 写缓冲区起始地址 (Start of put area) char *_IO_write_ptr; // 写的起始地址 (Current put pointer) char *_IO_write_end; // 写缓冲区的末端 char *_IO_buf_base; char *_IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; _IO_marker *_markers; _IO_FILE *_chain; int _fileno; // 文件描述符 int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; __off64_t _offset; void *__pad1; void *__pad2; void *__pad3; void *__pad4; size_t __pad5; int _mode; // 文件打开模式,参见系统调用open的mode参数取值 char _unused2[20]; } FILE; |
_IO_write_ptr和_IO_write_base之间为已写入缓冲区的数据,_IO_write_end和_IO_write_base为写缓冲区的大小,_IO_write_end和_IO_write_ptr之间为写缓冲区可用区域。
3. fflush函数实现
LIBC库函数fflush主要做的是借助系统调用write将_IO_write_ptr和_IO_write_base间的数据写入内核。可借助下列小段代码看出真相:
$ cat ggg.cpp #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { FILE* fp = fopen("/tmp/ggg.txt", "w+"); if (fp == NULL) { perror("fopen"); exit(1); } fputs("hello", fp); // 这一步并不会调用write fflush(fp); // 间接调用write fclose(fp); return 0; } |
借助GDB即可看到fflush时的调用栈:
(gdb) bt #0 0x00007ffff72e0840 in write () from /lib64/libc.so.6 #1 0x00007ffff726cfb3 in _IO_new_file_write () from /lib64/libc.so.6 #2 0x00007ffff726e41c in __GI__IO_do_write () from /lib64/libc.so.6 #3 0x00007ffff726c810 in __GI__IO_file_sync () from /lib64/libc.so.6 #4 0x00007ffff72620a2 in fflush () from /lib64/libc.so.6 #5 0x00000000004007bd in main () at ggg.cpp:12 |
函数__GI__IO_file_sync源码:
int _IO_new_file_sync (FILE *fp) { ssize_t delta; int retval = 0; /* char* ptr = cur_ptr(); */ if (fp->_IO_write_ptr > fp->_IO_write_base) if (_IO_do_flush(fp)) return EOF; delta = fp->_IO_read_ptr - fp->_IO_read_end; if (delta != 0) { off64_t new_pos = _IO_SYSSEEK (fp, delta, 1); if (new_pos != (off64_t) EOF) fp->_IO_read_end = fp->_IO_read_ptr; else if (errno == ESPIPE) ; /* Ignore error from unseekable devices. */ else retval = EOF; } if (retval != EOF) fp->_offset = _IO_pos_BAD; /* FIXME: Cleanup - can this be shared? */ /* setg(base(), ptr, ptr); */ return retval; } libc_hidden_ver (_IO_new_file_sync, _IO_file_sync) // 下面的“_f”类型为FILE #define _IO_do_flush(_f) \ ((_f)->_mode <= 0 \ ? _IO_do_write(_f, (_f)->_IO_write_base, (_f)->_IO_write_ptr-(_f)->_IO_write_base) \ : _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base, \ ((_f)->_wide_data->_IO_write_ptr - (_f)->_wide_data->_IO_write_base))) extern int _IO_do_write (FILE *, const char *, size_t); libc_hidden_proto (_IO_do_write) extern int _IO_wdo_write (FILE *, const wchar_t *, size_t); libc_hidden_proto (_IO_wdo_write) |
函数_IO_do_write源代码:
int _IO_new_do_write (FILE *fp, const char *data, size_t to_do) { // new_do_write实际调用write return (to_do == 0 || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF; } libc_hidden_ver (_IO_new_do_write, _IO_do_write) |
4. fclose函数实现
如果省掉fflush,代码变成如下:
$ cat ggg.cpp #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { FILE* fp = fopen("/tmp/ggg.txt", "w+"); if (fp == NULL) { perror("fopen"); exit(1); } fputs("hello", fp); //fflush(fp); fclose(fp); return 0; } |
可看到write发生在fclose时:
(gdb) bt #0 0x00007ffff72e0840 in write () from /lib64/libc.so.6 #1 0x00007ffff726cfb3 in _IO_new_file_write () from /lib64/libc.so.6 #2 0x00007ffff726e41c in __GI__IO_do_write () from /lib64/libc.so.6 #3 0x00007ffff726dd10 in __GI__IO_file_close_it () from /lib64/libc.so.6 #4 0x00007ffff7261c40 in fclose@@GLIBC_2.2.5 () from /lib64/libc.so.6 #5 0x000000000040076d in main () at ggg.cpp:13 |
但如果在flcose之前已调用了fflush,则fclose时不会再调用write。
附1:强弱函数名
调用一个函数时,如果存在强符号,则调用强函数;否则如果存在弱符号,则调用弱函数,可简单将“弱函数”看成为函数指针,如果弱函数对应的函数并不存在,仍然可以编译链接成功,这个时候类似于野指针或空指针,运行时会段错误。
1) 定义弱函数名weak_alias
是一个宏,用来定义弱别名,GLIBC中大量使用,如:
weak_alias (_IO_fflush, fflush) // fflush为别名 |
2) 定义强弱函数名strong_alias
是一个宏,用来定义强别名,GLIBC中大量使用,如:
strong_alias (_IO_fflush, __fflush_unlocked) |
3) 示例(f为弱函数名,_f为强函数名):
$ cat aaa.c #include <stdio.h> static void f() __attribute__((weakref, alias("_f"))); int main() { f(); return 0; } $ cat bbb.c #include <stdio.h> void _f() { printf("hello\n"); } |
如果如下方式编译,则运行时段错误:
gcc -g -o aaa aaa.c |
而如下方式,则实际执行文件bbb.c中的函数“_f”:
gcc -g -o aaa aaa.c bbb.c |
4) GLIBC中weak_alias的定义(定义在文件libc-symbols.h中)
/* Define ALIASNAME as a weak alias for NAME. If weak aliases are not available, this defines a strong alias. */ # define weak_alias(name, aliasname) _weak_alias (name, aliasname) # define _weak_alias(name, aliasname) \ extern __typeof (name) aliasname __attribute__ ((weak, alias (#name))) \ __attribute_copy__ (name); |
也可用weak_hidden_alias定义隐藏别名:
/* Same as WEAK_ALIAS, but mark symbol as hidden. */ # define weak_hidden_alias(name, aliasname) _weak_hidden_alias (name, aliasname) # define _weak_hidden_alias(name, aliasname) \ extern __typeof (name) aliasname \ __attribute__ ((weak, alias (#name), __visibility__ ("hidden"))) __attribute_copy__ (name); |
附2:属性__visibility__
先看一小段代码:
$ cat eee.cpp #include <stdio.h> __attribute ((visibility("default"))) void ff1() { printf("ff1\n"); } __attribute ((visibility("hidden"))) void ff2() { printf("ff2\n"); } |
$ g++ -g -o libeee.so -fPIC -shared eee.cpp $ nm libeee.so 0000000000201030 B __bss_start 0000000000201030 b completed.6337 w __cxa_finalize@@GLIBC_2.2.5 0000000000000600 t deregister_tm_clones 0000000000000670 t __do_global_dtors_aux 0000000000200dd0 t __do_global_dtors_aux_fini_array_entry 0000000000200de0 d __dso_handle 0000000000200de8 d _DYNAMIC 0000000000201030 D _edata 0000000000201038 B _end 000000000000070c T _fini 00000000000006b0 t frame_dummy 0000000000200dc8 t __frame_dummy_init_array_entry 00000000000007c8 r __FRAME_END__ 0000000000201000 d _GLOBAL_OFFSET_TABLE_ w __gmon_start__ 00000000000005a0 T _init w _ITM_deregisterTMCloneTable w _ITM_registerTMCloneTable 0000000000200dd8 d __JCR_END__ 0000000000200dd8 d __JCR_LIST__ w _Jv_RegisterClasses U puts@@GLIBC_2.2.5 0000000000000630 t register_tm_clones 0000000000201030 d __TMC_END__ 00000000000006e8 T _Z3ff1v 00000000000006fa t _Z3ff2v |
上面中的“T”和“t”均表示该符号文本(代码)段在,但visibility为“default”的对应大写“T”,而visibility为“hidden”的对应小写“t”,为小写“t”的符号对外并不可见。
通过下段代码,可以看到引用大写“T”的符号,可得到预期的结果:
$ cat fff.cpp extern void ff1(); int main() { ff1(); return 0; } $ g++ -g -o fff fff.cpp libeee.so -Wl,-rpath=. $ ./fff ff1 |
而引用小写“t”的符号,在链接时报“符号未定义”错误:
$ cat fff2.cpp extern void ff2(); int main() { ff2(); return 0; } $ g++ -g -o fff2 fff2.cpp libeee.so -Wl,-rpath=. /tmp/ccwoSyAu.o:在函数‘main’中: /tmp/fff.cpp:3:对‘ff2()’未定义的引用 collect2: 错误:ld 返回 1 |
GLIBC中的库函数fflush究竟做了什么?的更多相关文章
- C++标准库中的std::endl究竟做了什么?
先抓出std::endl的源代码: /** * @file ostream * @brief Write a newline and flush the stream. * * This m ...
- 一个请求中,ADF、JSF究竟做了哪些工作
在Oracle ADF开发中,一个请求发生后,经过ADF处理后,我们可以很快得到响应页面,但在请求过程中ADF框架在背后究竟做了什么东西呢?今天让我们一起来了解下,ADF.JSF是基于组件模型的,不同 ...
- 异步编程系列第05章 Await究竟做了什么?
p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...
- glibc中malloc()的空间overhead
在linux下调用malloc()分配内存的时候,实际占用的内存与请求的内存尺寸的关系是什么呢,这个需要研究一下glibc中malloc()的实现.现在常见linux发行版中带的glibc中采用的都是 ...
- glibc中malloc的详细解释_转
glibc中的malloc实现: The main properties of the algorithms are:* For large (>= 512 bytes) requests, i ...
- IP分片 与 TCP分段的区别 !!!!careful========以及udp中一个包大小究竟为多大合适 ==========三次握手四次挥手细节
首先声明:TCP分片应该称为TCP分段 TCP/IP详解--TCP的分段和IP的分片 分组可以发生在运输层和网络层,运输层中的TCP会分段,网络层中的IP会分片.IP层的分片更多的是为运输层的UDP服 ...
- [源码解析] Flink的groupBy和reduce究竟做了什么
[源码解析] Flink的groupBy和reduce究竟做了什么 目录 [源码解析] Flink的groupBy和reduce究竟做了什么 0x00 摘要 0x01 问题和概括 1.1 问题 1.2 ...
- 《大话数据库》-SQL语句执行时,底层究竟做了什么小动作?
<大话数据库>-SQL语句执行时,底层究竟做了什么小动作? 前言 大家好,我是Taoye,试图用玩世不恭过的态度对待生活的Coder. 现如今我们已然进入了大数据时代,无论是业内还是业外的 ...
- House of apple 一种新的glibc中IO攻击方法
目录 House of apple 一种新的glibc中IO攻击方法 前言 利用条件 利用原理 利用思路 思路一:修改tcache线程变量 思路二:修改mp_结构体 思路三:修改pointer_gua ...
随机推荐
- hbase 查看元数据
package com.jason.lala.pipe.dbinfo import com.jason.lala.common.query.option.HbaseOptions import org ...
- Unity和Jenkins真是绝配,将打包彻底一键化!
说起打包,我们的QA简直是要抓狂,这个确实我也很同情他们.项目最开始打包是另一个同事做的,打包步骤是有些繁琐,但是项目上线后,不敢轻易动啊!每次他们打包总要跟我抱怨,国内版本打包步骤要10多步还能忍, ...
- ThinkPHP连接Oracle数据库的详细教程
一. 操作环境搭建 系统:Windows7 旗舰版 64位PHP环境:wampserver2.2e-php5.4.3-httpd2.2.22-mysql5.5.24 32位版ThinkPHP:3.0正 ...
- python实现AES加密
pip install pycryptodomex 需要安装pycryptodomex模块 aes加密 from Cryptodome.Cipher import AES from binascii ...
- python正确使用异常处理机制
一.不要过度使用异常 不可否认,Python 的异常机制确实方便,但滥用异常机制也会带来一些负面影响.过度使用异常主要表现在两个方面: 把异常和普通错误混淆在一起,不再编写任何错误处理代码,而是以简单 ...
- vscode+flutter+win10搭建问题记录
1.下载安装vscode.flutter sdk.安装vscode相关插件.android sdk,这些网上有教程,比如https://blog.csdn.net/SVNzK/article/deta ...
- Ipfs基础入门
Ipfs介绍 Ipfs(Inter-Planetary File System!),中文译为星际网络文件系统,是基于默克尔有向无环图(merkle dag)的全球性p2p文件系统. 是一个面向全球的, ...
- Python学习笔记之try-except
Python使用被称为异常的特殊对象来管理程序执行期间发生的错误.每当发生让Python不知所措的错误时,它都会创建一个异常对象.如果你编写了处理该异常的代码,程序将继续运行:如果你未对异常进行处理, ...
- 关于spring中请求返回值的json序列化/反序列化问题
https://cloud.tencent.com/developer/article/1381083 https://www.jianshu.com/p/db07543ffe0a 先留个坑
- One SQL to Rule Them All – an Efficient and Syntactically Idiomatic Approach to Management of Streams and Tables(中英双语)
文章标题 One SQL to Rule Them All – an Efficient and Syntactically Idiomatic Approach to Management of S ...