仓库源文站点原文


title: 一段奇怪的 CPP 代码 date: 2023-12-03 13:56:55 updated: 2023-12-03 13:56:55 categories: 杂项 index_img: /image/cpp/cpp-list-insert/docs.png tag:


最近发现了一个奇怪的代码,在 C++17 下。使用的 cmake 命令是

"~/Applications/CLion Nova.app/Contents/bin/cmake/mac/aarch64/bin/cmake" -DCMAKE_BUILD_TYPE=Debug "-DCMAKE_MAKE_PROGRAM=~/Applications/CLion Nova.app/Contents/bin/ninja/mac/aarch64/ninja" -G Ninja -S ~/Code/ClionProject -B ~/Code/ClionProject/cmake-build-debug

而这段代码则是

list<int> l;
for (int i = 0; i < 10; ++i) l.push_back(i);
auto iter = l.begin();
for (int i = -1; i >= -10; --i) l.insert(iter--, i);
for (int&v: l) cout << v << ' ';

这段代码的结果却是

-1 0 1 -10 2 -9 3 -8 4 -7 5 -6 6 -5 7 -4 8 -3 9 -2

如果稍微调整一下,比如这样的代码

list<int> l;
for (int i = 0; i < 10; ++i) l.push_back(i);
auto iter = l.begin();
for (int i = -1; i >= -10; --i) {
    l.insert(iter, i);
    --iter;
}
for (int&v: l) cout << v << ' ';

得到的结果却是

-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9

如果调整成这样

list<int> l;
for (int i = 0; i < 10; ++i) l.push_back(i);
auto iter = l.begin();
++iter;
for (int i = -1; i >= -10; --i) {
    l.insert(--iter, i);
}
for (int&v: l) cout << v << ' ';

得到的结果也是

-10 -9 -8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9

这似乎有点不太符合预期。至少后两个是符合预期的,而第一个就有点奇怪了。第一反应是不是踩到 UB 了,但是很快在文档里找到了不符合预期的描述

docs

既然如此,那么就写一段测试代码看看

list<int> l;

void f(list<int>::iterator iter, int v) {
    cout << *iter << ' ';
    l.insert(iter, v);
    cout << *iter << endl;
}

void solve() {
    for (int i = 0; i < 10; ++i) l.push_back(i);
    auto iter = l.begin();
    for (int i = -1; i >= -10; --i) {
        f(iter--, i);
        cout << (iter == l.begin()) << endl;
        for (int&v: l) cout << v << ' ';
        cout << endl;
    }
}

此处进行了一下代理,将每次试图写入钱,通过 f 函数进行代理后,再执行插入操作。结果发现

0 0
0
-1 0 1 2 3 4 5 6 7 8 9 
11 12
0
-1 0 1 2 3 4 5 6 7 8 9 -2 
9 9
0
-1 0 1 2 3 4 5 6 7 8 -3 9 -2 
8 8
0
-1 0 1 2 3 4 5 6 7 -4 8 -3 9 -2 
7 7
0
-1 0 1 2 3 4 5 6 -5 7 -4 8 -3 9 -2 
6 6
0
-1 0 1 2 3 4 5 -6 6 -5 7 -4 8 -3 9 -2 
5 5
0
-1 0 1 2 3 4 -7 5 -6 6 -5 7 -4 8 -3 9 -2 
4 4
0
-1 0 1 2 3 -8 4 -7 5 -6 6 -5 7 -4 8 -3 9 -2 
3 3
0
-1 0 1 2 -9 3 -8 4 -7 5 -6 6 -5 7 -4 8 -3 9 -2 
2 2
0
-1 0 1 -10 2 -9 3 -8 4 -7 5 -6 6 -5 7 -4 8 -3 9 -2

仍然有这个奇奇怪怪的问题。

正当我想要搜索一些文档来看看是不是什么奇奇怪怪的 bug 的时候,突然意识到一个问题:i++; 等价于下面这三行代码

auto tmp = i;
++i;
return tmp;

这似乎就能解释为什么了!因为在试图进行 iter-- 操作的时候,又进行了插入操作,实际上导致了 iter 本身先移动到了前一个指针的位置,而在 STL 标准库实现的 list 中,这个链表是一个双向带头循环链表,故实际上此时 iter 是先被移动到了 end() 的位置,然后再返回了 begin() 的位置,并在 begin() 前插入了一个值,使得实际上的 begin() 发生了更新。而实际上我们的 iter 早就被移动到 end() 的位置。