Is NULL data-pointer the same as NULL function-pointer? - c

POSIX requires (I think) that function pointers can be stored in a variable of type void* and/or passed to functions expecting a void* argument, even though this is strictly non-standard.
My question is this: if I test such a variable/argument for NULL-ness, if (!(variable or argument)) say, and the result is true, does that necessarily mean that the function-pointer is NULL? Could the bit pattern for a NULL void* data-pointer ever equate to a non-NULL function-pointer value? Would any sane implementation do this? Do any common implementations do this?
EDIT: this answer (to a different question admittedly) made me wonder if I had to cast the void* intermediate back to the original function pointer type before I could test NULL-ness, otherwise it's UB... is that true? Can those who posted answers weigh in on this question?

C 2018 6.3.2.3 4 says:
Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
This paragraph, unlike paragraph 7, does not limit the conversions to pointers to object types or pointers to function types. Therefore, if a null pointer of some pointer-to-function type is converted to void *, the result is a null pointer, and then applying ! to it yields 1.
Establishing the converse, that if applying ! to a pointer yields 1, it necessarily arose from a null pointer, it more difficult. We could imagine some non-null function pointer that, when converted to void *, yields a null pointer. Considering the intent of POSIX to allow function pointers to be temporarily stored in void *, we can conclude that converting a pointer to a function to void * should never result in a null pointer.
Could the bit pattern for a NULL void* data-pointer ever equate to a non-NULL function-pointer value?
The C standard does not discuss the bit patterns used to represent pointers. The semantics are established in terms of the values.
Would any sane implementation do this?
Certainly bare-metal boot code on some hardware might put executable instructions at address zero and call a function there for some reason and might also use address zero as a null pointer. It will simply be designed not to depend on that function at address zero not being tested for being a null pointer.
Outside of such special situations, i.e., for all practical purposes, this is not done. If some software decides it needs a special representation for a null pointer, it will set aside some address for that and not use that address for any ordinary function or object.

This C11 Draft Standard suggests that, while not part of the core standard, casting function pointers to object pointers (for purposes of testing, as in your NULL-check) falls under the category of "Common Extensions" (Annex J.5):
J.5.7 Function pointer casts
1     A pointer to an object or to void may be cast to
a pointer to a function, allowing data to be invoked as a function
(6.5.4).
2     A pointer to a function may be cast to a pointer
to an object or to void, allowing a function to be inspected or
modified (for example, by a debugger) (6.5.4)

If an implementation were to target a platform where the common idiomatic bit pattern for a null code pointer was different from the common idiomatic bit pattern for a null data pointer, it would be free to either expose the fact that the pointers are different to the programmer, or wrap pointer operations in an abstraction that would make them behave identically. The authors of the Standard made no attempt to guess which approach would be more useful, since people who were actually writing code for the platform in question would be better placed than the Committee to judge the pros and cons of each approach.
In the vastly more common situation where a target platform uses the same representation for both kinds of null pointers, the authors of the Standard would have expected that implementations would store both kinds of pointers in that fashion, but would have also regarded the idea that such implementations should behave that way as sufficiently obvious that there was no need to expend ink recommending such behavior.
The Standard's failure to mandate the common behavior does not imply any judgment that implementations should ever do anything else, but merely an acknowledgment that implementations might exist where some other behavior might sometimes be more useful than the commonplace one.

Related

Can we cast an integer value with a pointer type in c programming language?

In a lot of c code snippets i see (void *)0,what does casting an integer with a pointer mean?
In general, casting an integer to a pointer "means" creating a pointer value pointing to that exact numeric address in memory. But I have put the word "means" in quotes here, because the actual meaning is more complicated.
There are basically three subcases.
If the integer is the constant 0 (and especially if the casted-to pointer type is void *), this is a null pointer constant. By definition, it is the syntactic construct that gets you a null pointer value. By definition, a null pointer points nowhere (it "compares unequal to the pointer to any actual object").
In old-school C programming, and to this day in embedded C programming, if the integer is nonzero, it's an actual memory address you're trying to access.
In modern, portable, high-level C programming, and especially here on Stack Overflow, the meaning is implementation-defined at best or undefined at worst, meaning that you shouldn't do it and you probably shouldn't even ask about it, since no one can say for sure what it means.
In answer to your comment in response to Jonathan Leffler's, NULL is not actually a keyword, but rather a preprocessor macro which expands to either (void *)0 or, maybe, plain 0. (How it is that it's possible for a plain 0, without a cast, to work properly as a null pointer constant is a fascinating but frequently badly misunderstood side question which I am not going to go into here. See section 5 in the C FAQ list if curious.)

