C++:混合使用printf和wprintf(或cout和wcout)

12
我知道不应该混合使用printing和printf、cout以及wprintf、wcout,但是很难找到一个好的答案来解释这样做的原因,以及是否有可能规避这个问题。我的问题在于我使用了一个使用printf进行打印的外部库,而我自己则使用wcout。如果我只是做一个简单的例子,那么它可以正常工作,但是在我的完整应用程序中,printf语句根本没有打印出来。如果这确实是一种限制,那么将会有许多库无法与宽字符打印应用程序一起使用。对此的任何见解都将不胜感激。
更新:
我将其简化为:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

#include <readline/readline.h>
#include <readline/history.h>

int main()
{
    char *buf;

    std::wcout << std::endl; /* ADDING THIS LINE MAKES PRINTF VANISH!!! */

    rl_bind_key('\t',rl_abort);//disable auto-complete

    while((buf = readline("my-command : "))!=NULL)
    {
        if (strcmp(buf,"quit")==0)
            break;

        std::wcout<<buf<< std::endl;

        if (buf[0]!=0)
            add_history(buf);
    }

    free(buf);

    return 0;
}

我猜可能是刷新问题,但这对我来说看起来仍然很奇怪,我需要检查一下。

更新 -> 解决方法:

首先,使用wprintf也会出现同样的问题。但我发现添加以下内容可以解决:

std::ios::sync_with_stdio(false);

