使用printf和cout在C++中输出结果不同

17

我有一个字符串想要打印。当我使用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);

}

cout 给了我 "rawname: file"。
printf 给了我 "rawname: ",然后是一堆波浪形字符。


除了答案之外,您需要了解 printf 是一个不安全的 C 函数。例如 %s 需要一个 char* 类型的参数,但它无法检查您是否提供了正确的类型。另一方面,cout 是一个 C++ 类型安全的运算符,根据您提供的类型采取不同的操作。 - J.N.
5个回答

20

之所以要使用

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

是因为rawname已被定义为std::string,而printf与%s配合使用时需要在内存中有一个以null结尾的C字符串。但是一个std::string并不完全是原始的,因为它在你的情况下最终会以null结尾,虽然这甚至不一定是一个保证,因为stl容器类内部管理了长度。

编辑:

正如评论中指出的那样,std::string在内部保证是以null结尾的。所以你看到的“波浪线”是该字符串中分配但未使用(或初始化)的所有内存的输出,直到null终止字符。


4
是的,std::string::c_str()函数在标准中保证是以空字符结尾的。 - Nicol Bolas
1
那个解决方案很好,但是解释有误。这里没有涉及“原始”(与“熟”的?)文本表示,只是 printf() 可能会打印 std::string 对象本身的字节(例如存储大小的数字,指向实际文本的指针),而不是跟随指针到文本值。因为它甚至没有查看字符串的文本,所以它打印垃圾,直到偶然出现一个可以解释为 NUL 的 0 字节(在大小成员变量中幸运地很常见)。 - Tony Delroy
2
@Nicol Bolas:c_str()保证以NUL结尾,但原始代码没有使用c_str(),即使它以某种方式访问了字符串的文本缓冲区——即%s参数指向rawname.data()——也不能保证以NUL结尾,尽管在任何合理的实现中,它都将是这样的;-) - Tony Delroy
3
我认为原帖作者看到的是一些二进制代码,因为“真正的”字符通常存储在动态分配的数组中,而不是直接在字符串中。 - Matthieu M.

15

什么有效

printf("%s", my_string.c_str());

问题概述

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

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 ^^^^^.

当你使用...传递任意数量参数的函数 - 例如printf() - 时,你不能传递非POD数据。("..."参数是C++从C继承而来的一个特性,对于复杂的C ++对象来说,它本质上不适用)

你甚至可以编译这个?

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

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

GCC 4.5.2错误:

cannot pass objects of non-trivially-copyable
type 'struct std::string' through '...'

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
他们不会将其编译,因为没有标准的方法可以使用...传递对象。编译器无法仅从...中确定它们是按值还是按引用/指针方式需要,因此不知道要生成什么代码。
但是你的编译器勇敢地做了某些事情。让我们考虑一下std::string对象的样子,然后再返回到您的编译器可能如何接收和访问它。
std::string对象的内部
std::string的内部未指定,但通常包括以下任意项:
- 记录当前大小的成员或指向字符串结尾的指针(ala end()) - 二者都可以简单地计算出另一个,但是我检查过的几个标准库实现都优化为指针/end()成员并计算size()——与惯用的迭代器循环更配合。
- 指向堆上字符缓冲区的指针(实际上可能会保持NUL终止,并直接通过c_str()返回,但是这个指针——通过data()成员函数可用,允许标准引用非NUL终止的文本,因此theoretically它可以只在调用c_str()时附加NUL终止符号,或者c_str()可能将文本复制到其他地方,然后附加NUL并返回指向该新缓冲区的指针)
- “短字符串优化”数据缓冲区,因此只有几个字符的字符串不需要使用堆
或者
- 指向其他地方的某个引用计数对象的指针(具有上述成员+引用计数器、互斥锁等)。
示例:存储文本的简单字符串实现
这些可以是任何顺序。因此,最简单的可能性是类似以下内容的东西:
std::string s = "this and that";
现在,“this and that”是一个字符串文字,假设它的地址为“A”,这个数据被复制到了string中,string不记得它来自哪里。变量s是实际的std::string对象,假设它的地址为“B”,它非常简单:size_type size_;(将保存值13,即strlen("this and that"))和const char* p_data_;将指向一些新分配的堆内存 - 假设它的地址是“C”,其中复制了“this and that\0”。重要的是,地址“A”,地址“B”和地址“C”是不同的!如果我们有一个坏的编译器,试图将std::string对象传递给printf(),那么printf()可能会收到两件事情,而不是const char*,告诉它期望什么:“%s”:1)指向std::string对象的指针,即地址“B”,2)从地址“A”复制sizeof(std::string)字节的数据,复制到某个栈地址“B”和/或寄存器中,如果printf()可以处理这些东西;-P。然后,printf()开始打印该地址的字节,就好像它们是字符,直到找到一个0/NUL字节。对于上面的情况1,它会打印对象中的字节,例如:假设size_type是4个字节并且位于对象开头;大小为13,则可能是13、0、0、0或0、0、0、13,具体取决于机器使用的大端或小端存储约定...假设它停在第一个NUL处,它将打印字符13(这恰好是ASCII回车/ CR值,返回光标到行首),然后停止,或者它可能根本什么也不打印。如果指向位于“C”处的堆分配缓冲区的const char*恰好位于对象开头,则会打印该地址中的单个字符:对于32位指针,这可能是4个垃圾字符(假设它们中没有一个是0/NUL),对于64位,它将是8个字符,然后它将继续以std::string中的下一个字段(可能是一个end()跟踪指针,但如果它是一个size_type字段,则更有可能有一个0/NUL)。printf()可能会将std::string对象数据的前4个字节解释为进一步的文本数据的指针......这与情况1不同:假设size_type成员首先出现,其值为13,则printf()可能会错误地将其解释为指向地址13的const char*,然后尝试从那里读取字符。这实际上几乎肯定会在打印任何内容之前崩溃(在现代操作系统上),因此这种行为实际上很少发生,这使我们留下了“1”。

我正在思考为什么gcc(版本4.1.2)决定将此作为运行时错误,而不是在编译时捕获该错误。这是一个致命错误,它应该关闭编译过程,而不是偷偷地将非法指令插入代码! - Mark Lakata

5
您需要打印std::string的内部char*内容:
printf("rawname: %s", rawname.c_str());

3

试试这个

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

rawname不是字符数组,而是std :: string类的实例。要获取实际的字符数组,您应该调用c_str()函数。


2
你是否尝试在printf中使用rawname.c_str()?

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