Related
I'm supposed to create a variable
long long hex = 0x1a1b2a2b3a3b4a4bULL;
and then define 4 pointers that point to 1a1b, 2a2b, 3a3b and 4a4b. I'm then printing the addresses and values of those double bytes.
My approach was to create a pointer
long long *ptr1 = &hex;
and then use pointer arithmetic to get to the next value. What I realized was that incrementing this pointer would increment it by long long bytes and not by 2 bytes like I need it to. Creating a short pointer
short *ptr1 = &hex;
Is what I would need but my compiler won't let me since the data types are incompatible. How do I get around that? Is there a way to create a pointer that increments by 2 bytes and assign that to a variable of a larger data type?
You can access any variable only through compatible types.
However, a char pointer can be used to access any type of variable.
Please do not cast it to a short* Please see NOTE below , they are not compatible types. You can only use a char* for conforming code.
Quoting C11, chapter §6.3.2.3
[...] When a pointer to an object is converted to a pointer to a character type,
the result points to the lowest addressed byte of the object. Successive increments of the
result, up to the size of the object, yield pointers to the remaining bytes of the object.
So, the way out is, use a char * and use pointer arithmetic to get to the required address.
NOTE: Since all other answers suggest a blatantly wrong method (casting the pointer to short *, which explicitly violates strict aliasing), let me expand a bit on my answer and supporting quotes.
Quoting C11, chapter §6.5/P7
An object shall have its stored value accessed only by an lvalue expression that has one of
the following types: 88)
— a type compatible with the effective type of the object,
— a qualified version of a type compatible with the effective type of the object,
— a type that is the signed or unsigned type corresponding to the effective type of the
object,
— a type that is the signed or unsigned type corresponding to a qualified version of the
effective type of the object,
— an aggregate or union type that includes one of the aforementioned types among its
members (including, recursively, a member of a subaggregate or contained union), or
— a character type.
In this case, a short and a long long are not compatiable types. so the only way out is to use pointer tochar` type.
Cut-'n-Paste from Question body
This was added as update by OP
Edit:
Here's the correct solution that doesn't cause undefined behavior.
Edit 2:
Added the memory address.
#include <stdio.h>
int main() {
long long hex = 0x1a1b2a2b3a3b4a4bULL;
char *ptr = (char*)&hex;
int i; int j;
for (i = 1, j = 0; i < 8, j < 7; i += 2, j += 2) {
printf("0x%hx%hx at address %p \n", ptr[i], ptr[j], (void *) ptr+i);
}
return 0;
}
As expected, it has been pointed out that this is undefined behavior. It's probably one of these stupid "C course" assignments where C isn't completely understood.
Just in case you want to avoid the UB, you could solve it using a union:
#include <stdio.h>
union longparts
{
unsigned long long whole;
unsigned short parts[4];
};
int main(void)
{
union longparts test;
test.whole = 0x1a1b2a2b3a3b4a4bULL;
for (int i = 0; i < 4; ++i)
{
unsigned short *part = &test.parts[i];
printf("short at addr %p: 0x%hx\n", (void *)part, *part);
}
return 0;
}
from C11 §6.5.2.3, footnote 95:
If the member used to read the contents of a union object is not the same as the member last used to store a value in the object, the appropriate part of the object representation of the value is reinterpreted as an object representation in the new type as described in 6.2.6 (a process sometimes called ‘‘type punning’’). This might be a trap representation.
So, you could still run into problems in some cases with trap representations, but at least it's not undefined. The result is implementation defined, e.g. because of endianness of the host machine.
add a cast:
short *ptr1 = (short*)&hex;
However, make sure you pay attention to the endianness of your platform.
On x86, for instance, data is stored little end first, so
ptr1[0] should point to 0x4a4b
Also pay attention to your platforms actual sizes: long long is at least 64bit, and short is at least 16 bit. If you want to make sure the types are really those sizes, use uint64_t and uint16_t. You'll get a compiler error if there aren't any types matching those exact sizes available on your system.
Furthermore, take note of alignment. You can use uint64_t as uint16_t[4], however not the other way around, as the address of a uint16_t is usually dividable by two, and the address of uint64_t dividable by 8.
Should I worry about the alignment during pointer casting?
You need to cast the pointer to assign it to a different type:
short *ptr1 = (short*)&hex;
However, doing this results in implementation-defined behavior, since you're depending on the endianness of the system.
Is there anything such as an unsigned int* which is different from int*. I know that unsigned has a higher range of values. Still, can't int* even point to any unsigned int?
int * and unsigned int * are two different pointer types that are not compatible types. They are also pointers to incompatible types. For the definition of compatible types, please refer to § 6.2.7 in the C Standard (C11).
Being pointers to incompatible types means that for example that this:
unsigned int a = 42;
int *p = &a; // &a is of type unsigned int *
is not valid (the constraints of the assignment operator are violated).
Another difference between the two types is as for most other pointer types (although unlikely here) there is no guarantee from C they have the same size or the same representation.
Using an unsigned pointer to point to a signed version of the same type is defined by C Standard.
Therefore interpreting an int through an unsigned int pointer and vice-versa is valid.
ISO/IEC 9899:201x 6.5 Expressions, p7:
An object shall have its stored value accessed only by an lvalue expression that has one of
the following types: 88)
— a type that is the signed or unsigned type corresponding to the effective type of the
object,
— a type that is the signed or unsigned type corresponding to a qualified version of the
effective type of the object,
88) The intent of this list is to specify those circumstances in which an object may or may not be aliased.
Effective type is basically the type of the object:
The effective type of an object for an access to its stored value is the declared type of the
object, if any.
An issue has been raised about the interpretation of the above rule. The following is my additional rationale about it.
This text below is listed purely for semantic reasoning of the word: corresponding ,and not for the direct rules it specifies.
6.2.5 Types
p6: For each of the signed integer types, there is a corresponding (but different) unsigned
integer type (designated with the keyword unsigned) that uses the same amount of
storage (including sign information) and has the same alignment requirements.
p9: The range of nonnegative values of a signed integer type is a subrange of the
corresponding unsigned integer type, and the representation of the same value in each
type is the same.41)
p12: For each floating type there is a corresponding real type, which is always a real floating
type. For real floating types, it is the same type. For complex types, it is the type given
by deleting the keyword _Complex from the type name.
p27: Further, there is the _Atomic qualifier. The presence of the _Atomic qualifier
designates an atomic type. The size, representation, and alignment of an atomic type
need not be the same as those of the corresponding unqualified type
6.2.6.2 Integer types
p2: For signed integer types, the bits of the object representation shall be divided into three
groups: value bits, padding bits, and the sign bit. There need not be any padding bits;
signed char shall not have any padding bits. There shall be exactly one sign bit.
Each bit that is a value bit shall have the same value as the same bit in the object
representation of the corresponding unsigned type
p5: The values of any padding bits are unspecified.54)A valid (non-trap) object representation
of a signed integer type where the sign bit is zero is a valid object representation of the
corresponding unsigned type, and shall represent the same value.
(And many more examples with identical usage of the word corresponding )
As you can see in the above snippets, Standard uses the word corresponding to refer to different types or types with different specifiers and/or qualifiers. Therefore, as seen in the above examples Standard uses the word as would be used in this example: qualified type is corresponding to type.
It would be illogical to suddenly use the word corresponding for a different purpose: referring to completely identically qualified/specified types and even confuse the matters more by including the words signed and unsigned in the same sentence for no good reason.
The intention of the 6.5, p7 is: a type that is the signed or unsigned type either a signed or unsigned type corresponding to the effective type of the object that othervise matches( corresponds ) to the target type. So for example: effective type is: int, int or unsigned int correspond to that type.
unsigned int * and int * are different types. To convert one to the other you must use a cast.
If you read a value through a pointer then it attempts to interpret the bits stored at that memory location as if they were bits for the type being pointed to by the pointer you are reading through.
If the bits at that memory location were not written by a pointer of the same type you are reading through, then this is called aliasing.
The strict aliasing rule specifies which types may or may not be aliased; alasing between a type's signed and unsigned versions is always permitted.
However, if the bits are not a valid representation of a value in the type you are reading , then it causes undefined behaviour.
On modern systems there are no such "trap" representations so you have no issue. But let's say you were on a 1's complement system that trapped on negative zero:
unsigned int x = 0xFFFFFFFF;
int *y = (int *)&x;
printf("%d\n", y);
The attempt to read y could cause a hardware fault or any other behaviour.
The value of the pointer is the same, but they are different types. A difference will arise depending on the way you interpret the pointer - for eg: dereferencing.
unsigned int *u;
int *d;
unsigned int v = 2147483648; /* 2^31 */
u = &v;
d = (int*) &v;
printf("%u\n", *u);
printf("%d\n", *d);
will output:
2147483648
-2147483648
The difference in the output arises because in printf("%d\n", *d), d is dereferenced and printed as if it points to a signed int, except it isn't. So you have to keep a distinction between the 2 types of pointers in your code.
It can point as both has the same size. The problem is this will introduce a hard to find bug, because you'll interpret a signed value as an unsigned or vice-versa.
A pointer is a number that is a memory address. So pointers have to have enough precision to be able to address all of memory for the implementation.
Whether you reference a signed or unsigned int makes no difference in the internal structure of the pointer, because, in theory anyway, the int or unsigned int could be almost anywhere in memory. The datatype (unsigned) has to be declared to "help" the compiler decide correctness of the code.
In the GLib documentation, there is a chapter on type conversion macros.
In the discussion on converting an int to a void* pointer it says (emphasis mine):
Naively, you might try this, but it's incorrect:
gpointer p;
int i;
p = (void*) 42;
i = (int) p;
Again, that example was not correct, don't copy it. The problem is
that on some systems you need to do this:
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
(source: GLib Reference Manual for GLib 2.39.92, chapter Type Conversion Macros ).
Why is that cast to long necessary?
Should any required widening of the int not happen automatically as part of the cast to a pointer?
The glib documentation is wrong, both for their (freely chosen) example, and in general.
gpointer p;
int i;
p = (void*) 42;
i = (int) p;
and
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
will both lead to identical values of i and p on all conforming c implementations.
The example is poorly chosen, because 42 is guaranteed to be representable by int and long (C11 draft standard n157: 5.2.4.2.1 Sizes of integer types ).
A more illustrative (and testable) example would be
int f(int x)
{
void *p = (void*) x;
int r = (int)p;
return r;
}
This will round-trip the int-value iff void* can represent every value that int can, which practically means sizeof(int) <= sizeof(void*) (theoretically: padding bits, yadda, yadda, doesn't actually matter). For other integer types, same problem, same actual rule (sizeof(integer_type) <= sizeof(void*)).
Conversely, the real problem, properly illustrated:
void *p(void *x)
{
char c = (char)x;
void *r = (void*)c;
return r;
}
Wow, that can't possibly work, right? (actually, it might).
In order to round-trip a pointer (which software has done unnecessarily for a long time), you also have to ensure that the integer type you round-trip through can unambiguously represent every possible value of the pointer type.
Historically, much software was written by monkeys that assumed that pointers could round-trip through int, possibly because of K&R c's implicit int-"feature" and lots of people forgetting to #include <stdlib.h> and then casting the result of malloc() to a pointer type, thus accidentally roundtripping through int. On the machines the code was developed for sizeof(int) == sizeof(void*), so this worked. When the switch to 64-bit machines, with 64-bit addresses (pointers) happened, a lot of software expected two mutually exclusive things:
1) int is a 32-bit 2's complement integer (typically also expecting signed overflow to wrap around)
2) sizeof(int) == sizeof(void*)
Some systems (cough Windows cough) also assumed sizeof(long) == sizeof(int), most others had 64-bit long.
Consequently, on most systems, changing the round-tripping intermediate integer type to long fixed the (unnecessarily broken) code:
void *p(void *x)
{
long l = (long)x;
void *r = (void*)l;
return r;
}
except of course, on Windows. On the plus side, for most non-Windows (and non 16-bit) systems sizeof(long) == sizeof(void*) is true, so the round-trip works both ways.
So:
the example is wrong
the type chosen to guarantee round-trip doesn't guarantee round-trip
Of course, the c standard has a (naturally standard-conforming) solution in intptr_t/uintptr_t (C11 draft standard n1570: 7.20.1.4 Integer types capable of holding object pointers), which are specified to guarantee the
pointer -> integer type -> pointer
round-trip (though not the reverse).
As according to the C99: 6.3.2.3 quote:
5 An integer may be converted to any pointer type. Except as
previously specified, the result is implementation-defined, might not
be correctly aligned, might not point to an entity of the referenced
type, and might be a trap representation.56)
6 Any pointer type may be converted to an integer type. Except as
previously specified, the result is implementation-defined. If the
result cannot be represented in the integer type, the behavior is
undefined. The result need not be in the range of values of any
integer type.
According to the documentation at the link you mentioned:
Pointers are always at least 32 bits in size (on all platforms GLib
intends to support). Thus you can store at least 32-bit integer values
in a pointer value.
And further more long is guaranteed to be atleast 32-bits.
So,the code
gpointer p;
int i;
p = (void*) (long) 42;
i = (int) (long) p;
is safer,more portable and well defined for upto 32-bit integers only, as advertised by GLib.
As I understand it, the code (void*)(long)42 is "better" than (void*)42 because it gets rid of this warning for gcc:
cast to pointer from integer of different size [-Wint-to-pointer-cast]
on environments where void* and long have the same size, but different from int. According to C99, §6.4.4.1 ¶5:
The type of an integer constant is the first of the corresponding list in which its value can be represented.
Thus, 42 is interpreted as int, had this constant be assigned directly to a void* (when sizeof(void*)!=sizeof(int)), the above warning would pop up, but everyone wants clean compilations. This is the problem (issue?) the Glib doc is pointing to: it happens on some systems.
So, two issues:
Assign integer to pointer of same size
Assign integer to pointer of different size
Curiously enough for me is that, even though both cases have the same status on the C standard and in the gcc implementation notes (see gcc implementation notes), gcc only shows the warning for 2.
On the other hand, it is clear that casting to long is not always the solution (still, on modern ABIs sizeof(void*)==sizeof(long) most of the times), there are many possible combinations depending on the size of int,long,long long and void*, for 64bits architectures and in general. That is why glib developers try to find the matching integer type for pointers and assign glib_gpi_cast and glib_gpui_cast accordingly for the mason build system. Later, these mason variables are used in here to generate those conversion macros the right way (see also this for basic glib types). Eventually, those macros first cast an integer to another integer type of the same size as void* (such conversion conforms to the standard, no warnings) for the target architecture.
This solution to get rid of that warning is arguably a bad design that is nowadys solved by intptr_t and uintptr_t, but it is posible it is there for historical reasons: intptr_t and uintptr_t are available since C99 and Glib started its development earlier in 1998, so they found their own solution to the same problem. It seems that there were some tries to change it:
GLib depends on various parts of a valid C99 toolchain, so it's time to
use C99 integer types wherever possible, instead of doing configure-time
discovery like it's 1997.
no success however, it seems it never got in the main branch.
In short, as I see it, the original question has changed from why this code is better to why this warning is bad (and is it a good idea to silence it?). The later has been answered somewhere else, but this could also help:
Converting from pointer to integer or vice versa results in code that is not portable and may create unexpected pointers to invalid memory locations.
But, as I said above, this rule doesn't seem to qualify for a warning for issue number 1 above. Maybe someone else could shed some light on this topic.
My guess for the rationale behind this behaviour is that gcc decided to throw a warning whenever the original value is changed in some way, even if subtle. As gcc doc says (emphasis mine):
A cast from integer to pointer discards most-significant bits if the pointer representation is smaller than the integer type, extends according to the signedness of the integer type if the pointer representation is larger than the integer type, otherwise the bits are unchanged.
So, if sizes match there is no change on the bits (no extension, no truncation, no filling with zeros) and no warning is thrown.
Also, [u]intptr_t is just a typedef of the appropriate qualified integer: it is not justifiable to throw a warning when assigning [u]intptr_t to void* since it is indeed its purpose. If the rule applies to [u]intptr_t, it has to apply to typedefed integer types.
I think it is because this conversion is implementation-dependendent. It is better to use uintptr_t for this purpose, because it is of the size of pointer type in particular implementation.
As explained in Askmish's answer, the conversion from an integer type to a pointer is implementation defined (see e.g. N1570 6.3.2.3 Pointers §5 §6 and the footnote 67).
The conversion from a pointer to an integer is implementation defined too and if the result cannot be represented in the integer type, the behavior is undefined.
On most general purpose architectures, nowadays, sizeof(int) is less than sizeof(void *), so that even those lines
int n = 42;
void *p = (void *)n;
When compiled with clang or gcc would generate a warning (see e.g. here)
warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
Since C99, the header <stdint.h> introduces some optional fixed-sized types. A couple, in particular, should be used here n1570 7.20.1.4 Integer types capable of holding object pointers:
The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
intptr_t
The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:
uintptr_t
These types are optional.
So, while a long may be better than int, to avoid undefined behaviour the most portable (but still implementation defined) way is to use one of those types(1).
Gcc's documentation specifies how the conversion takes place.
4.7 Arrays and Pointers
The result of converting a pointer to an integer or vice versa (C90 6.3.4, C99 and C11 6.3.2.3).
A cast from pointer to integer discards most-significant bits if the pointer representation is larger than the integer type, sign-extends(2) if the pointer representation is smaller than the integer type, otherwise the bits are unchanged.
A cast from integer to pointer discards most-significant bits if the pointer representation is smaller than the integer type, extends according to the signedness of the integer type if the pointer representation is larger than the integer type, otherwise the bits are unchanged.
When casting from pointer to integer and back again, the resulting pointer must reference the same object as the original pointer, otherwise the behavior is undefined. That is, one may not use integer arithmetic to avoid the undefined behavior of pointer arithmetic as proscribed in C99 and C11 6.5.6/8.
[...]
(2) Future versions of GCC may zero-extend, or use a target-defined ptr_extend pattern. Do not rely on sign extension.
Others, well...
The conversions between different integer types (int and intptr_t in this case) are mentioned in n1570 6.3.1.3 Signed and unsigned integers
When a value with integer type is converted to another integer type other than _Bool, if the value can be represented by the new type, it is unchanged.
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
Otherwise, the new type is signed and the value cannot be represented in it; either the result is implementation-defined or an implementation-defined signal is raised.
So, if we start from an int value and the implementation provides an intptr_t type and sizeof(int) <= sizeof(intptr_t) or INTPTR_MIN <= n && n <= INTPTR_MAX, we can safely convert it to an intptr_t and then convert it back.
That intptr_t can be converted to a void * and then converted back to the same (1)(2) intptr_t value.
The same doesn't hold in general for a direct conversion between an int and a void *, even if in the example provided, the value (42) is small enough not to cause undefined behaviour.
I personally find quite debatable the reasons given for those type conversion macros in the linked GLib documentation (emphasis mine)
Many times GLib, GTK+, and other libraries allow you to pass "user data" to a callback, in the form of a void pointer. From time to time you want to pass an integer instead of a pointer. You could allocate an integer [...] But this is inconvenient, and it's annoying to have to free the memory at some later time.
Pointers are always at least 32 bits in size (on all platforms GLib intends to support). Thus you can store at least 32-bit integer values in a pointer value.
I'll let the reader decide whether their approach makes more sense than a simple
#include <stdio.h>
void f(void *ptr)
{
int n = *(int *)ptr;
// ^ Yes, here you may "pay" the indirection
printf("%d\n", n);
}
int main(void)
{
int n = 42;
f((void *)&n);
}
(1) I'd like to quote a passage in this Steve Jessop's answer about those types
Take this to mean what it says. It doesn't say anything about size.
uintptr_t might be the same size as a void*. It might be larger. It could conceivably be smaller, although such a C++ implementation approaches perverse. For example on some hypothetical platform where void* is 32 bits, but only 24 bits of virtual address space are used, you could have a 24-bit uintptr_t which satisfies the requirement. I don't know why an implementation would do that, but the standard permits it.
(2) Actually, the standard explicitly mention the void* -> intptr_t/uintptr_t -> void* conversion, requiring those pointers to compare equal. It doesn't explicitly mandate that in the case intptr_t -> void* -> intptr_t the two integer values compare equal. It just mention in footnote 67 that "The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.".
I would like to know if there is a difference between:
Casting a primitive variable to another primitive type
Dereferencing a cast of a primitive variable's address to a pointer of another primitive type
I would also like to know if there is a good reason to ever use (2) over (1). I have seen (2) in legacy code which is why I was wondering. From the context, I couldn't understand why (2) was being favored over (1). And from the following test I wrote, I have concluded that at least the behavior of an upcast is the same in either case:
/* compile with gcc -lm */
#include <stdio.h>
#include <math.h>
int main(void)
{
unsigned max_unsigned = pow(2, 8 * sizeof(unsigned)) - 1;
printf("VALUES:\n");
printf("%u\n", max_unsigned + 1);
printf("%lu\n", (unsigned long)max_unsigned + 1); /* case 1 */
printf("%lu\n", *((unsigned long *)&max_unsigned) + 1); /* case 2 */
printf("SIZES:\n");
printf("%d\n", sizeof(max_unsigned));
printf("%d\n", sizeof((unsigned long)max_unsigned)); /* case 1 */
printf("%d\n", sizeof(*((unsigned long *)&max_unsigned))); /* case 2 */
return 0;
}
Output:
VALUES:
0
4294967296
4294967296
SIZES:
4
8
8
From my perspective, there should be no differences between (1) and (2), but I wanted to consult the SO experts for a sanity check.
The first cast is legal; the second cast may not be legal.
The first cast tells the compiler to use the knowledge of the type of the variable to make a conversion to the desired type; the compiler does it, provided that a proper conversion is defined in the language standard.
The second cast tells the compiler to forget its knowledge of the variable's type, and re-interpret its internal representation as that of a different type *. This has limited applicability: as long as the binary representation matches that of the type pointed by the target pointer, this conversion will work. However, this is not equivalent to the first cast, because in this situation value conversion never takes place.
Switching the type of the variable being cast to something with a different representation, say, a float, illustrates this point well: the first conversion produces a correct result, while the second conversion produces garbage:
float test = 123456.0f;
printf("VALUES:\n");
printf("%f\n", test + 1);
printf("%lu\n", (unsigned long)test + 1);
printf("%lu\n", *((unsigned long *)&test) + 1); // Undefined behavior
This prints
123457.000000
123457
1206984705
(demo)
* This is valid only when one of the types is a character type and the pointer alignment is valid, type conversion is trivial (i.e. when there is no conversion), when you change qualifiers or signedness, or when you cast to/from a struct/union with the first member being a valid conversion source/target. Otherwise, this leads to undefined behavior. See C 2011 (N1570), 6.5 7, for complete description. Thanks, Eric Postpischil, for pointing out the situations when the second conversion is defined.
Let's look at two simple examples, with int and float on modern hardware (no funny business).
float x = 1.0f;
printf("(int) x = %d\n", (int) x);
printf("*(int *) &x = %d\n", *(int *) &x);
Output, maybe... (your results may differ)
(int) x = 1
*(int *) &x = 1065353216
What happens with (int) x is you convert the value, 1.0f, to an integer.
What happens with *(int *) &x is you pretend that the value was already an integer. It was NOT an integer.
The floating point representation of 1.0 happens to be the following (in binary):
00111111 100000000 00000000 0000000
Which is the same representation as the integer 1065353216.
This:
(type)variable
takes the value of variable and converts it to type type. This conversion does not necessarily just copy the bits of the representation; it follows the language rules for conversions. Depending on the source and target types, the result may have the same mathematical value as variable, but it may be represented completely differently.
This:
*((type *)&variable)
does something called aliasing, sometimes informally called type-punning. It takes the chunk of memory occupied by variable and treats it as if it were an object of type type. It can yield odd results, or even crash your program, if the source and target types have different representations (say, an integer and a floating-point type), or even if they're of different sizes. For example, if variable is a 16-bit integer (say, it's of type short), and type is a 32-bit integer type, then at best you'll get a 32-bit result containing 16 bits of garbage -- whereas a simple value conversion would have given you a mathematically correct result.
The pointer cast form can also give you alignment problems. If variable is byte-aligned and type requires 2-byte or 4-byte alignment, for example, you can get undefined behavior, which could result either in a garbage result or a program crash. Or, worse yet, it might appear to work (which means you have a hidden bug that may show up later and be very difficult to track down).
You can examine the representation of an object by taking its address and converting it to unsigned char*; the language specifically permits treating any object as an array of character type.
But if a simple value conversion does the job, then that's what you should use.
If variable and type are both arithmetic, the cast is probably unnecessary; you can assign an expression of any arithmetic type to an object of any arithmetic type, and the conversion will be done implicitly.
Here's an example where the two forms have very different behavior:
#include <stdio.h>
int main(void) {
float x = 123.456;
printf("d = %g, sizeof (float) = %zu, sizeof (unsigned int) = %zu\n",
x, sizeof (float), sizeof (unsigned int));
printf("Value conversion: %u\n", (unsigned int)x);
printf("Aliasing : %u\n", *(unsigned int*)&x);
}
The output on my system (it may be different on yours) is:
d = 123.456, sizeof (float) = 4, sizeof (unsigned int) = 4
Value conversion: 123
Aliasing : 1123477881
What's the difference between “(type)variable” and “*((type *)&variable)”, if any?
The second expression may lead to alignment and aliasing issues.
The first form is the natural way to convert a value to another type. But assuming there is no violation of alignment or aliasing, in some cases the second expression has an advantage over the first form. *((type *)&variable) will yield a lvalue whereas (type)variable will not yield a lvalue (the result of a cast is never a lvalue).
This allows you do things like:
(*((type *)& expr)))++
See for example this option from Apple gcc manual which performs a similar trick:
-fnon-lvalue-assign (APPLE ONLY): Whenever an lvalue cast or an lvalue conditional expression is encountered, the compiler will issue a deprecation warning
and then rewrite the expression as follows:
(type)expr ---becomes---> *(type *)&expr
cond ? expr1 : expr2 ---becomes---> *(cond ? &expr1 : &expr2)
Casting the pointer makes a difference when working on a structure:
struct foo {
int a;
};
void foo()
{
int c;
((struct foo)(c)).a = 23; // bad
(*(struct foo *)(&c)).a = 42; // ok
}
First one ((type)variable is simple casting a variable to desired type and second one (*(type*)&variable) is derefencing a pointer after being casted by the desired pointer type.
The difference is that in the second case you may have undefined behavior. The reason being that unsinged is the same as unsigned int and an unsigned long may be larger than the the unsigned int, and when casting to a pointer which you dereference you read also the uninitialized part of the unsigned long.
The first case simply converts the unsigned int to an unsigned long with extends the unsigned int as needed.
If a value is set to an int e.g. 2, does the compiler convert the int types to the size it needs e.g. int8_t or uint16_t etc.?
Not in vanilla C, no. The compiler can't possible know what you meant if you do not tell it.
If you write
int value = 2;
the type is, by default, signed int. What the compiler does then really depends from the platform, but it has to guarantee that int's size is not less than short int's and not greater than long int'.s
For constants it may true, often the reverse conversion small to large is also done: byte to int for example.
It's somewhat dependent on the implementation and optimization techniques used by the compiler and the data alignment requirements by the architecture/OS. Have a look at this write up.
The compiler first looks at the context of the expression, learning about what type it expects. The context can be i.e.
Left hand side of an assignment
Expected argument type provided by the function header or operator definition
It then evaluates the expression, inserting implicit type conversions as needed (type coercion). It does
Promotion
Truncation
Rounding
In situations where all the bits matter, you need to be extremely carefull about what you write: Types, operators and order.
Integer numbers are values of the type "int". When you assign an integer value to short or char using the operator "=", the int value will be converted to short or char. The compiler may detect this conversion and do optimization to convert the integer value on compile time.
short a = 50; //50 is an int, it will be implicitly converted to short. The compiler may convert 50 to short on compile time.
int b = 60;
short c = b; //the value of b will be converted to short and assigned to c.
short d = b + 70; //b + 70 is a sum of ints and the result will be an int that will be converted to short.
int8_t and uint16_t are not standard types. Many times these types may be defined as something like:
typedef char int8_t;
typedef unsigned short uint16_t;