目录

目录 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究竟做了什么?的更多相关文章

  1. C++标准库中的std::endl究竟做了什么?

    先抓出std::endl的源代码: /** *  @file  ostream *  @brief  Write a newline and flush the stream. * *  This m ...

  2. 一个请求中,ADF、JSF究竟做了哪些工作

    在Oracle ADF开发中,一个请求发生后,经过ADF处理后,我们可以很快得到响应页面,但在请求过程中ADF框架在背后究竟做了什么东西呢?今天让我们一起来了解下,ADF.JSF是基于组件模型的,不同 ...

  3. 异步编程系列第05章 Await究竟做了什么?

    p { display: block; margin: 3px 0 0 0; } --> 写在前面 在学异步,有位园友推荐了<async in C#5.0>,没找到中文版,恰巧也想提 ...

  4. glibc中malloc()的空间overhead

    在linux下调用malloc()分配内存的时候,实际占用的内存与请求的内存尺寸的关系是什么呢,这个需要研究一下glibc中malloc()的实现.现在常见linux发行版中带的glibc中采用的都是 ...

  5. glibc中malloc的详细解释_转

    glibc中的malloc实现: The main properties of the algorithms are:* For large (>= 512 bytes) requests, i ...

  6. IP分片 与 TCP分段的区别 !!!!careful========以及udp中一个包大小究竟为多大合适 ==========三次握手四次挥手细节

    首先声明:TCP分片应该称为TCP分段 TCP/IP详解--TCP的分段和IP的分片 分组可以发生在运输层和网络层,运输层中的TCP会分段,网络层中的IP会分片.IP层的分片更多的是为运输层的UDP服 ...

  7. [源码解析] Flink的groupBy和reduce究竟做了什么

    [源码解析] Flink的groupBy和reduce究竟做了什么 目录 [源码解析] Flink的groupBy和reduce究竟做了什么 0x00 摘要 0x01 问题和概括 1.1 问题 1.2 ...

  8. 《大话数据库》-SQL语句执行时,底层究竟做了什么小动作?

    <大话数据库>-SQL语句执行时,底层究竟做了什么小动作? 前言 大家好,我是Taoye,试图用玩世不恭过的态度对待生活的Coder. 现如今我们已然进入了大数据时代,无论是业内还是业外的 ...

  9. House of apple 一种新的glibc中IO攻击方法

    目录 House of apple 一种新的glibc中IO攻击方法 前言 利用条件 利用原理 利用思路 思路一:修改tcache线程变量 思路二:修改mp_结构体 思路三:修改pointer_gua ...

随机推荐

  1. nginx 安装ab小工具方法

    nginx 安装ab小工具方法测试工具安装(以centos系统为例)yum -y install httpd-tools 然后测试下ab -V

  2. linux安装go环境并编写第一个go程序

    1.从官网下载go源码包 wget https://dl.google.com/go/go1.12.5.linux-amd64.tar.gz 2.在/usr/local下解压源码包 sudo tar ...

  3. Extra:Cg Math Functions

    常用Cg函数 数学函数 abs(x):绝对值 // float类型的实现 float abs(float x) { return max(-a, a); } sin(x):正弦,输入为弧度 // fl ...

  4. 第十九节:Asp.Net Core WebApi基础总结和请求方式

    一. 基础总结 1.Restful服务改造 Core下的WebApi默认也是Restful格式服务,即通过请求方式(Get,post,put,delete)来区分请求哪个方法,请求的URL中不需要写方 ...

  5. 基础知识---IEnumerable、ICollection、IList、IQueryable

    一.定义 IEnumerable public interface IEnumerable<out T> : IEnumerable ICollection public interfac ...

  6. 自动化部署Ruby on Rails应用(docker + jenkins)

    docker安装参考链接docker官网jenkins中文官网 我的博客网站已经迁移到了https://johnnyting.github.io/,如果有兴趣的可以关注下.下面文章格式可能有点乱,可以 ...

  7. Scala Types 2

    存在类型 形式: forSome { type ... } 或 forSome { val ... } 主要为了兼容 Java 的通配符 示例 Array[_] // 等价于 Array[T] for ...

  8. 【POI】java服务生成List数据集合,后台服务生成xlsx临时文件,并将临时文件上传到腾讯云上

    场景: java服务生成List数据集合,后台服务生成xlsx临时文件,并将临时文件上传到腾讯云上 今日份代码: 1.先是一个变量,作为文件名 private static final String ...

  9. sql server 分页总结

    1.第一种方式:使用 ROW_NUMBER() OVER(ORDER BY ID) …… BETWEEN AND 的方式SELECT * FROM( SELECT ROW_NUMBER() OVER( ...

  10. c#多个按钮执行同一类事件-按钮按下和弹起

    首先在Winform中添加一个Button控件,在属性里面为控件添加鼠标按下和弹起事件(不要双击按钮,在属性里面添加) 再添加其他几个按钮控件,在控件的属性里面为鼠标按下和弹起添加已定义好处理函数(M ...