C++数组中的坑

前言

  在写快排的时候偶然发现了 C++ 数组中的一个坑,具体表现为:对数组元素进行无临时变量的自交换时竟然会将数组该元素置为 0,这应该是 C++ 的一个 BUG Shaun 脑子抽了。

交换函数篇

  据参考资料 [1] 中, C++ 的交换函数可以有如下三种写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一种:使用模板,创建临时变量
template <class T> void swap(T &a, T &b)
{
T temp(a); a = b; b = temp;
}

// 第二种:无临时变量,针对int,double等内建数值类型的基本运算(以double为例)
void swap(double &a, double &b)
{
a = a + b;
b = a - b;
a = a - b;
}

// 第三种:无临时变量,针对int的异或运算
void swap(int &a, int &b)
{
// a ^= b ^= a ^= b;
a = a ^ b;
b = b ^ a;
a = a ^ b;
}

  其中,第一种是通用的交换方法,无论做什么交换都能用第一种,但需要创建一个临时对象;而第二种不需要创建一个临时对象,只能用在 int,double 等内建数值类型上,且存在溢出的风险;第三种同样不需要创建临时对象,只能用在 int 类型上,由于采用位运算,所以不存在溢出的风险,且效率最高。

BUG 复现篇

  bug 复现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>

template <class T> void swap_1(T &a, T &b)
{
T temp(a); a = b; b = temp;
}

void swap_2(int &a, int &b)
{
a = a + b;
b = a - b;
a = a - b;
}

void swap_3(int &a, int &b)
{
a ^= b ^= a ^= b;
}

int main(int argc, char *argv[])
{
int data[] = { 0, 3, 8, 2, 9, 4, 6, 1, 7, 5 };
int a = 1, b = 2, c = 3;
swap_1<int>(data[a], data[b]);
printf("%d\t%d\n", data[a], data[b]); // 输出 8 3
swap_1<int>(data[a], data[a]);
printf("%d\t%d\n", data[a], data[a]); // 输出 8 8
swap_2(data[a], data[b]);
printf("%d\t%d\n", data[a], data[b]); // 输出 3 8

swap_2(data[a], data[a]);
printf("%d\t%d\n", data[a], data[a]); // ***输出 0 0***

swap_3(data[b], data[c]);
printf("%d\t%d\n", data[b], data[c]); // 输出 2 8

swap_3(data[b], data[b]);
printf("%d\t%d\n", data[b], data[b]); // ***输出 0 0***

std::swap(data[4], data[5]);
printf("%d\t%d\n", data[4], data[5]); // 输出 4 9
std::swap(data[4], data[4]);
printf("%d\t%d\n", data[4], data[4]); // 输出 4 4

return 0;
}

  目前没有找到什么好的解决方案,只能老老实实的用第一种创建一个临时变量的交换方法,或者在交换之前先判断一下是不是同一个元素,若不为同一个元素,才进行交换,否则不交换,即避免自交换

后记

  刚开始出现这个问题的时候,Shaun 还纳闷了,怎么写个快排把数组元素都改变了,刚开始根本没想到这茬,还以为是 Shaun 代码的问题,倒着检查了好几遍,换第一种交换方式以及换个冒泡排序用一样的交换方式进行排序输出结果都没问题,后面使出终极调试法,一个个的输出看看才知道原来是自交换的锅。

  如果有大佬知道为什么会出现这个问题还望不吝赐教 ◔ ‸◔?。经 @ Magnesium12 大佬提醒,这个问题由于引用引起的,采用第二种方法进行交换时如果两个数是完全一样的(地址也一样),则在进行两数相减时,由于是引用,所以这两个数完全一样,则最后会导致这两个完全一样的数,即自交换的该数置为 0 。

参考资料

[1] 谈谈C++中的swap函数