返回 std::vector 时缺少元素
Missing elements when returning a std::vector
我正在用 C++ 编写一个函数,从理论上讲,它应该接受用户输入并将此输入根据空格拼接成段,return 这些段作为向量。
我目前正在做的是在输入字符串上使用 strtok() 以用空格分隔单词。对于每个 "word",我将其推入缓冲区向量。遍历每个单词后,我 return 向量。
这是我目前的代码:
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
std::vector<char*> tokenize(std::string input_, char const* delims=" \t\r\n\a")
{
char* input = (char*)input_.c_str();
std::vector<char*> tk_stream;
char* tk = strtok(input, delims);
while(tk != NULL) {
tk_stream.push_back(tk);
tk = strtok(NULL, delims);
}
return tk_stream;
}
int main(int argc, char** argv)
{
while (true) {
std::string input;
std::getline(std::cin, input);
if (input.empty()) {
continue;
}
std::vector<char*> tks = tokenize(input);
for (char* el : tks) {
std::cout << el << std::endl;
}
}
return 0;
}
那么应该发生什么?好吧,如果我输入“1 2 3 4”,它应该在不同的行上打印每个数字。这实际上适用于该输入。但是当输入的字符串长度比较大时,比如“1 2 3 4 5 6 7 8 9”,输出就不同了:
1 2 3 4 5 6 7 8 9
5
6
7
8
9
它缺少前 4 个数字!这也适用于任何长度超过此长度的字符串,并且缺失数字的数量是常量。我也注意到较长的句子会发生这种情况。例如 "hello everyone this is a test" 给出:
hello everyone this is a test
0��
this
is
a
test
我已经对 gdb 进行了一些挖掘,发现了一些有趣的东西。通过输入“1 2 3 4 5 6 7 8 9”,我在 'tk_stream' 被 return 之前设置了一个断点并检查了它的值:
(gdb) print tk_stream
= std::vector of length 9, capacity 16 = {0x6176c0 "1", 0x6176c2 "2", 0x6176c4 "3", 0x6176c6 "4", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}
这似乎是正确的。但是当我从函数中 returned 执行几行并检查 'tks' 的值(向量应该包含 'tokenize' 的 returned 值功能);我收到这个:
(gdb) print tks
= std::vector of length 9, capacity 16 = {0x6176c0 "", 0x6176c2 "a", 0x6176c4 "", 0x6176c6 "", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}
缺少前 4 个条目,第二个条目出现乱码。
所以在 'tk_stream' 向量的 returning 中一定发生了一些事情。
这种异常行为的原因是什么?我该如何解决这个问题,以免删除矢量的任何元素?
您不想使用像 char*
这样的原始指针,而是使用 std::string
。
类似于:
std::vector<std::string> tokenize(std::string input_, const std:string delims=" \t\r\n\a")
{
std::string input = input_;
std::vector<std::string> tk_stream;
// ...
您正在按值将字符串传递给标记化函数。然后,您对该本地字符串对象调用 c_str() 并将指向该 space 的指针存储到您的向量中。然后你的函数 returns,以及本地字符串对象中的存储。现在这意味着您存储到向量中的所有指针现在都是悬挂指针。引用其中任何一个都是未定义的行为。
它 "appears to work" 用于短字符串(可能字符串长度小于 16 个字符),这是由于所谓的短字符串优化。 std::string 的许多实现在 std::string 对象本身内部有一个小缓冲区(常见大小为 16 字节,但标准未定义)。一旦字符串长于该长度,std::string 将动态分配一个缓冲区来保存该字符串。当您使用短字符串时,您的悬挂指针指向您的堆栈,并且您的数据尚未在那里被覆盖。当您使用长字符串时,您的指针指向内存中的某个任意位置,该位置可能已被其他内容覆盖。
哦,要修复,请通过引用传递您的 std::string:const std::string & input_
。
我正在用 C++ 编写一个函数,从理论上讲,它应该接受用户输入并将此输入根据空格拼接成段,return 这些段作为向量。
我目前正在做的是在输入字符串上使用 strtok() 以用空格分隔单词。对于每个 "word",我将其推入缓冲区向量。遍历每个单词后,我 return 向量。
这是我目前的代码:
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
std::vector<char*> tokenize(std::string input_, char const* delims=" \t\r\n\a")
{
char* input = (char*)input_.c_str();
std::vector<char*> tk_stream;
char* tk = strtok(input, delims);
while(tk != NULL) {
tk_stream.push_back(tk);
tk = strtok(NULL, delims);
}
return tk_stream;
}
int main(int argc, char** argv)
{
while (true) {
std::string input;
std::getline(std::cin, input);
if (input.empty()) {
continue;
}
std::vector<char*> tks = tokenize(input);
for (char* el : tks) {
std::cout << el << std::endl;
}
}
return 0;
}
那么应该发生什么?好吧,如果我输入“1 2 3 4”,它应该在不同的行上打印每个数字。这实际上适用于该输入。但是当输入的字符串长度比较大时,比如“1 2 3 4 5 6 7 8 9”,输出就不同了:
1 2 3 4 5 6 7 8 9
5
6
7
8
9
它缺少前 4 个数字!这也适用于任何长度超过此长度的字符串,并且缺失数字的数量是常量。我也注意到较长的句子会发生这种情况。例如 "hello everyone this is a test" 给出:
hello everyone this is a test
0��
this
is
a
test
我已经对 gdb 进行了一些挖掘,发现了一些有趣的东西。通过输入“1 2 3 4 5 6 7 8 9”,我在 'tk_stream' 被 return 之前设置了一个断点并检查了它的值:
(gdb) print tk_stream
= std::vector of length 9, capacity 16 = {0x6176c0 "1", 0x6176c2 "2", 0x6176c4 "3", 0x6176c6 "4", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}
这似乎是正确的。但是当我从函数中 returned 执行几行并检查 'tks' 的值(向量应该包含 'tokenize' 的 returned 值功能);我收到这个:
(gdb) print tks
= std::vector of length 9, capacity 16 = {0x6176c0 "", 0x6176c2 "a", 0x6176c4 "", 0x6176c6 "", 0x6176c8 "5", 0x6176ca "6", 0x6176cc "7", 0x6176ce "8", 0x6176d0 "9"}
缺少前 4 个条目,第二个条目出现乱码。
所以在 'tk_stream' 向量的 returning 中一定发生了一些事情。
这种异常行为的原因是什么?我该如何解决这个问题,以免删除矢量的任何元素?
您不想使用像 char*
这样的原始指针,而是使用 std::string
。
类似于:
std::vector<std::string> tokenize(std::string input_, const std:string delims=" \t\r\n\a")
{
std::string input = input_;
std::vector<std::string> tk_stream;
// ...
您正在按值将字符串传递给标记化函数。然后,您对该本地字符串对象调用 c_str() 并将指向该 space 的指针存储到您的向量中。然后你的函数 returns,以及本地字符串对象中的存储。现在这意味着您存储到向量中的所有指针现在都是悬挂指针。引用其中任何一个都是未定义的行为。
它 "appears to work" 用于短字符串(可能字符串长度小于 16 个字符),这是由于所谓的短字符串优化。 std::string 的许多实现在 std::string 对象本身内部有一个小缓冲区(常见大小为 16 字节,但标准未定义)。一旦字符串长于该长度,std::string 将动态分配一个缓冲区来保存该字符串。当您使用短字符串时,您的悬挂指针指向您的堆栈,并且您的数据尚未在那里被覆盖。当您使用长字符串时,您的指针指向内存中的某个任意位置,该位置可能已被其他内容覆盖。
哦,要修复,请通过引用传递您的 std::string:const std::string & input_
。