C standard compliant way to access null pointer address?

In C, deferencing the null pointer is Undefined Behavior, however the null pointer value has a bit representation that in some architectures make it points to a valid address (e.g the address 0).
Let's call this address the null pointer address, for the sake of clarity.
Suppose I want to write a piece of software in C, in an environment with unrestrained access to memory. Suppose further I want to write some data at the null pointer address: how would I achieve that in a standard compliant way?
Example case (IA32e):
#include <stdint.h>
int main()
{
uintptr_t zero = 0;
char* p = (char*)zero;
return *p;
}
This code when compiled with gcc with -O3 for IA32e gets transformed into
movzx eax, BYTE PTR [0]
ud2
due to UB (0 is the bit representation of the null pointer).
Since C is close to low level programming, I believe there must be a way to access the null pointer address and avoid UB.
Just to be clear
I'm asking about what the standard has to say about this, NOT how to achieve this in a implementation defined way.
I know the answer for the latter.
I read (part of) the C99 standard to clear my mind. I found the sections that are of interest for my own question and I'm writing this as a reference.
DISCLAIMER
I'm an absolute beginner, 90% or more of what I have written is wrong, makes no sense, or may break you toaster. I also try to make a rationale out of the standard, often with disastrous and naive results (as stated in the comment).
Don't read.
Consult #Olaf, for a formal and professional answer.
For the following, the term architectural address designed a memory address as seen by the processor (logical, virtual, linear, physical or bus address). In other word the addresses that you would use in assembly.
In section 6.3.2.3. it reads
An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.
If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal
to a pointer to any object or function.
and regarding integer to pointer conversion
An integer may be converted to any pointer type. Except as previously specified [i.e. for the case of null pointer constant], 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†.
These imply that the compiler, to be compliant, need only to implement a function int2ptr from integer to pointers that
int2ptr(0) is, by definition, the null pointer.
Note that int2ptr(0) is not mandated to be 0. It can be any bit representation.
*int2ptr(n != 0) has no constraints.
Note that this means that int2ptr needs not to be the identity function, nor a function that return valid pointers!
Given the code below
char* p = (char*)241;
The standard makes absolute no guarantee that the expression *p = 56; will write to the architectural address 241.
And so it gives no direct way to access any other architectural address (including int2ptr(0), the address designed by a null pointer, if valid).
Simply put the standard does not deal with architectural addresses, but with pointers, their comparison, conversions and their operations‡.
When we write code like char* p = (char*)K we are not telling the compiler to make p point to the architectural address K, we are telling it to make a pointer out of the integer K, or in other term to make p point to the (C abstract) address K.
Null pointer and the (architectural) address 0x0 are not the same (cit.) and so is true for any other pointer made from the integer K and the (architectural) address K.
For some reasons, childhood heritages, I thought that integer literals in C could be used to express architectural addresses, instead I was wrong and that only happen to be (sort of) correct in the compilers I was using.
The answer to my own question is simply: There is no standard way because there are no (architectural) address in the C standard document. This is true for every (architectural) address, not only the int2ptr(0) one1.
Note about return *(volatile char*)0;
The standard says that
If an
invalid value [a null pointer value is an invalid value] has been assigned to the pointer, the behavior of the unary * operator is undefined.
and that
Therefore any expression referring
to such an [volatile] object shall be evaluated strictly according to the rules of the abstract machine.
The abstract machine says that * is undefined for null pointer values, so that code shouldn't differ from this one
return *(char*)0;
which is also undefined.
Indeed they don't differ, at least with GCC 4.9, both compile to the instructions stated in my question.
The implementation defined way to access the 0 architectural address is, for GCC, the use of the -fno-isolate-erroneous-paths-dereference flag which produces the "expected" assembly code.
†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.
‡Unfortunately it says that the & yields the address of its operand, I believe this is a bit improper, I would say that it yields a pointer to its operand. Consider a variable a that is known to resides at address 0xf1 in a 16 bit address space and consider a compiler that implements int2ptr(n) = 0x8000 | n. &a would yield a pointer whose bit representation is 0x80f1 which is not the address of a.
1Which was special to me because it was the only one, in my implementations, that couldn't be accessed.
As OP has correctly concluded in her answer to her own question:
There is no standard way because there are no (architectural) address in the C standard document. This is true for every (architectural) address, not only the int2ptr(0) one.
However, a situation where one would want to access memory directly is likely one where a custom linker script is employed. (I.e. some kind of embedded systems stuff.) So I would say, the standard compliant way of doing what OP asks would be to export a symbol for the (architectural) address in the linker script, and not bother with the exact address in the C code itself.
A variation of that scheme would be to define a symbol at address zero and simply use that to derive any other required address. To do that add something like the following to the SECTIONS portion of the linker script (assuming GNU ld syntax):
_memory = 0;
And then in your C code:
extern char _memory[];
Now it is possible to e.g. create a pointer to the zero address using for example char *p = &_memory[0]; (or simply char *p = _memory;), without ever converting an int to a pointer. Similarly, int addr = ...; char *p_addr = &_memory[addr]; will create a pointer to the address addr without technically casting an int to a pointer.
(This of course avoids the original question, because the linker is independent from the C standard and C compiler, and every linker might have a different syntax for their linker script. Also, the generated code might be less efficient, because the compiler is not aware of the address being accessed. But I think this still adds an interesting perspective to the question, so please forgive the slightly off-topic answer..)
Whatever solution is going to be implementation-dependent. Needfully. ISO C does not describe the environment a C programs runs on; rather, what a conforming C program looks like among a variety of environments («data-processing systems»). The Standard cannot indeed guarantee what you would get by accessing an address that is not an array of objects, i.e. something you visibly allocated, not the environment.
Therefore, I would use something the standard leaves as implementation-defined (and even as conditionally-supported) rather than undefined behavior*: Inline assembly. For GCC/clang:
asm volatile("movzx 0, %%eax;") // *(int*)0;
It also worth mentioning freestanding environments, the one you seem to be in. The standard says about this execution model (emphasis mine):
§ 5.1.2
Two execution environments are defined: freestanding and hosted. [...]
§ 5.1.2.1, comma 1
In a freestanding environment (in which C program execution may take place without any benefit of an operating system), the name and type of the function called at program startup are implementation-defined. Any library facilities available to a freestanding program, other than the minimal set required by clause 4, are implementation-defined. [...]
Notice it doesn't say you can access any address at will.
Whatever that could mean. Things are a bit different when you are the implementation the standard delegates control to.
All quotes are from the draft N. 1570.
The C Standard does not require that implementations have addresses that resemble integers in any way shape or form; all it requires is that if types uintptr_t and intptr_t exist, the act of converting a pointer to uintptr_t or intptr_t will yield a number, and converting that number directly back to the same type as the original pointer will yield a pointer equal to the original.
While it is recommended that platforms which use addresses that resemble integers should define conversions between integers and addresses in a fashion that would be unsurprising to someone familiar with such mapping, that is not a requirement, and code relying upon such a recommendation would not be strictly conforming.
Nonetheless, I would suggest that if a quality implementation specifies that it performs integer-to-pointer conversion by a simple bitwise mapping, and if there may be plausible reasons why code would want to access address zero, a it should regard statements like:
*((uint32_t volatile*)0) = 0x12345678;
*((uint32_t volatile*)x) = 0x12345678;
as a request to write to address zero and address x, in that order even if
x happens to be zero, and even if the implementation would normally trap on
null pointer accesses. Such behavior isn't "standard", insofar as the
Standard says nothing about the mapping between pointers and integers, but
a good quality implementation should nonetheless behave sensibly.
I'm assuming the question you are asking is:
How do I access memory such that a pointer to that memory has the same representation as the null pointer?
According to a literal reading of the Standard, this is not possible. 6.3.2.3/3 says that any pointer to an object must compare unequal to the null pointer.
Therefore this pointer we are talking about must not point to an object. But the deference operator *, applied to an object pointer, only specifies the behaviour in the case that it points to an object.
Having said that, the object model in C has never been specified rigorously, so I would not put too much weight into the above interpretation. Nevertheless, it seems to me that whatever solution you come up with is going to have to rely on non-standard behaviour from whichever compiler is in use.
We see an example of this in the other answers in which gcc's optimizer detects an all-bits-zero pointer at a late stage of processing and flags it as UB.

