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

  • 相关阅读:
    Markdown高级使用之流程图
    Sentinel滑动窗口算法
    Markdown基础使用
    多线程学习(二)--整体简介
    MYSQL学习(三) --索引详解
    MYSQL学习(二) --MYSQL框架
    MYSQL 学习(一)--启蒙篇《MYSQL必知必会》
    数据结构学习(六) --排序
    数据结构学习(五)--查找
    数据结构学习(四)--图
  • 原文地址:https://www.cnblogs.com/aquester/p/11856308.html
Copyright © 2011-2022 走看看