Reputation: 21
I had in mind the numeric constant in C++ (and also in C) have a datatype assigned. When these constants are an operand the datatype can influence the result and the result's datatype.
I know the datatype depends on the constant itself like 5
or 5.0
5.0f
and it depends on the architecture/compiler.
But I don't know the rules exactly and searched a lot and it seems I didn't get the right keywords.
I did something like that:
inline unsigned long __DWORD(byte b0, byte b1, byte b2, byte b3) { return (unsigned long)((b0 << 24) + (b1 << 16) + (b2 << 8) + (b3 << 0)); }
and while it worked perfectly on a SAMD21 (32bit) it didn't work on atmega (8bit).
inline unsigned long __DWORD(byte b0, byte b1, byte b2, byte b3) { return (unsigned long)(((unsigned long)b0 << 24) + ((unsigned long)b1 << 16) + ((unsigned long)b2 << 8) + ((unsigned long)b3 << 0)); }
that worked. And I'm sure casting the right operand would also have worked.
how can I know whats the default datatyp of a numeric constant is? Is the behaviour defined?
Upvotes: 0
Views: 84
Reputation: 11220
It sounds as though you're conflating the concept of "data type" with the size of the underlying type -- which are two different things.
The type of an expression is fixed and well-defined in C++.
5
is an int
literal, 5.0
is a double
literal, 5.0f
is a float
literal.unsigned int x = 5
, which assigns an int
literal to an unsigned int
type variable.The size of a type, on the other hand, is implementation-defined. It depends entirely on the compiler (most-often, on the target architecture).
The C++ language gives us few guarantees on the size of these types[cppreference], which makes it unreliable for portability to use these types while expecting a specific size.
In general, you're better off using the <cstdint>
header types for fixed-with integrals.
For example, your __DWORD
example could be written:
inline std::uint32_t __DWORD(std::uint8_t b0, std::uint8_t b1, std::uint8_t b2, std::uint8_t b3)
{
return static_cast<std::uint32_t>(
(static_cast<std::uint32_t>(b0) << 24) +
(static_cast<std::uint32_t>(b1) << 16) +
(static_cast<std::uint32_t>(b2) << 8) +
(static_cast<std::uint32_t>(b3) << 0)
);
}
The reason the cast to uint32_t
is needed for b0
,b1
,b2
, and b3
is because the resultant type of a shift operations is always the type on the left-hand side of the operation. So if sizeof(b0)
is 1
, then b0 << 24
will shift all data out of b0
because it exceeds 8 bits. Casting to uint32_t
ensures that the result of the shift will fit into the resultant type.
So to be clear: The type of the expression does not depend on the compiler or architecture; just the size of the underlying type.
On an unrelated note, any identifier containing two double underscores (__
), or starting with an underscore followed by an uppercase letter (_D
) are reserved for the implementation, and should not be used. Your __DWORD
function should probable be renamed something like make_uint32(...)
instead
Upvotes: 2