实际上这个技巧很有效...(请注意是false而不是我期望的true..),唯一让我困扰的是,我不明白为什么以及如何解决它 :-(


我将其简化为以下内容: #include <stdio.h> #include <stdlib.h> #include <iostream>#include <readline/readline.h> #include <readline/history.h>int main() { char *buf;std::wcout << std::endl; /* 添加此行会使 printf 消失!!! */ rl_bind_key('\t',rl_abort);//禁用自动完成 while((buf = readline("my-command : "))!=NULL) { if (strcmp(buf,"quit")==0) break; std::wcout<} - Bo Jensen
抱歉,我的错误,显然不应与代码片段一起使用。 - Bo Jensen
2
为什么回答都集中在混合使用 coutprintf,而问题是关于混合使用 coutwcout 的? - kennytm
不是的,因为在readline函数内部有printf语句。 - Bo Jensen
@Kenny:因为标题不够清晰,我会尝试改进它。 - Ben Voigt
我可以确认,在我的情况下,混合使用 _tprintf 和 wcout 会导致应用程序崩溃(使用 Visual Studio 2010 编写)。这是可以重现的。 - Helge Klein
6个回答

7

我想你在谈论 std::ios_base::sync_with_stdio,但据我所知它默认开启。


6

您应该能够混合它们,但它们通常使用单独的缓冲机制,因此它们会相互重叠:

printf("hello world");
cout << "this is a suprise";

可能会导致以下结果:

你好,这是一个充满惊喜的世界。

关于您应用程序中printf()的问题,您没有提供足够的信息来进行诊断,但我怀疑您有多个c运行时(一个在您的代码中,一个在printf()代码中),并且存在冲突。


我的做法是使用GNU readline库来实现命令行历史记录。因此,我的应用程序与该库链接。 - Bo Jensen
1
我想补充一点,如果我们谈论混合使用cout和wcout(而不是printf和wcout),那么我的经验是,不仅输出混乱是一个问题,应用程序还可能会崩溃。感谢您的回答。 - Bo Jensen
@Bo Jensen:这不是wcout的错,而是你的问题。或者这是你的C++标准库实现中的一个错误。 - Billy ONeal
3
虽然这种情况应该很少发生。默认情况下,cout 和 C 的输出流应该是同步的(http://www.cplusplus.com/reference/iostream/ios_base/sync_with_stdio/)。在一个循环中输出100次并查看输出结果。然后关闭同步并再次尝试。 - UncleBens

4

printf() 和 cout 缓冲区默认情况下是同步的,或者实际上是同一个缓冲区。如果您遇到缓冲问题,明显的解决方案是在每次输出后刷新缓冲区:

fflush( stdout );
cout.flush();

这将缓冲区刷新到操作系统中,一旦完成,就不会出现交错或输出丢失的可能性。


0

这是我的观点,不一定正确,仅供参考。😄


概述

  • cout/wcout是C++世界的概念,C语言中的等效术语是printf/wrpintf。这些机制用于将我们在应用程序中提供的内容传输到标准输出流。
  • 与C语言兼容是C++设计目标之一。也就是说,C++的实现必须保证C++和C代码的混合执行正确。
  • 问题显而易见,毕竟C和C++是两种不同的语言。C++的cout/wcout和C的printf/wprintf有自己的实现,例如它们自己的应用层缓冲区、缓冲区的刷新策略、本地化依赖等等。简单混合可能导致意想不到的结果,因此C++标准设计者必须开发可行的解决方案。
  • 从编码人员的角度来看,混合使用coutprintf的正确行为应该保证执行结果与仅使用coutprintf相同。为了实现这个效果,提出了与stdio同步的概念。我认为,“stdio”特指C语言的I/O机制,printf/wprintf属于它。因此,“sync to stdio”意味着将C++的I/O机制与C的机制同步。我们可以使用方法std::ios::sync_with_stdio来启用/禁用同步,默认情况下启用。
  • 那么,“sync”到底是什么?如何与stdio同步?这取决于实现,我不知道细节。它似乎更像“共享”的概念,printf/wprintf的缓冲区与cout/wcout共享。或者cout/wcout直接使用print/wprintf的缓冲区。简而言之,如果我们启用“sync”,cout/wcout就不再独立,通常是缓冲区,它依赖于C stdio。同步使得C++和C的I/O过程就像一种语言。
  • 因此,在C++代码中启用与stdio同步后(默认情况下已启用,我们不需要操作),我们可以放心地使用cout+printfwcout+wprintf。这个问题通过“sync”得到了解决。另一个难题是混合使用cout+wcoutprintf+wprintf,可以吗?——下面讨论这个话题。
  • cout printf工作在char存储单元上,wcout wprintf工作在wchar_t上。混合使用cout+wcoutprintf+wprintf不涉及语言级别的冲突。因为cout wcout属于同一种语言,printfwprintf也是如此。问题的关键在于“标准输出流的方向”。
  • 方向是什么意思?我认为可以解释为粒度。众所周知,OOD是面向对象设计,这意味着设计的粒度是对象。另一个例子是字节流定向协议——TCP,这意味着如果我们基于TCP构建应用程序,我们必须关注字

    一些示例

    我编写了一个名为checkStdoutOrientation的函数,用于获取标准输出流的方向

    void checkStdoutOrientation()                                                   
    {                                                                               
        std::fstream fs("result.txt", std::ios::app);                               
        int ret = fwide(stdout, 0);                                                 
        if (ret > 0) {                                                              
            fs << "wide byte oriented\n";                                           
        } else if (ret < 0) {                                                       
            fs << "byte oriented\n";                                                
        } else {                                                                    
            fs << "undecided oriented\n";                                           
        }                                                                           
        fs.close();                                                                 
    }    
    

    演示01:先调用wcout,然后调用cout

    #include <cstdio>                                                               
    #include <iostream>                                                             
    #include <fstream>  
    
    int main()                                                                      
    {                                                                               
        checkStdoutOrientation();                                                   
                                                                                    
        std::wcout << "456" << std::endl;                                           
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        std::cout << "123" << std::endl;                                            
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        return 0;                                                                   
    }          
    

    Debian10 Buster + GCC 8.3.0

    输出:
    在此输入图像描述

    result.txt:
    在此输入图像描述

    我的理解:

    • 首先调用了wcout,因此标准输出流是宽字节定向的;
    • 正因为上述情况,由cout处理的内容无法被标准输出流处理,因此“123”无法打印出来;

    Win10 + VS2022

    输出:
    在此输入图像描述

    result.txt:
    在此输入图像描述

    我的理解:

    • 似乎窗口不符合标准,标准输出流总是未决定的方向;
    • 正因为标准输出流的未决定方向,所有内容都被打印出来。

    Demo02: 先调用cout,再调用wcout

    #include <cstdio>                                                               
    #include <iostream>                                                             
    #include <fstream>  
    
    int main()                                                                      
    {                                                                               
        checkStdoutOrientation();                                                   
                                                                                    
        std::cout << "123" << std::endl;                                            
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        std::wcout << "456" << std::endl;                                           
                                                                                    
        checkStdoutOrientation();
    
        return 0;
    } 
    

    Debian10 Buster + GCC 8.3.0

    输出:
    enter image description here

    result.txt:
    enter image description here

    我的理解:

    • 首先调用cout,因此标准输出流是字节定向的;
    • 由于字节比宽字节更小的粒度,因此wcout内容到字节定向的标准输出流最终可以打印到控制台。

    Win10 + VS2022

    输出:
    在此输入图像描述 result.txt:
    在此输入图像描述 我的理解:

    • 标准输出流始终是未决定方向的;
    • 因此所有内容都会被打印。

    Demo03:先调用wprintf,再调用printf

    #include <cstdio>                                                               
    #include <iostream>                                                             
    #include <fstream>  
    
    int main()                                                                                                                                                                                                       
    {                                                                               
        checkStdoutOrientation();                                                   
                                                                                    
        wprintf(L"456\n");                                                          
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        printf("123\n");                                                            
                                                                                    
        checkStdoutOrientation();                                                 
                                                                                    
        return 0;                                                                   
    }          
    

    “这是与Demo01相同的结论。”

    Demo04:先调用printf,然后调用wprintf

    #include <cstdio>                                                               
    #include <iostream>                                                             
    #include <fstream>  
    
    int main()                                                                      
    {                                                                               
        checkStdoutOrientation();                                                   
                                                                                   
        printf("123\n");                                                                                                    
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        wprintf(L"456\n");                                                          
                                                                                    
        checkStdoutOrientation();
    
        return 0;
    } 
    

    Debian10 buster + GCC 8.3.0

    输出:
    enter image description here

    result.txt:
    enter image description here

    我的理解:

    • 首先调用cout,因此标准输出流是字节定向的;
    • 与Demo02的结果不同,我不知道为什么没有显示"456"。

    Win10 + VS2022

    输出:
    在这里输入图像描述

    result.txt:
    在这里输入图像描述

    我的理解:

    • 与 Demo02 相同的结果。

    Demo05:禁用与 stdio、wcout 和 cout 的同步

    #include <cstdio>                                                               
    #include <iostream>                                                             
    #include <fstream>  
    
    int main()                                                                      
    {                                                                               
        std::ios::sync_with_stdio(false);                                           
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        std::wcout << "456" << std::endl;                                           
                                                                                    
        checkStdoutOrientation();                                                   
                                                                                    
        std::cout << "123" << std::endl;                                            
                                                                                    
        checkStdoutOrientation(); 
    
        return 0;
    } 
    

    Debian10 Buster + GCC 8.3.0

    输出:
    输入图像描述

    result.txt:
    输入图像描述

    我的理解:

    • 禁用与stdio同步后,标准输出流始终是未决定方向的;
    • 因此所有内容都可以打印。

    Win10 + VS2022

    输出: 在此输入图像描述 result.txt: 在此输入图像描述 我的理解:
    • 与Demo01的结果相同。

    Demo06: 混合使用cout和printf

    #include <cstdio>                                                               
    #include <iostream> 
    
    int main()                                                                      
    {                                                                               
        printf("1\n");                                                              
        std::cout << "2\n";                                                         
        printf("3\n");                                                              
        std::cout << "4\n";                                                         
        printf("5\n");                                                              
                                                                                    
        printf("\n");                                                               
                                                                                    
        std::ios::sync_with_stdio(false);                                           
                                                                                    
        printf("1\n");                                                              
        std::cout << "2\n";                                                         
        printf("3\n");                                                              
        std::cout << "4\n";                                                         
        printf("5\n");                                                              
                                                                                                                                                            
        return 0;                                                                   
    }   
    

    Debian10 Buster + GCC 8.3.0

    输出:
    enter image description here

    我的理解:

    • 默认情况下启用了与stdio的同步,因此混合使用cout和printf只需调用cout或printf即可。
    • 禁用与stdio的同步后,cout将独立工作,cout和printf各自为政,因此打印内容会失序。

    Win10 + VS2022

    输出:
    enter image description here

    我的理解:

    • Windows仍然很特别,无论是否禁用与stdio的同步,混合使用cout和printf只需调用cout或printf即可。

    演示07:打印非ASCII字符 -- 方法A

    #include <cstdio>                                                               
    #include <iostream>                                                             
    
    int main()                                                                      
    {                                                                               
        std::locale myloc("en_US.UTF-8");                                           
        std::locale::global(myloc); // this setting does not affect wcout                                         
                                                                                    
        std::wcout << L"漢字\n";  
        wprintf(L"漢字\n");                                                         
    
        return 0;
    } 
    

    Debian10 Buster + GCC 8.3.0

    输出:
    enter image description here

    我的理解:

    • 设置全局语言环境不会影响wcout,因为wcout的语言环境是C语言环境。这是因为wcout是一个对象,在构造对象时已经设置了它的语言环境。
    • 那么在这种情况下,为什么wcout可以打印内容呢?别忘了C++的iostream默认与stdio同步,我们可以简单地认为,wcout在stdio的缓冲区上工作,而stdio已经通过global(myloc)代码设置为en_US.UTF-8。

    Win10 + VS2022

    输出:
    在此输入图片描述

    Demo08: 打印非ASCII字符 -- 方法B

    #include <cstdio>                                                               
    #include <iostream>                                                             
    
    int main()                                                                      
    {                                                                               
        std::ios::sync_with_stdio(false);                                           
                                                                                    
        std::locale myloc("en_US.UTF-8");                                           
        // std::locale::global(myloc);                                              
                                                                                    
        std::wcout.imbue(myloc);                                                    
        std::wcout << "wcout> " << L"漢字\n";                                       
        wprintf(L"wprintf> 漢字\n");                                                         
    
        return 0;
    } 
    

    Debian10 Buster + GCC 8.3.0

    输出:
    enter image description here

    我的理解:

    • 由于禁用了与stdio的同步,wprintf和wcout分别工作,因此打印顺序不正确;
    • 仍然由于同步被禁用,wcout应该独立工作,因此我们必须通过imbue方法将wout的区域设置为en_US.UTF-8,如果不这样做,wcout将打印出类似“??”的内容;
    • wprintf打印“??”,这是因为我们注释掉了std::locale::global(myloc);,所以stdio的区域仍然是C区域。

    Win10 + VS2022

    演示09:打印非ASCII字符 - 方法C

    此演示依赖于Windows平台。

    输出:
    enter image description here

    我的理解:

    • printf和cout总是按顺序打印,这是Windows的特殊之处,已经提到过多次;
    • wprintf打印空值相当于Linux中的“??”;
    • 所以,新的问题是乱码!我尝试取消注释std::locale::global(myloc);代码,打印内容就可以了!因此,我认为Windows的实现有点特殊,wcout可能依赖更多需要通过全局语言环境设置更改的东西。
    #include <cstdio>                                                               
    #include <iostream>                                                             
    
    int main()                                                                      
    {                                                                               
        _setmode(_fileno(stdout), _O_WTEXT); // Unique function to windows 
    
        std::wcout << "wcout> " << L"漢字\n";
        wprintf(L"wprintf> 漢字\n");                                                  
    
        return 0;
    } 
    

    Win10 + vs2022

    输出:
    输入图像描述

    我的理解:

    • _setmode 后,全局和 wcout 区域设置仍然是 C 区域设置;
    • 为什么 wcout 和 wprintf 都能正确打印内容?我猜可能是 Windows 在标准输出流之后实现了一种机制,通过 _setmode 指定的模式来转换内容。

0

缓冲头疼。通常情况下,你可以,因为它们是同步的。告诉你不要这样做的人可能是记得使用多个IO方法时的痛苦并想要拯救你免受此苦。 (只是不要将它们与系统调用混合使用。那会很痛苦。)


0

库不应使用 printf,cout 或任何其他标准句柄的 I/O。 库应使用回调例程将输出委托给主程序选择的方法。

一个明显的例外是唯一目的是输出的库,但这时主程序可以选择使用它。 这个规则并不禁止对库打开的文件描述符进行 I/O。

这不仅解决了此处提出的问题,而且还解决了断开操作(没有 tty 的 Linux 程序,例如通过 nohup 或 Win32 服务运行)的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接