【问题标题】:printf() confusion - printing (null) when trying to print string and printing correct value when printing bufferprintf() 混淆 - 尝试打印字符串时打印 (null) 并在打印缓冲区时打印正确的值
【发布时间】:2014-01-27 06:21:54
【问题描述】:

查看简化代码 - 我很困惑...

#include <stdio.h>
#include <string>      
#include <cstdlib>  // includes the standard library and overarchs over stdlib.h

using namespace std;  
void main()
{
    char buffer[10];
    string theString;
    int i = 997799; //(simplified)
    itoa(i,buffer,10);                      
    theString = buffer;             

    printf("\n string is: %s of length %d \n", theString, theString.length());
    printf("\n buffer is: %s of length %d \n", buffer, theString.length());
    return;
}

我得到的输出是意外的:

string is: (null) of length 926366009
buffer is: 997799 of length 6

(1) 为什么字符串打印为空?

(2) 为什么 theString.length() 在第一个 printf() 中打印不正确,但在第二个打印正确?

(3) 如果我在 Visual Studio 中设置断点,“缓冲区”显示为“997799”,而“theString”显示为 {“997799”} - 这里发生了什么奇怪的事情?

谢谢各位! 编辑我非常感谢所提供答案的详细程度 - 它们都增加了清晰度并帮助我解决了我的问题 - 非常感谢您花时间提供帮助:)

【问题讨论】:

标签: c++ string null printf


【解决方案1】:

当您将%s 说明符与printf() 一起使用时,您承诺将char const* 作为相应的参数传递。传递除 char const* 或衰减为 char const* 之外的任何内容都是未定义的行为。当然,传递 C++ 对象会有未定义的行为。

std::string 传递给printf() 的正确方法是使用%s 格式说明符并使用c_str() 成员,例如:

printf("string=%s\n", s.c_str());

