什么有效
printf("%s", my_string.c_str());
问题概述
简短说明(假设稍后解释):
std::string s {
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
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”。
printf
是一个不安全的 C 函数。例如%s
需要一个char*
类型的参数,但它无法检查您是否提供了正确的类型。另一方面,cout
是一个 C++ 类型安全的运算符,根据您提供的类型采取不同的操作。 - J.N.