哈希函数中对c_str()与const char *的函数调用

时间:2018-11-22 02:18:11

标签: c++ string casting size-t c-str

当我发现一个非常有趣的函数时,我正在看stackoverflow上的哈希函数。它涉及将const char *转换为size_t *,然后取消引用size_t。然后将其移至一定精度。这适用于const char *,每次产生相同的值。但是,当我使用实际的字符串类型并改为调用c_str()时,产生的两个值不匹配。此外,在每次运行代码时,字符串每次运行都会产生不同的值。任何人都知道为什么会这样吗?

const string l = "BA";
const char* k = l.c_str();
const char* p = "BA";
cout << k << " " << *((size_t*)k) << endl;
cout << p << " " << *((size_t*)p) << endl;

运行1:

BA 140736766951746
BA 7162260525311607106

运行2:

BA 140736985055554
BA 7162260525311607106

原始问题:Have a good hash function for a C++ hash table?

3 个答案:

答案 0 :(得分:3)

*((size_t*)k)通过违反严格的别名规则导致未定义的行为。仅当k实际指向类型为size_t的对象时,此代码仅有效

由于行为不确定,看到奇怪的数字是可能的结果(就像其他任何事情一样)。


我想你打算做些类似的事情:

size_t x;
memcpy(&x, k, sizeof x);
cout << k << " " << x << '\n';

现在应该清楚问题出在哪里。您的字符串仅包含3个字符(2个加上空终止符),但是您尝试读取3个以上的字符,这也会导致未定义的行为。

答案 1 :(得分:0)

// Simple null terminated character that is represented in memory as:
//
// ['B', 'A', '\0']
const char* p = "BA";

// From the other side `std::string` isn't so simple
//
// c_str() returns a pointer to some kind of buffer.
//
// ['B', 'A', '\0', ... reserved_memory]
//
const std::string l = "BA";
const char* k = l.c_str();

// Then you do a C-style cast.
//
// (size_t*)k that gives you the address to the beginning of the underlying
// data of the std::string (possibly it will be pointer on the heap or on
// stack depending on the SSO) and after that you dereference it to receive
// the value. BTW it can lead to the undefined behavior because you
// attempt to receive the value for 8 bytes (depending on the size_t size)
// but your actual string may be less than it, e.g. 4 bytes. As a result
// you will receive the garbage.
std::cout << k << " " << *((size_t*)k) << std::endl;

// Two strings created as
//
// const char* foo = "foo";
// const char* bar = "foo";
//
// are stored in the Read only segment of data in your executable. Actually
// two different pointers will point to the same string in this segment. Also
// note the same undefined behavior mentioned earlier.
std::cout << p << " " << *((size_t*)p) << std::endl;

答案 2 :(得分:0)

我首先要说:

const string l = "BA";
const char* k = l.c_str();
const char* p = "BA";
cout << k << " " << *((size_t*)k) << endl;
cout << p << " " << *((size_t*)p) << endl;

*((size_t*)k)*((size_t*)p)都调用未定义的行为。之所以如此,是因为在大多数系统上,它将访问超出char数组边界的数据。请注意,sizeof(size_t) > 3 * sizeof(char)用于32位和64位系统,因此*((size_t*)k)会访问边界之外的至少一个字节。

在整个示例中,字符串文字(在您的系统上)可能至少与sizeof(size_t)对齐,且填充为零(不要指望它,但看起来如此)。这意味着字符串文字"BA"(和NUL终止符)之后的垃圾字符是NUL字符。这在每次运行中都是一致的。

对于k来自std::string的情况,您不是很幸运。字符串很短,因此大多数系统将采用短字符串优化。这意味着char缓冲区在std::string对象中。在您的情况下,字符串是如此之短,以至于其其余部分仍在专用于短字符串优化的缓冲区中。看起来,缓冲区的其余部分未初始化,并且包含垃圾。垃圾邮件是在调用该函数之前留下的。结果,除了BA\0的前3个字节以外,其余都是随机垃圾。

您很幸运,这种未定义行为的情况最终会带来一些额外的垃圾,而不会造成更多的混乱(例如始终返回零或调用不相关的函数)。永远不要依赖UB。