复制字节模式以实现浮点数不起作用。

3

我目前正在自学C++,并尽可能了解有关内存的所有知识。我发现你可以使用char指针将int的位模式复制并通过强制类型转换存储在内存中:

#include <iostream>

using namespace std;

int main()
{

int x = 20;

char* cp = new char[sizeof(int)];

cp[0] = *((char*)&x);
cp[1] = *((char*)&x+1);
cp[2] = *((char*)&x+2);
cp[3] = *((char*)&x+3);

std::cout << (int)*cp; // returns 20; 


return 0;
}

上述代码有效,当我将cp转换为int时,编译器每次读取4个字节,我得到的正确数字是20。

但是如果将其更改为float:

#include <iostream>

using namespace std;

int main()
{

float x = 20;

char* cp = new char[sizeof(float)];

cp[0] = *((char*)&x);
cp[1] = *((char*)&x+1);
cp[2] = *((char*)&x+2);
cp[3] = *((char*)&x+3);

std::cout << (float)*cp; // returns 0.


return 0;
}

返回0。现在我有点困惑了。如果我复制每个字节,为什么它仍然给我一个0?如果有人能帮我理解这一点,那就太棒了。


1
(float)*cp 改为 *(float*)cp。我警告不要使用这种做法,因为它们绕过了 C++ 的强类型系统,只有在极少数情况下并且经过谨慎考虑才应该使用。 - Eljay
@Elijay,这似乎解决了问题,我以为我在逻辑上做错了什么。你能解释一下为什么我必须要做*(float*)cp而不是(float)*cp吗?我想我明白了,但还没有完全理解。非常感谢你,我肯定不会在其他项目中这样做。我目前只是试图学习关于内存的知识,我发现这很有趣,我可以对它有如此多的控制。 - MP3D
*cp首先发生,然后(float)将ASCII字符的值从整数转换为浮点数。其他字节不被访问。 - Eljay
@Eljay,这样做可能有效,但它是未定义的行为。认真地说,不要这样做,如果您遇到由于违反严格别名规则而导致的错误,甚至猜测正在发生什么都会非常痛苦。这不值得,额外的std::memcpy不会伤害任何人,编译器可以看穿它并尽可能避免实际复制。 - Quimby
1个回答

4

(int)*cp;首先对指针进行解引用,返回一个char值,然后将该值强制转换为整数。这只适用于char可以存储的范围— 0 255-128 127,并且需要一个小端系统。

修复方法可能看起来是*reinterpret_cast<float*>(cp);*((float*)cp)。但两者都是错误的,会导致未定义行为,因为它们违反了严格别名规则。

严格别名规则规定,只有在指针指向的内存位置上存在类型为T的对象时,才能对指向T的指针进行解引用。除了charstd::byteunsigned char之外。这意味着可以通过将任何类型转换为char来检查任何类型,但不能简单地将一堆字节解释为随机的T

序列化和反序列化对象的正确方法是:

#include <iostream>

using namespace std;

int main() {
    float x = 20.0f;

    // This is safe.
    char* cp1 = reinterpret_cast<char*>(&x);
    // Also safe because there is a float object at cp1.
    std::cout << *reinterpret_cast<float*>(cp1);

    // No need for dynamic allocation.
    char cp2[sizeof(float)];
    // Copy the individual bytes into a buffer.
    //  = Serialize the object.
    std::memcpy(cp2, &x, sizeof(x));

    // NOT SAFE, UNDEFINED BEHAVIOUR
    // There is no float object at cp2.
    std::cout << *reinterpret_cast<float*>(cp2);

    // Deserialization through copy
    float y;
    std::memcpy(&y, cp2, sizeof(y));
    // Safe
    std::cout << y;

    return 0;
}

我明白了,我得告诉你我还没有重新解释转换工具...我在YouTube上看了一些使用char和void的视频。谢谢你向我展示如何安全地处理它,尽管我主要不想使用像memcopy或其他工具,因为我想看看如何自己实现它。在我的情况下,*(float*)cp确实起作用了(感谢上面的评论者)。你有什么好的资源可以推荐给初学者,他们对编程语言中的内存工作原理和一些陷阱感兴趣吗?我对字节和内存很着迷。非常感谢! - MP3D
@MP3D,手动复制字节也可以,但在这些重新解释内存的情况下,编译器通常会识别std::memcpy。像std::uint32_t x; float y; std::memcpy(&x,&y,4);这样的代码是将整数的字节解释为浮点数的唯一安全方式(用于某些位操作)。编译器会识别这一点并避免复制,memcpy只是针对别名问题的提示。 - Quimby
@MP3D 是的,代码很可能在某些优化启动后突然失效。不幸的是,我没有任何资源,也许可以看看经过筛选的C++书籍列表。我肯定会避免使用任何C++11之前的源码,或者至少不要从它们开始。 - Quimby

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接