SSO? SOS!
· 8 min read
这是我在编写编译器的过程中遇到的一个让人拍案叫绝的陷阱。特此记录下来。
问题
假设你正在编写源代码处理系统。你设计了这样一个函数和类:
std::vector<std::string_view> splitLines(std::string_view view) {
std::vector<std::string_view> lines;
const char *p = view.begin(), *q = p, *r = view.end();
while (q != r) {
if (*q++ == '\n') {
lines.emplace_back(p, q - 1);
p = q;
}
}
if (p != q) lines.emplace_back(p, q);
return lines;
}
struct Source {
std::vector<std::string> snippets;
std::vector<std::string_view> lines;
void append(std::string code) {
snippets.push_back(std::move(code));
for (auto line : splitLines(snippets.back())) {
lines.push_back(line);
}
}
};
为了效率,你没有真的去分割字符串——而是利用 string_view
,引用字符串中的一段作为一行,并把它们存入 lines
。但是 string_view
并没有字符串的所有权,为了保证它引用的字符串不被回收,再使用 snippets
来保存这些字符串。如下图所示:
下面我们来试一试:
int main() {
Source source;
source.append("ab\ncd\nef");
for (auto line : source.lines) {
std::cout << line << std::endl;
}
}
输出:
ab
cd
ef
很好!
可是,当我们试图多次调用 append 的时候
int main() {
Source source;
source.append("line 1");
source.append("line 2");
source.append("line 3");
for (auto line : source.lines) {
std::cout << line << std::endl;
}
}
大跌眼镜的事情发生了:输出了一团乱码!
思考
输出乱码原因有哪些呢?你肯定已经想到了以下两种可能:
string
管理的char[]
被覆写或被释放了string_view
被修改了
先来测一下第一种可能
int main() {
Source source;
source.append("line 1");
source.append("line 2");
source.append("line 3");
for (auto const& snippet : source.snippets) {
std::cout << snippet << std::endl;
}
}
输出:
line 1
line 2
line 3
看上去字符串完好无损。
再来测测第二种可能
int main() {
Source source;
source.append("line 1");
for (auto line : source.lines) {
std::cout << (void*) line.data() << ' ';
}
std::cout << std::endl;
source.append("line 2");
for (auto line : source.lines) {
std::cout << (void*) line.data() << ' ';
}
std::cout << std::endl;
source.append("line 3");
for (auto line : source.lines) {
std::cout << (void*) line.data() << ' ';
}
}
输出:
0x26a40fc18e0
0x26a40fc18e0 0x26a40fc1c90
0x26a40fc18e0 0x26a40fc1c90 0x26a40fc1d00
string_view
也没有变化!
规律
你可能百思不得其解。先别急,我们再来做一个实验。
之前我们就发现,如果只 append
一次是没问题的。那么不妨测试一下乱码与 append
次数有没有什么关系。
int main() {
Source source;
for (int i = 0; i < 10; ++i) {
source.append("OOO");
for (auto line: source.lines) {
if (line == "OOO") {
std::cout << line << ' ';
} else {
std::cout << "***" << ' ';
}
}
std::cout << std::endl;
}
}
输出:
OOO
*** OOO
*** *** OOO
*** *** OOO OOO
*** *** *** *** OOO
*** *** *** *** OOO OOO
*** *** *** *** OOO OOO OOO
*** *** *** *** OOO OOO OOO OOO
*** *** *** *** *** *** *** *** OOO
*** *** *** *** *** *** *** *** OOO OOO
这太有规律了!每次 append
乱码的 string_view
数量要么保持不变,要么就翻一倍,这似乎暗合了某个容器的某个特性。是的,vector
。于是我们对前面的 Source
作出一点修改:
struct Source {
- std::vector<std::string> snippets;
+ std::deque<std::string> snippets;
};
再执行上面的代码,输出:
OOO
OOO OOO
OOO OOO OOO
OOO OOO OOO OOO
OOO OOO OOO OOO OOO
OOO OOO OOO OOO OOO OOO
OOO OOO OOO OOO OOO OOO OOO
OOO OOO OOO OOO OOO OOO OOO OOO
OOO OOO OOO OOO OOO OOO OOO OOO OOO
OOO OOO OOO OOO OOO OOO OOO OOO OOO OOO
修复了!太棒了!
但,这应该不是 vector
的问题吧?