When is casting between pointer types not undefined behavior in C?

As a newcomer to C, I'm confused about when casting a pointer is actually OK.
As I understand, you can pretty much cast any pointer type to any other type, and the compiler will let you do it. For example:
int a = 5;
int* intPtr = &a;
char* charPtr = (char*) intPtr;
However, in general this invokes undefined behavior (though it happens to work on many platforms).
This said, there seem to be some exceptions:
you can cast to and from void* freely (?)
you can cast to and from char* freely (?)
(at least I've seen it in code...).
So which casts between pointer types are not undefined behaviour in C?
Edit:
I tried looking into the C standard (section "6.3.2.3 Pointers", at http://c0x.coding-guidelines.com/6.3.2.3.html ), but didn't really understand it, apart from the bit about void*.
Edit2:
Just for clarification: I'm explicitly only asking about "normal" pointers, i.e. not about function pointers. I realize that the rules for casting function pointers are very restrictive. As I matter of fact, I've already asked about that :-): What happens if I cast a function pointer, changing the number of parameters
Basically:
a T * may be freely converted to a void * and back again (where T * is not a function pointer), and you will get the original pointer.
a T * may be freely converted to a U * and back again (where T * and U * are not function pointers), and you will get the original pointer if the alignment requirements are the same. If not, the behaviour is undefined.
a function-pointer may be freely converted to any other function-pointer type and back again, and you will get the original pointer.
Note: T * (for non-function-pointers) always satisfies the alignment requirements for char *.
Important: None of these rules says anything about what happens if you convert, say, a T * to a U * and then try to dereference it. That's a whole different area of the standard.
Oli Charlesworth's excellent answer lists all cases where casting a pointer to a pointer of a different type gives a well-defined result.
In addition, there are four cases where casting a pointer gives implementation-defined results:
You can cast a pointer to an sufficiently large (!) integer type. C99 has the optional types intptr_t and uintptr_t for this purpose. The result is implementation-defined. On platforms that address memory as a contiguous stream of bytes ("linear memory model", used by most modern platforms), it usually returns the numeric value of the memory address the pointer points to, thus simply a byte count. However, not all platforms use a linear memory model, which is why this is implementation-defined :-).
Conversely, you can cast an integer to a pointer. If the integer has a type large enough for intptr_t or uintptr_t and was created by casting a pointer, casting it back to the same pointer type will give you back that pointer (which however may no longer be valid). Otherwise the result is implementation-defined. Note that actually dereferencing the pointer (as opposed to just reading its value) may still be UB.
You can cast a pointer to any object to char*. Then the result points to the lowest addressed byte of the object, and you can read the remaining bytes of the object by incrementing the pointer, up to the object's size. Of course, which values you actually get is again implementation-defined...
You can freely cast null pointers, they'll always stay null pointers regardless of pointer type :-).
Source: C99 standard, sections 6.3.2.3 "Pointers", and 7.18.1.4 "Integer types capable of holding object pointers".
As far as I can tell, all other casts of a pointer to a pointer of a different type are undefined behavior. In particular, if you are not casting to char or a sufficiently large integer type, it may always be UB to cast a pointer to a different pointer type - even without dereferencing it.
This is because the types may have different alignment, and there is no general, portable way to make sure different types have compatible alignment (except for some special cases, such as signed/unsigned integer type pairs).
Generally, if as usual nowadays the pointers themselves have the same alignment properties, the problem is not the cast itself, but whether or not you may access the data through the pointer.
Casting any type T* to void* and back is guaranteed for any object type T: this is guaranteed to give you exactly the same pointer back. void* is the catch all object pointer type.
For other casts between object types there is no guarantee, accessing an object through such a pointer may cause all sorts of problems, such as alignments (bus error), trap representations of integers. Different pointer types are not even guaranteed to have the same width, so theoretically you might even loose information.
One cast that should always work, though, is to (unsigned char*). Through such a pointer you may then investigate the individual bytes of your object.
The authors of the Standard made no attempt to weigh the costs and benefits of supporting conversions among most combinations of pointer types on platforms where such support would be expensive, since:
Most platforms where such conversions would be expensive would likely have been obscure ones the authors of the Standard didn't know about.
People using such platforms would be better placed than the authors of the Standard with the costs and benefits of such support.
If some particular platform uses a different representation for int* and double*, I think the Standard would deliberately allow for the possibility that e.g. round-drip conversion from double* to int* and back to double* would work consistently but conversions from int* to double* and back to int* might fail.
I don't think the authors of the Standard intended that such operations might fail on platforms where such conversions cost nothing. They described the Spirit of C in the charter and rationale documents as including the principle "Don't prevent [or needlessly obstruct] the programmer from doing what needs to be done." Given that principle, there would be no need for the Standard to mandate that implementations process actions in a way that helps programmers accomplish what they need to do in cases where doing so would cost nothing, since implementations that make a bona fide effort to uphold the Spirit of C will behave in such fashion with or without a mandate.

