将 printf 与 C++ 迭代器一起使用

using printf with C++ iterators

我不清楚在这种情况下将 std::printf 与迭代器一起使用时发生了什么:

#include <string>
#include <vector>
#include <list>
#include <cstdio>
int main() {
   std::string str{"hello, world"};
   std::printf("%s\n", str.begin()); // prints "hello, world"
   std::vector<char> vec(str.begin(), str.end());
   std::printf("%s\n", vec.begin()); // still same output
   std::list<char> lst(str.begin(), str.end());
   std::printf("%s\n", lst.begin()); // weird ascii outputs ???
}

我还使用了 实用程序来模拟 printf 的行为,看起来它甚至在 va_arg(ap, char*) 下仍然有效,并且与上面的代码的行为方式相同,我有几个问题. 它是否适用于连续的数组(使用 RAI 迭代器)?还是 UB?为什么甚至 va_arg 也会允许这样的事情,是否有一些隐式转换?

TL;DR:这都是无效的 C++,因为 std::printf 没有类型安全 API。 将对 non-POD objects 的引用作为 std::printf 参数传递始终无效printf 有一个 C API.

在所有情况下发生的都是未定义的行为:您不应该编写那样的代码。语言规范本身并没有告诉你会发生什么。这样的代码是一个错误,任何合理的代码审查都会失败。

对于 std::stringstd::vector 迭代器,它们恰好在您的平台上具有相同的大小、值、布局和参数传递约定 作为 printf 期望的 const char * 它们恰好分别包含 string/vector 开头的地址。 std::stringstd::vector<char> 对数据数组的布局相同,即元素是连续的,巧合的是 std::vector 中的内存块在字符串末尾有一个零,所以这恰好工作。也许它只适用于你在调试模式下构建:)

对于 std::list 迭代器,它可能恰好包含指向列表数据块 header 的指针,或者指向 std::string 开始的指针在列表项中,因此您会得到无意义的输出,但不会导致无效的内存访问,因为等效指针指向某个已分配的内存区域并且在其他方​​面是有效的。当然,这是您平台上实施细节的结果。在某些平台和构建选项中,代码只会崩溃。不,你甚至不能指望它会可靠地崩溃。这就是UB很差的原因。

在您的平台上,std::printf("%s\n", *lst.begin()); 可能只是一个错误但碰巧“起作用”,因为 *lst.begin() 产生对 std::string 的引用,而这样的引用通常像指针一样布置和传递。当然这仍然是UB,所以不要做任何事情!