【C++】unordered_map在Windows和Linux上的不同行为

本文探讨了C++中unordered_map在Windows/Linux环境下结果不一致的问题,涉及哈希表遍历顺序和平台特定的哈希函数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我目前手头上的项目,需要编译在板端Linux上运行,但是日常daily调试多在Windows上开发。这就涉及到同一份代码在多平台上的编译个运行。有一次遇到了一个奇怪的现象:跑同样的一份代码,Windows和Linux出来的结果是不一致的。最终确定到不一致的原因出现在unordered_map上,就把这次记录总结下来。

这次不一致发生在:处理一个状态序列的投票操作。从编程的角度而言,最适合投票操作的容器就是键值对,key放置投票的内容,value放置投票的次数。在C++中,STL提供了两个相关容器:map和unordered_map。在代码上选择了unordered_map来实现。


问题复现

我把这个问题简化:假设车的类型有四种情况,分别为UNKNOWN、SMALL、MIDDLE、BIG。现有15次的估计,以出现次数最多的类型作为其最终类型。代码非常简单:

#include <iostream>
#include <unordered_map>
#include <string>

int main()
{
  std::unordered_map<std::string, int> vehs;
  vehs["UNKNOWN"] = 5;
  vehs["SMALL"] = 3;
  vehs["MIDDLE"] = 5;
  vehs["BIG"] = 2;

  std::string max_type;
  int max_times = -1;

  auto iter = vehs.begin();
  for (; iter != vehs.end(); ++iter) {
    if (iter->second > max_times) {
      max_times = iter->second;
      max_type = iter->first;
    }
    std::cout << iter->first << " ";
  }
  std::cout << std::endl;

  std::cout << "max_type : " << max_type << std::endl;

  return 0;
}

在Windows平台下,编译并运行代码:

BIG UNKNOWN SMALL MIDDLE
max_type : UNKNOWN

在Linux平台下,编译并运行代码:

BIG MIDDLE SMALL UNKNOWN
max_type : MIDDLE

可以看到,在Windows和Linux两个平台下的运行结果中,出现次数最多的车的类型,一个为UNKNOWN,一个为MIDDLE。主要原因是这两种类型都是5次(最多)。这时,unordered_map的遍历顺序就很重要了,这将直接关系到最终的输出结果。通过打印结果可以看到,两个平台下的遍历顺序是不一致的。

在C++中,STL提供了两个键值对容器:map和unordered_map。上面的代码,采用的是unordered_map容器,如果将其改成map容器呢?还会发生这种不一致的结果么?

std::map<std::string, int> vehs;    // 将unordered_map修改为map

此时再在Windows和Linux平台下,分别编译并运行代码,最终的输出都是一致的:

BIG MIDDLE SMALL UNKNOWN
max_type : MIDDLE

也就是说,这种平台不一致性只发生在unordered_map容器上,而不发生在map容器上


原因分析

map和unordered_map分别在Windows和Linux上的不同运行结果,主要体现在遍历顺序上。对于map而言,两个平台上的遍历顺序是一致的;而对于unordered_map而言,两个平台上的遍历顺序是不一致的。

遍历顺序的不同,究其根本,主要体现在其内部构造的不同

map的内部构造:

  • map的内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的。红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。

unordered_map的内部构造:

  • unordered_map的内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。

两者在运行效率和占用内存的对比:

  • 运行效率方面:unordered_map最高,而map效率较低,但提供了稳定效率和有序的序列
  • 占用内存方面:map内存占用略低,unordered_map内存占用略高,而且是线性成比例的

需要无序容器,快速查找删除,不担心略高的内存时可以选择unordered_map;需要有序容器稳定查找删除效率,内存很在意时候用map。

因此,如果需要使用map,那么key需要定义operator<,用于进行有序的排序;如果需要使用unordered_map,那么key需要定义hash_value函数并且定义operator==,用于hash值的计算。而:

  • 当使用map时,Windows和Linux对于operator<的定义都是一致的
  • 当使用unordered_map时,Windows和Linux对于operator==的定义都是一致的,对于hash_value函数的定义是不一致的

从而,在使用unordered_map时,Windows和Linux使用两种不同的哈希函数,产生不同的哈希码,从而依次产生不同的存储区放置,导致在迭代时产生不同的顺序


相关阅读

在C语言中,unordered_map是一个无序容器,它可以存储键值对。你可以使用unordered_map来快速查找指定键对应的值,它的查找效率比关联式容器高。你可以使用at()函数来查找指定键对应的值。例如,在下面的代码中,我们创建了一个unordered_map容器,并初始化了一些键值对。然后使用at()函数查找指定键"C语言教程"对应的值,最后输出结果为"http://c.biancheng.net/c/"。 ```c++ #include <iostream> #include <string> #include <unordered_map> using namespace std; int main() { unordered_map<string, string> my_uMap{ {"C语言教程","http://c.biancheng.net/c/"}, {"Python教程","http://c.biancheng.net/python/"}, {"Java教程","http://c.biancheng.net/java/"} }; string str = my_uMap.at("C语言教程"); cout << "str = " << str << endl; return 0; } ``` 此外,在C++11标准中,unordered_map模板类还增加了移动构造函数的功能。移动构造函数可以以右值引用的方式将临时unordered_map容器中存储的所有键值对复制给新建容器。例如,在下面的代码中,我们创建了一个返回临时unordered_map容器的函数retUmap(),然后使用移动构造函数创建了新的umap2容器。 ```c++ #include <iostream> #include <string> #include <unordered_map> using namespace std; unordered_map<string, string> retUmap(){ unordered_map<string, string> tempUmap{ {"Python教程","http://c.biancheng.net/python/"}, {"Java教程","http://c.biancheng.net/java/"}, {"Linux教程","http://c.biancheng.net/linux/"} }; return tempUmap; } int main() { unordered_map<string, string> umap2(retUmap()); return 0; } ``` 另外,你还可以使用insert()函数将一个unordered_map容器中的键值对复制给另一个unordered_map容器。下面的代码演示了如何创建一个空的unordered_map容器otherumap,并将指定unordered_map容器umap中的键值对复制给otherumap容器,然后遍历otherumap容器中的键值对并输出。 ```c++ #include <iostream> #include <string> #include <unordered_map> using namespace std; int main() { unordered_map<string, string> umap{ {"STL教程","http://c.biancheng.net/stl/"}, {"Python教程","http://c.biancheng.net/python/"}, {"Java教程","http://c.biancheng.net/java/"} }; unordered_map<string, string> otherumap; unordered_map<string, string>::iterator first = umap.begin(); unordered_map<string, string>::iterator last = umap.end(); otherumap.insert(first, last); for (auto iter = otherumap.begin(); iter != otherumap.end(); iter++) { cout << iter->first << " " << iter->second << endl; } return 0; } ``` 这样,你就可以在C语言中使用unordered_map来存储操作键值对了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值