您正在使用%d 作为std::string::size_type 的格式说明符。这可能会起作用,但不能保证会起作用!虽然std::string::size_type保证是std::size_t,但是这个类型可能是unsigned intunsigned longunsigned long long,甚至是一些非标准的内置整数类型!为std::size_t 拼写格式说明符的正确方法是%zu(当然不是另一篇文章中的%ul:它可能本来应该是%lu,但仍然是错误的:

printf("string.size()=%zu\n", s.size());

由于您使用的是 C++,因此最好让编译器确定要调用的格式:

std::cout << "\n string is: " << theString << " of length " << theString.length() << " \n";
std::cout << "\n buffer is: " << buffer << " of length " << theString.length() << " \n";

【讨论】:

    【解决方案2】:

    程序具有未定义的行为,因为函数 printf 无法输出 std::string 类型的对象。当使用格式符号 %s 时,该函数假定相应的参数是字符串文字的指针。因此该函数尝试将 std::string 类型的对象作为字符串文字输出。 要正确使用函数 printf 和 std::string 类型的对象,您应该使用成员函数 c_str() 或 data() (对于 C++ 2011)将它们转换为字符串。例如

    printf("\n string is: %s of length %ul \n", theString.c_str(), theString.length());
    

    【讨论】:

    • 不是 %ul 不是 不是 unsigned long 的格式说明符,尽管它是文字后缀:%ul%uunsigned int 后跟l 字符。您可能打算写 %lu,但仍然是错误的:std::size_t 的正确格式说明符是 %zu
    • @Dietmar Kühl,我认为你错了。来自 C 标准 l (ell) 指定后面的 d、i、o、u、x 或 X 转换说明符适用于 long int 或 unsigned long int...
    • @Dietmar Kühl 或者你的意思是应该使用 %lu 吗?至于 size_t 那么谢谢。当我写这篇文章的时候,我不记得那个说明符了。
    • size_t 的大小未指定,并且不能保证它是unsigned long 类型。对于size_t,正确的长度修饰符是z(参见7.21.6.1 第7 段,第六项:“z 指定以下dioux , 或 X 转换说明符适用于 size_t 或相应的有符号整数类型参数;...")。 ...并且长度修饰符在转换说明符之前,即,%zu 代表 size_t%lu 代表 unsigned long
    【解决方案3】:

    正如另一个答案所指出的那样,(null) 值的原因是因为(正式)printf %s 需要 const char* 作为其输入,std::string不是 const char* 并导致未定义的行为...当您执行第一个 theString.length() 时出现随机数的原因与您的编译器有关。

    在带有 g++ 4.2.1 的 OpenBSD 5.1 上,当我编译代码时,会收到很多警告;特别是使用itoa,我不得不更改为sprintf,我还收到以下关于%s 的警告,printfstd::string 上:warning: cannot pass objects of non-POD type 'struct std::string' through '...'; call will abort at runtime

    当我运行编译后的代码时,它会在第一个 printf 处中止和核心转储,因为在 std::string“对象”上调用了 %s(这是“正确的”行为,尽管在技术上仍然是“未定义的行为” ')

    但是,当我在 MS 编译器上编译上述代码(未经编辑)时,我实际上得到了以下输出:

    string is: 997799 of length 6
    buffer is: 997799 of length 6
    

    这仍然是“未定义”但“预期”的结果。

    显然,MS 编译器识别 printf 上的 std::string 并将其“更改”为 string.c_str() 调用或 MS C/C++ 库中的 printf 接受 std::string 并调用 @ 987654345@给你。

    所以要 100%“兼容”和 C++“兼容”,请考虑以下几点:

    #include <iostream>
    #include <sstream>
    
    int main(int argc, char **argv)
    {
        std::stringstream theString;
        int i = 997799; //(simplified)
        theString << i;
    
        std::cout << "string is: " << theString.str() << " of length " << theString.str().length() << std::endl;
        // printf("string is: %s of length %d \n", theString.str().c_str(), theString.str().length());
        return 0;
    }
    

    混合 C++ 和 C 调用/样式通常不是一个好习惯,但恕我直言,我经常使用 printf 以“方便”,但通常将其取出或使用一些 #defines 仅用于调试构建。

    我希望这可以帮助回答“为什么选择 #2”

    【讨论】:

    【解决方案4】:

    我的编译器(os x 上的 clang)报告以下错误:

    c++     foo.cc   -o foo
    foo.cc:6:1: error: 'main' must return 'int'
    void main()
    ^~~~
    int
    foo.cc:11:5: error: use of undeclared identifier 'itoa'
        itoa(i,buffer,10);                      
        ^
    foo.cc:14:48: error: cannot pass non-POD object of type 'string' (aka
          'basic_string<char, char_traits<char>, allocator<char> >') to variadic
          function; expected type from format string was 'char *'
          [-Wnon-pod-varargs]
        printf("\n string is: %s of length %d \n", theString, theString.length());
                              ~~                   ^~~~~~~~~
    foo.cc:14:48: note: did you mean to call the c_str() method?
        printf("\n string is: %s of length %d \n", theString, theString.length());
                                                   ^
                                                            .c_str()
    foo.cc:14:59: warning: format specifies type 'int' but the argument has type
          'size_type' (aka 'unsigned long') [-Wformat]
        printf("\n string is: %s of length %d \n", theString, theString.length());
                                           ~~                 ^~~~~~~~~~~~~~~~~~
                                           %lu
    foo.cc:15:56: warning: format specifies type 'int' but the argument has type
          'size_type' (aka 'unsigned long') [-Wformat]
        printf("\n buffer is: %s of length %d \n", buffer, theString.length());
                                           ~~              ^~~~~~~~~~~~~~~~~~
                                           %lu
    

    让我们修复它们:

    1. void main() { ... } 应该是int main() { ... }

    2. itoa() 在 C++ 中不是标准的。不过有std::stoi(),所以让我们改用它。如果我们正在写入字符数组,我们也可以使用std::snprintf,而不是std::string

    3. 如果我们要将printf()std::string 一起使用,我们将需要使用std::string::c_str() 方法返回一个字符数组并打印它。 printf 本身不理解 C++ 对象。 (我们也可以使用 cout,它可以理解 c++ 对象)。

    这些更改导致代码如下所示:

    #include <cstdio>
    #include <string>      
    #include <cstdlib>
    
    using namespace std;
    int main()
    {
        char buffer[10];
        string theString;
        int i = 997799; //(simplified)
    
        snprintf(buffer, sizeof(buffer), "%d", i);
        theString = buffer;
    
        printf("string is: %s of length %lu\n", theString.c_str(), theString.length());
        printf("buffer is: %s of length %lu\n", buffer, strlen(buffer));
    }
    

    这段代码输出:

    [4:46pm][wlynch@watermelon /tmp] ./foo
    string is: 997799 of length 6
    buffer is: 997799 of length 6
    

    【讨论】:

    • 如前所述,%lu 是错误的格式说明符:不能保证std::size_ttypedefunsigned long。正确的格式说明符是%zu。方便的是,这清楚地表明 C 的格式说明符真的很难使用,很明显使用 IOStreams 更安全。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多