What is guaranteed about the size of a function pointer?

In C, I need to know the size of a struct, which has function pointers in it. Can I be guaranteed that on all platforms and architectures:
the size of a void* is the same size as a function pointer?
the size of the function pointer does not differ due to its return type?
the size of the function pointer does not differ due to its parameter types?
I assume the answer is yes to all of these, but I want to be sure. For context, I'm calling sizeof(struct mystruct) and nothing more.
From C99 spec, section 6.2.5, paragraph 27:
A pointer to void shall have the same
representation and alignment
requirements as a pointer to a
character type. Similarly, pointers
to qualified or unqualified versions of
compatible types shall have the same
representation and alignment
requirements. All pointers to
structure types shall have the same
representation and alignment
requirements as each other. All
pointers to union types shall have the
same representation and alignment
requirements as each other. Pointers
to other types need not have the same
representation or alignment
requirements.
So no; no guarantee that a void * can hold a function pointer.
And section 6.3.2.3, paragraph 8:
A pointer to a function of one type
may be converted to a pointer to a
function of another type and back
again; the result shall compare equal
to the original pointer.
implying that one function pointer type can hold any other function pointer value. Technically, that's not the same as guaranteeing that function-pointer types can't vary in size, merely that their values occupy the same range as each other.
No, no, no.
C doesn't favour Harvard architectures with different code and data pointer sizes, because ideally when programming for such an architecture you want to store data in program memory (string literals and the like), and to do that you'd need object pointers into the code space. But it doesn't forbid them, so as far as the standard is concerned function pointers can refer to an address space which has a different size from the data address space.
However, any function pointer can be cast to another function pointer type[*] and back without trashing the value, in the same way that any object pointer can be cast to void* and back. So it would be rather surprising for function pointers to vary in size according to their signature. There's no obvious "use" for the extra space, if you have to be able to somehow store the same value in less space and then retrieve it when cast back.
[*] Thanks, schot.
In addition to the other answers, Wikipedia says this:
http://en.wikipedia.org/wiki/Function_pointer
Although function pointers in C and
C++ can be implemented as simple
addresses, so that typically
sizeof(Fx)==sizeof(void *), member
pointers in C++ are often implemented
as "fat pointers", typically two or
three times the size of a simple
function pointer, in order to deal
with virtual inheritance.
A function pointer is an abstraction. As long as the requirements of the standard are fulfilled, anything is possible. I.e. if you have less than 256 functions in your program, function pointers could be implemented by using a single byte with the value 0 for NULL and the values 1 to 255 as the index into a table with the physical addresses. If you exceed 255 functions, it could be extended to use 2 bytes.
There is a practical example of differing sizes that used to be common. In the MS-DOS & early Windows C programming, in the "medium" memory model you had 16-bit data pointers but 32-bit function pointers, and the "compact" memory model was the other way round.

