将 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::string
和 std::vector
迭代器,它们恰好在您的平台上具有相同的大小、值、布局和参数传递约定 作为 printf
期望的 const char *
, 和 它们恰好分别包含 string/vector 开头的地址。 std::string
和 std::vector<char>
对数据数组的布局相同,即元素是连续的,巧合的是 std::vector
中的内存块在字符串末尾有一个零,所以这恰好工作。也许它只适用于你在调试模式下构建:)
对于 std::list
迭代器,它可能恰好包含指向列表数据块 header 的指针,或者指向 std::string
开始的指针在列表项中,因此您会得到无意义的输出,但不会导致无效的内存访问,因为等效指针指向某个已分配的内存区域并且在其他方面是有效的。当然,这是您平台上实施细节的结果。在某些平台和构建选项中,代码只会崩溃。不,你甚至不能指望它会可靠地崩溃。这就是UB很差的原因。
在您的平台上,std::printf("%s\n", *lst.begin());
可能只是一个错误但碰巧“起作用”,因为 *lst.begin()
产生对 std::string
的引用,而这样的引用通常像指针一样布置和传递。当然这仍然是UB,所以不要做任何事情!
我不清楚在这种情况下将 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 ???
}
我还使用了 va_arg(ap, char*)
下仍然有效,并且与上面的代码的行为方式相同,我有几个问题.
它是否适用于连续的数组(使用 RAI 迭代器)?还是 UB?为什么甚至 va_arg
也会允许这样的事情,是否有一些隐式转换?
TL;DR:这都是无效的 C++,因为 std::printf
没有类型安全 API。 将对 non-POD objects 的引用作为 std::printf
参数传递始终无效:printf
有一个 C API.
在所有情况下发生的都是未定义的行为:您不应该编写那样的代码。语言规范本身并没有告诉你会发生什么。这样的代码是一个错误,任何合理的代码审查都会失败。
对于 std::string
和 std::vector
迭代器,它们恰好在您的平台上具有相同的大小、值、布局和参数传递约定 作为 printf
期望的 const char *
, 和 它们恰好分别包含 string/vector 开头的地址。 std::string
和 std::vector<char>
对数据数组的布局相同,即元素是连续的,巧合的是 std::vector
中的内存块在字符串末尾有一个零,所以这恰好工作。也许它只适用于你在调试模式下构建:)
对于 std::list
迭代器,它可能恰好包含指向列表数据块 header 的指针,或者指向 std::string
开始的指针在列表项中,因此您会得到无意义的输出,但不会导致无效的内存访问,因为等效指针指向某个已分配的内存区域并且在其他方面是有效的。当然,这是您平台上实施细节的结果。在某些平台和构建选项中,代码只会崩溃。不,你甚至不能指望它会可靠地崩溃。这就是UB很差的原因。
在您的平台上,std::printf("%s\n", *lst.begin());
可能只是一个错误但碰巧“起作用”,因为 *lst.begin()
产生对 std::string
的引用,而这样的引用通常像指针一样布置和传递。当然这仍然是UB,所以不要做任何事情!