当我发现一个非常有趣的函数时,我正在看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
答案 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。