Is casting an integer value to a void* an often used paradigm in callbacks?

Rather than sending an actual pointer to a value, the value is cast to a pointer. I found these examples in the GUI interface code of a GTK program.
g_signal_connect (pastebutton[pane],
"clicked",
G_CALLBACK(on_paste_button_pressed),
(void*)((long)pane<<4));
In the above example, I am referring to the last parameter of g_signal_connect. When on_paste_button_pressed is called by GTK2, on_paste_button_pressed casts the user_data void pointer back like so:
int pane = ((long)user_data) >> 4;
Actually, I added this particular example to the code, but I based it upon what was already there. I added the bit-shifting so as to avoid warnings about casting. The program itself has four panes containing quite a number of widgets, the copy and paste buttons allow you to copy all the values from one pane to another.
Is this way of casting a value to a pointer address often used, and are there reasons why this should not be used?
edit:
The cast from an integer to a void pointer can also be achieved like so:
void* void_ptr = some_int - NULL;
It is used. It is used quite commonly when it does what's required.
One reason not to use it is that theoretically pointer size might be smaller than the source integer size.
Another reason not to use it is that it allows you to pass only one piece of integer data to the callback. If in the future you'll need to add another piece of data (or switch to a non-integer), you'll have to locate and rewrite every single place where the callback makes access to the passed data. So, if there's a chance that you'd have to extend the data in the future, it is better to create a struct (even if it holds just a single int at this time) and pass a pointer to that struct.
But if you are sure that you'll never have to pass anything other than that single integer and that your integer fits into a void *, then this technique is not in any way broken.
P.S. Pedantically speaking, neither C nor C++ appear to have the roundtrip guarantee for integer-to-void *-to-integer conversion, i.e. they don't guarantee that it will work and restore the original integral value.
You should use macros GINT_TO_POINTER() and GPOINTER_TO_INT() to cast between pointers and integers.
glib: Type Conversion Macros
Casting an integer to a pointer is used to pass a value by-value. This is the preferred way if you do need a by-reference parameter because the compiler then does not need to dereference the pointer.
The bit-shifting is a bad idea because it can cause overflows.
For really portable code, you should use intptr_t as your integer type cause it will nicely fit into a pointer.
It does see use in these kinds of cases, yes. It works on many platforms, but might fail because an arbitrary integer is not always a valid pointer value, though the shifting you do should get around that. It is also possible that a pointer cannot hold all the values that an integer can; this would be the case if you're using a 64-bit long on a platform where pointers are 32 bits, and of course since you are shifting the value, it could also fail even if pointers and integers are the same size. If you want to use this trick, you should probably check sizeof (void*) against the size of the integer type you use, and at runtime check against the actual value if the pointer isn't big enough. It's probably not worth it to make this fully portable so that you would use an actual pointer on platforms where that's needed, so either limit yourself to platforms where this trick works or abandon the trick altogether.
I find it just fine. Don't have statistics about use frequency, but do you really care?
I'm not sure I understand how bit-shifting helps, though.
It is used but it's by no means portable so you have to be careful.
The C standard does not mandate that pointer types have at least as many bits as integer types, so you may not always be able to do it.
But I can't recall any platform in which pointers have actually been smaller than integers so you're probably safe (despite not being technically safe).
The only reason I can think that the casting may be there for is to remove the possibility of alignment warnings. Section 6.3.2.3 of the C1x draft states:
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.
This is from the C-FAQ:
Q: How are integers converted to and from pointers? Can I temporarily stuff an integer into a pointer, or vice versa?
A: Once upon a time, it was guaranteed that a pointer could be converted to an integer (though one never knew whether an int or a long might be required), and that an integer could be converted to a pointer, and that a pointer remained unchanged when converted to a (large enough) integer and back again, and that the conversions (and any mapping) were intended to be ``unsurprising to those who know the addressing structure of the machine.'' In other words, there is some precedent and support for integer/pointer conversions, but they have always been machine dependent, and hence nonportable. Explicit casts have always been required (though early compilers rarely complained if you left them out).
The ANSI/ISO C Standard, in order to ensure that C is widely implementable, has weakened those earlier guarantees. Pointer-to-integer and integer-to-pointer conversions are implementation-defined (see question 11.33), and there is no longer any guarantee that pointers can be converted to integers and back, without change.
Forcing pointers into integers, or integers into pointers, has never been good practice. When you need a generic slot that can hold either kind of data, a union is a much better idea.
See also questions 4.15, 5.18, and 19.25.
References: K&R1 Sec. A14.4 p. 210
K&R2 Sec. A6.6 p. 199
ISO Sec. 6.3.4
Rationale Sec. 3.3.4
H&S Sec. 6.2.3 p. 170, Sec. 6.2.7 pp. 171-2

Resources