zoukankan      html  css  js  c++  java
  • 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 "); }

    如果如下方式编译,则运行时段错误:

    gcc -g -o aaa aaa.c

    而如下方式,则实际执行文件bbb.c中的函数“_f”:

    gcc -g -o aaa aaa.c bbb.c

    4) GLIBCweak_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 ");

    }

    __attribute ((visibility("hidden"))) void ff2() {

      printf("ff2 ");

    }

    $ 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

  • 相关阅读:
    ORACLE 查看进程数,已执行任务数, 剩余任务数,删除指定任务
    ORACLE 收集统计整个用户数据
    解决Hystrix dashboard Turbine 一直 Loading…… 及其他坑
    利用 Maven 构造 Spring Cloud 微服务架构 模块使用 spring Boot构建
    AES加解密
    JAVA POI XSSFWorkbook导出扩展名为xlsx的Excel,附带weblogic 项目导出Excel文件错误的解决方案
    JAVA 文件的上传下载
    shell启停服务脚本模板
    JAVA 设计模式之 原型模式详解
    JAVA 设计模式之 工厂模式详解
  • 原文地址:https://www.cnblogs.com/aquester/p/11856308.html
Copyright © 2011-2022 走看看