Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cast int to pointer - why cast to long first? (as in p = (void*) 42; )

Tags:

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?

like image 666
sleske Avatar asked Aug 19 '14 10:08

sleske


People also ask

What happens when you cast int to pointer?

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.

Can you cast an int to a pointer?

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.

Can you typecast pointer from void to int?

But, if you keep that in mind and get the value of sum by casting a void * to int it will work. void * is used this way sometimes to return either a value (e.g. int ) or an address to something (e.g. struct ). int a = 5; void *p = (void *)a; int b = (int)p; b = *(int *)p; // Undefined Behavior!

What does it mean to cast a pointer?

In the C language, casting is a construct to view a data object temporarily as another data type.


3 Answers

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).

like image 146
EOF Avatar answered Sep 20 '22 01:09

EOF


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.

like image 20
askmish Avatar answered Sep 22 '22 01:09

askmish


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:

  1. Assign integer to pointer of same size
  2. 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.

like image 7
Fusho Avatar answered Sep 23 '22 01:09

Fusho