Home Articles

endl并刷新缓冲区

Asked
Viewed 1361 times
26

C++ primer 书中,在第(1)章中,它提到了以下内容:

endl是一个特殊的值,称为操纵符,当写入输出流时,可以将新行写入输出并释放与该设备关联的缓冲区 . 通过浏览缓冲区,我们确保用户将立即看到写入流的输出 .

在这里“冲洗缓冲区”是什么意思?

5 Answers

  • 30

    这里的“冲洗缓冲区”是什么意思?

    std::endl 导致流的内部分段存储器(其"buffer")中的数据被"flushed"(传送)到操作系统 . 后续行为取决于流映射到的设备类型,但通常,刷新将提供数据已物理传输到关联设备的外观 . 然而,突然失去力量可能会打败这种错觉 .

    这种冲洗涉及一些(浪费的时间),因此当执行速度是一个重要问题时应该最小化 . 最大限度地减少这种开销的总体影响是data buffering的根本目的,但这个目标可以通过过度冲洗来消除 .


    背景资料

    计算系统的I / O通常非常复杂并且由多个抽象层组成 . 每个这样的层可能引入一定量的开销 . 数据缓冲是一种通过最小化在系统的两个层之间执行的单个事务的数量来减少此开销的方法 .

    • CPU/memory system-level buffering (caching) :对于非常高的活动,即使计算机的随机存取存储器系统也可能成为瓶颈 . 为了解决这个问题,CPU通过提供多层隐藏缓存(其各个缓冲区称为缓存线)来虚拟化内存访问 . 这些处理器高速缓存缓冲算法的内存写入(根据writing policy),以最大限度地减少内存总线上的冗余访问 .

    • Application-level buffering :虽然并不总是必要,但应用程序分配内存块以在将输出数据传递给I / O库之前累积输出数据的情况并不少见 . 这提供了允许随机访问(如果需要)的基本好处,但是这样做的一个重要原因是它最小化了与进行库调用相关的开销 - 这可能比简单地写入存储器阵列更耗时 . .

    • I/O library bufferingC++ IO stream library可选择为每个打开的流管理缓冲区 . 特别是,此缓冲区用于限制操作系统内核的数量,因为此类调用往往会产生一些非常重要的开销 . 这是缓冲区使用 std::endl 时刷新 .

    • operating system kernel and device drivers :操作系统根据流附加到哪个输出设备将数据路由到特定设备驱动程序(或子系统) . 此时,实际行为可能会有很大差异,具体取决于该类型设备的性质和特征 . 例如,当设备是硬盘时,设备驱动程序可能不会立即启动向设备的传输,而是维护自己的缓冲区以进一步减少冗余操作(因为磁盘也可以最有效地写入块中) ) . 为了显式刷新内核级缓冲区,可能需要调用系统级函数,例如fsync() on Linux - 甚至closing关联的流,不一定强制进行此类刷新 .

    示例输出设备可能包括......

    • 本地计算机上的终端

    • 远程计算机上的终端(通过SSH或类似设备)

    • 数据通过pipes or sockets发送到另一个应用程序

    • 大量存储设备和相关文件系统的许多变体,可以(再次)通过网络本地连接或分发

    • hardware buffers :特定硬件可能包含自己的内存缓冲区 . 例如,硬盘驱动器通常包含disk buffer,以便(除其他外)允许进行物理写入而不需要系统的CPU参与整个过程 .

    在许多情况下,这些不同的缓冲层往往(在某种程度上)是多余的 - 因此基本上是过度的 . 然而,如果其他层由于某种原因未能提供关于与每层相关的开销的最佳缓冲,则每层的缓冲可以提供吞吐量的巨大增益 .

    简而言之, std::endl 仅解决了由该特定流的C IO流库管理的缓冲区 . 在调用 std::endl 之后,数据将被移动到内核级管理,接下来发生的数据取决于很多因素 .


    如何避免std :: endl的开销


    inline std::ostream & endl( std::ostream & os )
       {
       os.put( os.widen('\n') ); // http://en.cppreference.com/w/cpp/io/manip/endl
       if ( debug_mode ) os.flush(); // supply 'debug_mode' however you want
       return os;
       }
    

    在此示例中,您提供了一个自定义 endl ,可以调用或不调用对flush()的内部调用(这是强制传输到操作系统的内容) . 启用刷新(使用 debug_mode 变量)对于调试在程序终止之前能够检查输出(例如磁盘文件)的情况非常有用,然后才能完全关闭关联的流(这将导致最终冲洗缓冲区) .

  • 20

    C的iostream是缓冲的,这意味着当你输出到ostream时,内容不会立即转到流后面的内容,例如在cout的情况下stdout . 流的实现确定何时实际发送流的缓冲部分 . 这是出于效率的原因,逐字节写入网络或磁盘流是非常低效的,通过缓冲这个问题得以解决 .

    但这确实意味着当您将调试消息写入日志文件并且程序崩溃时,您可能会丢失通过流写入日志文件的部分数据,因为日志的一部分可能仍在流的缓冲区中尚未写入实际文件 . 要防止这种情况发生,您需要通过显式刷新方法调用或使用endl的便利性使流刷新其缓冲区 .

    但是,如果您只是定期写入文件,则应使用\ n而不是endl来防止流在每行都不必要地刷新流,从而降低性能 .

    编辑包括此注释:

    cin和cout有一种特殊的关系,从cin读取会事先自动冲洗cout . 这确保了例如在从cin读取等待输入之前,用户实际上会看到您写入cout的提示 . 因此,即使在cout中你通常不需要endl但可以使用\ n代替 . 您可以通过将它们绑在一起来在其他流之间创建这种关系 .

  • 7

    输出通常在写入目标设备之前进行缓冲 . 这样,当写入慢速访问设备(如文件)时,它不必在每个单个字符后访问设备 .

    刷新意味着清空缓冲区并将其实际写入设备 .

  • 1

    使用 std::cout 时,输出运算符( << )之后使用的操作数存储在缓冲区中,并且不会显示在 stdin (通常是终端或命令提示符)上,直到遇到 std::endlstd::cin ,这会导致缓冲区被刷新,从某种意义上说,将缓冲区的内容显示/输出到 stdin 上 .

    考虑这个程序:

    #include <iostream>
    #include <unistd.h>
    
    int main(void)
    {
        std::cout << "Hello, world";
        sleep(2);
        std::cout << std::endl;
    
        return 0;
    }
    

    获得的输出将是:

    2秒后

    你好,世界

  • 0

    一个简单的代码,向您展示c中缓冲I / O的影响

    无论您提供什么输入都是缓冲的,然后在输入的情况下传递给程序变量 .

    看看下面的代码://程序来测试缓冲I / O如何对我们的程序产生意外影响#include using namespace std;

    int main()
    {
        int a;
        char c;
        cin>>a;
        cin>>c;
        cout<<"the number is : "<<a;
        cout<<"\nthe character is : "<<c;
    
    }
    

    这里我们已经声明了两个变量一个int和一个char,如果我们输入数字为“12d34”这将导致int变量只接受12作为值,它将丢弃其余的仍然在缓冲区中 . 在下一个输入中,char变量将自动接受值“d”,甚至不要求您输入任何内容

Related