I am analyzing software not written by me and I found this instruction:
memcpy((void *)&vardata.value, (void *)&fvalue, sizeof(vardata.value));
assuming that:
- vardata.value is a long value
- fvalue is a float value
I know (more or less) what memcpy does, but in this case what is exactly memcpy doing? How much would vardata.value be if fvalue were, for example, 12.65? Is it some kind of casting?
If I try to print the variables after memcpy using:
printf("VAL 1 = %ld VAL 2 = %.2f\n", vardata.value, fvalue);
the output is:
VAL 1 = 1095394918 VAL 2 = 12.65
CodePudding user response:
A cast requests a conversion, which reproduces the value of its operand in a requested type.
memcpy
copies bytes, which reproduces the bytes of an object in another object.
Each object in C is represented by bytes, except that bit-fields may use sequences of bits less than full bytes. Every object type has some encoding by which the bits in its bytes represent values. For integer types, the encodings use binary, plus, for signed integer types, some method for representing negative numbers. For pointer types and floating-point types, the C standard does not say how their values are encoded. Common floating-point formats use a bit for the sign, some bits mostly for the exponent, and some bits for most of the significand, which is the fraction portion of a floating-point representation. However, the method used may be completely arbitrary as far as the C standard is concerned; you could use the bits 00000000 to encode 3.25, the bits 000000001 to encode 0.00, the bits 00000010 to encode 1.75, and so on.
When you perform a cast, as in (float) 3
, then a conversion is performed that reproduces the value in the new type, if possible. The operand 3 is an int
with bits 0000…0011, and the result will be 3 in a float
type, but the bits used to represent it will be whatever bits encoded 3 in the float
type. (In conversions where the value cannot be reproduced, such as converting 3.25 to int
, we seek to get close, so 3 will be produced.)
When you memcpy
the contents of an int
with value 3 to a float
, and assuming that int
and float
use the same number of bytes, the result will be a float
with the bits 0000…0011, and the value of that float
will be whatever value is encoded by the bits 0000…0011. It will usually be a value different from 3.
In your example, supposing long
and float
have the same size, the value 1095394918 is 414A666616. When the common IEEE-754 binary32 format is used for float
, then the most significant bit is the sign bit. The most significant bit in 414A666616 is 0, which is the code for positive. The next eight bits, 10000010, encode the exponent and the first bit of the significand. The exponent is encoded as a binary numeral with a bias of 127 added. 10000010 is binary for 130, so removing the bias leaves 3. So the exponent is 3, representing a scaling of 23 in the floating-point representation. Also, the fact this field is neither all zero bits nor all one bits means the first bit of the significand is 1.
The last 23 bits, 4A666616, are the next 23 bits of the significand. They are 10010100110011001100110. They are the bits put after the leading bit (from the exponent field) and a “.”, so the significand is 1.100101001100110011001102. In decimal, this is 1.5812499523162841796875. (A way to see this is that 1.100101001100110011001102 is 1100101001100110011001102 / 223, which is 13,264,486 / 223 = 1.5812499523162841796875.)
Now we have the whole float
value; it is 23•1.5812499523162841796875 = 12.6499996185302734375. Although you supposed fvalue
were 12.65, a binary-based float
cannot have the value 12.65, since that number is not representable in a binary-based format. The closest representable value is 12.6499996185302734375, so that is the actual value fvalue
had when it was copied.
CodePudding user response:
memcpy()
does as its name suggests: it copies memory. Its arguments are a void*
base pointer for the destination memory, a const void*
base pointer for the source memory to be copied, and a number of bytes to copy.
The first problem is you are copying sizeof(long)
bytes from the start of the float
into the receiving long
. That float
is a 32-bit floating-point number; long
, on the other hand. is a signed long integer
value. If your long
size is 64 bits, for example, the memcpy()
will therefore read not just the four bytes of the float
, but another four bytes beyond it, which could cause a memory fault and will definitely copy arbitrary (indeterminate) data even if it doesn't crash.
The second problem is that even if your long
is a 32-bit signed integer, memcpy()
simply copies the bytes from your float
into the bytes in your long
, byte by byte. It is not aware, and does not care, that the two pieces of memory use different formats (float
: IEEE 754; long
: two's complement signed integer). It just slaps the bytes from one place into the other.
What it will not do is a numeric conversion between them.
What the code (presumably) needs, and what its writer (presumably) meant is vardata.value = (long)fvalue;
. In this way, the correct conversion is achieved by the cast.
CodePudding user response:
Is it some kind of casting?
No, it is not similar to casting.
Let's assume some exotic implementation where the value 1.23e 5
would be stored as 0x78 0x56 0x34 0x12
in memory. (C does not define to use a specific IEEE representation)
Then this is what a cast would do:
long l = (long)1.23e 5;
Now l
would contain 123000
as that value matches the initial value.
On the other side, memcopy
just moves bits around:
doubld d = 1.23e 5;
memcpy(&l, &d, sizeof(l));
Now l
holds the value 0x12345678
because it just contains the bytes from the source value. This is assuming little endian representation is used to store the values in memory.
Actually you have another questions in your post:
How much would vardata.value be if fvalue were, for example, 12.65?
This depends on the representation that is used to store floatig point types. The C standard does not mandate any specific spec. Yet, IEEE754 is quite common.