使用printf和cout获得不同的输出 - C++

Scr*_*est 17 c++ string printf

我有一个我想要打印的字符串.当我使用cout它时,它输出完美但是使用printf它会损坏它.

这是代码:

int main ( int argc, char *argv[] )
{
    // Check to make sure there is a single argument
    if ( argc != 2 )
    {
        cout<<"usage: "<< argv[0] <<" <filename>\n";
        return 1;
    }

    // Grab the filename and remove the extension
    std::string filename(argv[1]);
    int lastindex = filename.find_last_of("."); 
    std::string rawname = filename.substr(0, lastindex);

    cout << "rawname:" << rawname << endl;
    printf("rawname: %s", rawname);

}
Run Code Online (Sandbox Code Playgroud)

cout给我"rawname:文件"
printf给了我"rawname:",然后一串字符的波浪

Nek*_*ios 20

这是因为rawname被定义为std :: string.你需要使用

printf("rawname: %s", rawname.c_str());

原因是带有%s的printf期望内存中有一个空终止的C字符串.而std :: string stl字符串并不完全是原始的 - 它最终会在你的情况下终止null,不确定这是否是一种保证,因为长度是由stl容器类在内部管理的.

编辑:

正如评论中指出的那样,内部保证无效终止.所以你所看到的'波浪线'是该字符串中所有已分配但未被利用(或初始化)的内存的输出,直到空终止符.

  • 是的,标准保证std :: string :: c_str()为空终止. (4认同)
  • 实际上,我认为OP正在看到一些二进制文件,因为"真实"字符存储在动态分配的数组中(通常),而不是直接存储在字符串中. (3认同)
  • @Nicol Bolas:c_str()保证NUL终止,但原始代码没有使用c_str(),即使它以某种方式访问​​了字符串的文本缓冲区 - 即`%s`参数指向`rawname.data ()` - 这不会被保证*NUL被终止 - 尽管在任何理智的实现中它本来都是;-) (2认同)

Ton*_*roy 15

什么有效

printf("%s", my_string.c_str());
Run Code Online (Sandbox Code Playgroud)

出了什么问题 - 故事大纲

简短说明(后面解释的假设):

std::string s {
   // members in unknown order
   size_type member:    13 00 00 00                       HEAP
   const char* member:  pointer C to ................ "this and that"
};

You print characters here ^^^^^^       not          here ^^^^^.
Run Code Online (Sandbox Code Playgroud)

您不能将非POD数据传递给函数 - 例如printf()- 接受任意数量的参数使用....("..."参数是C++继承自C的一个特性,它本身不适合与复杂的C++对象一起使用).

你甚至可以编译它?

我的GCC编译器不喜欢这样:

printf("rawname: %s", rawname);
Run Code Online (Sandbox Code Playgroud)

GCC 4.5.2错误:

cannot pass objects of non-trivially-copyable
type 'struct std::string' through '...'
Run Code Online (Sandbox Code Playgroud)

GCC 4.1.2警告+运行时行为:

cannot pass objects of non-POD type 'struct std::string'
through '...'; call will abort at runtime

# ./printf_string
zsh: illegal hardware instruction  ./printf_string
Run Code Online (Sandbox Code Playgroud)

他们不会编译它,因为没有标准的方法来传递对象....编译器不能简单地...通过值或引用/指针来满足它们,因此不知道要生成什么代码.

但是你的编译器勇敢地做了些什么.让我们考虑std :: string对象暂时的样子,然后返回编译器可能接收和访问它的方式.

std :: string对象的g

未指定std :: string的内部,但通常包含以下任何内容:

  • 记录当前大小的成员或指向字符串末尾的指针(ala end())
    • 或者允许对另一个进行简单的计算,但是我已经检查的标准库实现优化了指针/ end()成员并进行了计算size()- 使用惯用迭代器循环更好地工作
  • 指向堆上字符缓冲区的指针(实际上它可能保持NUL终止并c_str()直接返回它,但是这个指针 - 通过data()成员函数可用,标准允许解决非NUL终止文本,因此理论上它可能有一个NUL终止符仅在c_str()被调用时附加,或者c_str()可能将文本复制到别处,然后附加NUL并返回指向该新缓冲区的指针)
  • 一个"短字符串优化"数据缓冲区,所以只有几个字符的字符串不需要使用堆

和/或

  • 指向其他地方的某个引用计数对象的指针(上面有成员+引用计数器,互斥锁,......?)

示例:存储文本的简单字符串实现

这些可以是任何顺序.所以,最简单的可能性是:

std::string s = "this and that";
Run Code Online (Sandbox Code Playgroud)

现在,

  • "this and that"是一个字符串文字,让我们说地址为"A"; 这些数据被复制到string; 该string不会记得它有它

  • s是实际的std::string对象,让我们说地址为"B"; 让我们想象一下,这是最简单的:

    • size_type size_;(将保持值13,是strlen("this and that"))
    • const char* p_data_; 将指向一些新分配的堆内存 - 让我们说在地址"C" - 已经复制了"this和that\0"

至关重要的是,地址"A",地址"B"和地址"C"是不同的!

printf()如何看到std :: string

如果我们有一个糟糕的编译器,将尝试通过我们的std::string对象printf(),则有两件事情printf()可能会收到的,而不是const char*"%s"告诉它期望:

1)指向std::string对象的指针,即地址"B"

2)地址"A" sizeof(std::string)复制到某个堆栈地址"B" 的数据字节和/或寄存器,如果它可以处理这些东西,它会期望它;-Pprintf()

printf() 然后开始从该地址打印字节,就像它们是字符一样,直到找到0/NUL字节:

  1. 对于上面的场景1,它打印对象中的字节,例如:

    • 比如size_type是4个字节并且在对象的开头; 大小为13,它可能是13,0,0,0或0,0,0,13,具体取决于机器是使用big-endian还是little-endian存储约定......如果它在第一个NUL停止,它会打印字符13(恰好是ASCII回车符/ CR值,将光标返回到行首)然后停止,或者它可能绝对不打印.在你自己的情况下你的字符串内容是不同的,所以它会打印一些其他垃圾,但可能只有一个或两个字符才能达到0/NUL.

    • 假设const char*在"C"处的堆分配缓冲区恰好位于对象的开头,那么将打印该地址中的各个字符:对于32位指针,可能是4个垃圾字符(假设它们都没有发生在是0/NUL),对于64位它将是8,然后它将继续下一个字段std::string(可能是一个end()跟踪指针,但如果它是一个size_type更有可能有0/NUL 的字段) .

  2. printf()可能会将std::string对象数据的前四个字节解释为指向更多文本数据的指针...这与1)不同:假设size_type成员是第一个且值为13,printf()可能会将其误解const char*为地址13,然后尝试从那里读取字符.这实际上保证在打印任何东西之前崩溃(在现代操作系统上),因此这种行为实际上不太可能发生,这使我们得到"1".


jon*_*ham 5

您需要打印std :: string的内部char*:

printf("rawname: %s", rawname.c_str());
Run Code Online (Sandbox Code Playgroud)