3

I am currently teaching myself c++ and learning all I can about memory. I found out that you can use a char pointer to copy the bit pattern of an int for example and store it in memory with casting:

#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;
}

the code above works, when I cast cp to a int so the compiler reads 4 bytes at a time, I get the correct number which is 20.

However changing it to a 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;
}

returns 0. Now I am a bit confused here. If I am copying every single byte, why is it still giving me a 0? If someone could help me out understanding this it would be very awesome.

MP3D
  • 41
  • 5
  • 1
    Change `(float)*cp` to `*(float*)cp`. I caution against these kinds of practices, as they are circumventing the strong type system of C++ and should only be used in rare circumstances and with due diligence. – Eljay Oct 23 '21 at 15:39
  • @Elijay This seem to have fixed it, I thought I was doinh something wrong logically. Can you explain me why I have to do *(float*)cp and not (float)*cp. I think I get why but it didn't quite click yet. And yes, thank you so much I will definitely don't do it in other projects. I am currently just trying to learn about memory and I found this fascinating that I have that much control over it. – MP3D Oct 23 '21 at 16:20
  • `*cp` happens first, and then `(float)` converts the value of the ASCII character from an integer into a float. The other bytes are not accessed. – Eljay Oct 23 '21 at 17:05
  • @Eljay It might work, except it is undefined behaviour. Do not do this, seriously, if you ever encounter a bug caused by violating the strict aliasing rule, it will be so much pain to even guess what is going on. It's not worth it, extra `std::memcpy` won't kill anyone and the compiler can see through it and avoid the actual copy if possible. – Quimby Oct 23 '21 at 17:31

1 Answers1

4

(int)*cp; first dereferences the pointer, returning a char value, that is now static-casted to integers. This will only work for the range char can store - 0 255 or -128 127 and requires a little-endian system.

It may seem that the way how to fix it would be *reinterpret_cast<float*>(cp); or *((float*)cp). Both are wrong and cause undefined behaviour because they break the strict aliasing rule.

The strict aliasing rule states that one can dereference a pointer to T only if there exists an object of type T at the memory location the pointer points to. With exception of char, std::byte, unsigned char. Meaning it is correct to inspect any type through cast to char, but one cannot simply interpret bunch of bytes as a random T.

The correct way to serialize and deserialize objects is:

#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;
}
Quimby
  • 17,735
  • 4
  • 35
  • 55
  • I see, I have to tell you I haven't gotten to reinterpreting cast tools yet... I was watching some videos on youtube which worked with char* and void*. Thank you for showing me how to approach it safely, though I primarily didnt want to use tools like memcopy or others since I wanted to see how to actually implement it on my own. Doing *(float*)cp actually worked in my case (thanks to the commenter above). Do you have any good resource for beginners who are interested in how memory works with programing languages and some pitfalls? Im just fascinated by bytes and memory. Thank you so much! – MP3D Oct 23 '21 at 16:25
  • @MP3D Okay, manually copying the bytes is fine too, but `std::memcpy` is often recognized by the compiler particularly in these cases of reinterpreting the memory. Code like `std::uint32_t x; float y; std::memcpy(&x,&y,4);` is the only safe way how to interpret bytes of integers as a float (used for some bit magic). And the compilers recognize this and avoid the copy, `memcpy` just serves as a hint against the aliasing issue. – Quimby Oct 23 '21 at 17:20
  • @MP3D Yes, it is very likely the code works until suddenly it won't due to some optimizations kicking in. Unfortunately I do not have any resources, maybe look at [curated list of C++ books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). I would definitely avoid any pre-C++11 sources, or at least not start with them. – Quimby Oct 23 '21 at 17:24