Why can't we generalize the pointer declaration? - c++

The size of pointer is same irrespective of datatype it is pointing to.Then why do we need to declare the datatype it points to?
For example,
int *p; //p will point to an integer data type.
char *p; //p will point to a character
Then,why can't we generalize a pointer declaration like this
pointer p; //where p is an object of pointer class.

TL;DR because, different data types occupy different size in memory and has different alignment requirement.
To elaborate, a pointer variable holds an address which points to some type of data. Without the type associated, there would be no way to dereference the pointer and get the value.
In other words, to access the data pointed to by a pointer, the associated data type must be known.
The size of the pointer itself, has little connection to the actual data it points to.
There exists a pointer , void * which is considered a generic pointer, but then, you can't dereference it, as the result of dereference would attempt to produce an incomplete type. You need to cast it to a complete type to be able to dereference or apply pointer arithmetic on a void pointer.
The reason behind considering void * a generic pointer is as below, quoting from the C11 standard, chapter §6.3.2.3
A pointer to void may be converted to or from a pointer to any object type. A pointer to
any object type may be converted to a pointer to void and back again; the result shall
compare equal to the original pointer.
So, a void * can be used as a generic container which can hold any pointer type, but to make some operation on the pointer (which includes the knowledge of the data type), you need to cast it to a complete type first.

You can generalize pointers using void*. void* is a pointer to some address without any type information associated. However, there is little you can do with these pointers without casting them to an explicit pointer type first.
Consider the following example. It will not compile, because it's impossible to deduce the "value" pointed to by ptr is. You can't even know how many bytes constitute the value.
void print(const void * ptr) {
std::cout << *ptr; // What's the "value" of ptr?
}

Pointer arithmetic.
Explanation:
int arr[] = {4, 6, 9, 10};
int* x = arr;
*(x+0) = 4
*(x+1) = 6
Compiler knows that x+1 is actually x+sizeof(int) and not 1.
To read the second element the compiler has to skip sizeof(int) each time. Without knowing the actual type you can't dereference it and extract the data correctly.
Plus types differ in size, and the compiler needs to know how many bytes to read from the memory pointed to. A char will be 1 byte and int will be more than 1 byte.

A void * pointer can be viewed as a "generalized" pointer.
It can't be dereferenced, though, because by being generic it doesn't point to any one type of object. To actually use a void * pointer and access whatever it's pointing to, you need to cast it to the type of object you're accessing.
And since it doesn't point to any one type of object, there's no proper way to perform pointer arithmetic on a void * pointer.

The size of pointer is same irrespective of data type it is pointing to.
This is not necessarily true - pointers to different types may have different sizes and representations:
A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.48) 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.
C 2011 Online Draft, §6.2.5 ¶28
The value representation of pointer types
is implementation-defined. Pointers to layout-compatible types shall have the same value representation and
alignment requirements (3.11).
C++ 2014 Working Draft, §3.9.2, ¶3
On most modern desktop and servers they're the same, but don't expect it to be universally true.
Then why do we need to declare the data type it is pointing to?
Pointer arithmetic depends on the pointed-to type - if p points to an object of type T, then p + 1 points to the next object of type T. Since different types have different sizes, you need to know the pointed-to type to compute the offset correctly.

Then,why can't we generalize a pointer declaration like this
pointer p; //where p is an object of pointer class.
You actually can using void*. But the consequence is that all type (and size information) is lost at that point.
You'll have to keep track of that somehow (hard-coded type casts or whatever), to make that useful.

Related

Can std::launder be used to convert an object pointer to its enclosing array pointer?

The current draft standard (and presumably C++17) say in [basic.compound/4]:
[ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]
So a pointer to an object cannot be reinterpret_cast'd to get its enclosing array pointer.
Now, there is std::launder, [ptr.launder/1]:
template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;
Requires: p represents the address A of a byte in memory. An object X that is within its lifetime and whose type is similar to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below).
And the definion of reachable is in [ptr.launder/3]:
Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. The program is ill-formed if T is a function type or cv void.
Now, at first sight, it seems that std::launder is can be used to do the aforementioned conversion, because of the part I've put emphasis.
But. If p points to an object of an array, the bytes of the array is reachable according to this definition (even though p is not pointer-interconvertible to array-pointer), just like the result of the launder. So, it seems that the definition doesn't say anything about this issue.
So, can std::launder be used to convert an object pointer to its enclosing array pointer?
This depends on whether the enclosing array object is a complete object, and if not, whether you can validly access more bytes through a pointer to that enclosing array object (e.g., because it's an array element itself, or pointer-interconvertible with a larger object, or pointer-interconvertible with an object that's an array element). The "reachable" requirement means that you cannot use launder to obtain a pointer that would allow you to access more bytes than the source pointer value allows, on pain of undefined behavior. This ensures that the possibility that some unknown code may call launder does not affect the compiler's escape analysis.
I suppose some examples could help. Each example below reinterpret_casts a int* pointing to the first element of an array of 10 ints into a int(*)[10]. Since they are not pointer-interconvertible, the reinterpret_cast does not change the pointer value, and you get a int(*)[10] with the value of "pointer to the first element of (whatever the array is)". Each example then attempts to obtain a pointer to the entire array by calling std::launder on the cast pointer.
int x[10];
auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0]));
This is OK; you can access all elements of x through the source pointer, and the result of the launder doesn't allow you to access anything else.
int x2[2][10];
auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));
This is undefined. You can only access elements of x2[0] through the source pointer, but the result (which would be a pointer to x2[0]) would have allowed you to access x2[1], which you can't through the source.
struct X { int a[10]; } x3, x4[2]; // assume no padding
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK
This is OK. Again, you can't access through a pointer to x3.a any byte you can't access already.
auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));
This is (intended to be) undefined. You would have been able to reach x4[1] from the result because x4[0].a is pointer-interconvertible with x4[0], so a pointer to the former can be reinterpret_cast to yield a pointer to the latter, which then can be used for pointer arithmetic. See https://wg21.link/LWG2859.
struct Y { int a[10]; double y; } x5;
auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));
And this is again undefined, because you would have been able to reach x5.y from the resulting pointer (by reinterpret_cast to a Y*) but the source pointer can't be used to access it.
Remark: any non schizophrenic compiler will probably gladly accept that, as it would accept a C-style cast or a re-interpret cast, so just try and see is not an option.
But IMHO, the answer to your question is no. The emphasized immediately-enclosing array object if Y is an array element lies in a Remark paragraph, not in the Requires one. That means that provided the requires section is respected, the remarks one also applies. As an array and its element type are not similar types, the requirement is not satisfied and std::launder cannot be used.
What follows is more of a general (philosophycal?) interpretation. At the time of K&R C (in the 70's), C was intended to be able to replace assembly language. For that reason the rule was: the compiler must obey the programmer provided the source code can be translated. So no strict aliasing rule and a pointer was no more that an address with additional arithmetics rules. This strongly changed in C99 and C++03 (not speaking of C++11 +). Programmers are now supposed to use C++ as a high level language. That means that a pointer is just an object that allows to access another object of a given type, and an array and its element type are totally different types. Memory addresses are now little more than implementation details. So trying to convert a pointer to an array to a pointer to its first element is then against the philosophy of the language and could bite the programmer in a later version of the compiler. Of course real life compiler still accept it for compatibility reasons, but we should not even try to use it in modern programs.

Why can't I assign the address of a variable of one type(say double) to a pointer of int type?

In case of pointers, we know that their size is always same irrespective of data type of the variable it is pointing.
Data type is needed when dereferencing the pointer so it knows how much data it should read. So why cant i assign address of variable of double type to a pointer of int type?
why cant it happen like dereferencing a int pointer reads next 4 bytes from variable of double type and print its value?
Many computers have alignment requirements, so (for example) to read a 2-byte value, the address at which it's located must be a multiple of 2 (and likewise, a 4-byte value must be located at an address that's a multiple of 4, and so on). In fact, this alignment requirement is common enough that it's frequently referred to as "natural alignment".
Likewise, some types (e.g., floating point types) impose requirements on the bit sequence that can be read as that type, so if you try to take some arbitrary data and treat it as a double, you might trigger something like a floating point exception.
If you want to do this badly enough, you can use a cast to turn the pointer into the target type (but the results, if any, aren't usually portable).
You are guaranteed that you can convert a pointer to any other type of object to a pointer to unsigned char, and use that to read the bytes that represent the pointee object.
Also, if you primarily want an opaque pointer, without type information attached, you can assign a pointer to some other type to a void *.
Finally: no, not all pointers are actually the same. Pointers to different types can be different sizes (e.g., on the early Cray compilers, a char * was substantially different from an int *).
In case of pointers, we know that their size is always same irrespective of data type of the variable it is pointing.
No, we do not know that.
Chapter and verse for C
6.2.5 Types
...
28 A pointer to void shall have the same representation and alignment requirements as a
pointer to a character type.48) 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.
48) The same representation and alignment requirements are meant to imply interchangeability as
arguments to functions, return values from functions, and members of unions.
Emphasis added.
Chapter and verse for C++
3.9.2 Compound types
...
3 The type of a pointer to void or a pointer to an object type is called an object pointer type. [ Note: A pointer
to void does not have a pointer-to-object type, however, because void is not an object type. — end note ]
The type of a pointer that can designate a function is called a function pointer type. A pointer to objects
of type T is referred to as a “pointer to T.” [Example: a pointer to an object of type int is referred to as
“pointer to int ” and a pointer to an object of class X is called a “pointer to X.” — end example ] Except
for pointers to static members, text referring to “pointers” does not apply to pointers to members. Pointers
to incomplete types are allowed although there are restrictions on what can be done with them (3.11).
A valid value of an object pointer type represents either the address of a byte in memory (1.7) or a null
pointer (4.10). If an object of type T is located at an address A, a pointer of type cv T* whose value is the
address A is said to point to that object, regardless of how the value was obtained. [ Note: For instance,
the address one past the end of an array (5.7) would be considered to point to an unrelated object of the
array’s element type that might be located at that address. There are further restrictions on pointers to
objects with dynamic storage duration; see 3.7.4.3. — end note ] The value representation of pointer types
is implementation-defined. Pointers to layout-compatible types shall have the same value representation and
alignment requirements (3.11). [ Note: Pointers to over-aligned types (3.11) have no special representation,
but their range of valid values is restricted by the extended alignment requirement. This International
Standard specifies only two ways of obtaining such a pointer: taking the address of a valid object with
an over-aligned type, and using one of the runtime pointer alignment functions. An implementation may
provide other means of obtaining a valid pointer value for an over-aligned type. — end note ]
4 A pointer to cv-qualified (3.9.3) or cv-unqualified void can be used to point to objects of unknown type.
Such a pointer shall be able to hold any object pointer. An object of type cv void* shall have the same
representation and alignment requirements as cv char*.
Emphasis added. It is entirely possible to have different sizes and representations for different pointer types. There is no reason to expect a pointer to int to have the same size and representation as a pointer to double, or a pointer to a struct type, or a pointer to a function type. It's true for commodity platforms like x86, but not all the world runs on x86.
This is why you can't assign pointer values of one type to pointer values of another type without an explicit cast (except for converting between void * and other pointer types in C), since a representation change may be required.
Secondly, pointer arithmetic depends on the size of the pointed-to type. Assume you have pointers to a 32-bit int and a 64-bit double:
int *ip;
double *dp;
The expression ip + 1 will return the address of the next integer object (current address plus 4), while the expression dp + 1 will return the address of the next double object (current address plus 8).
If I assign the address of a double to a pointer to int, incrementing that int pointer won't take me to the next double object.

What is the data type of pointer variables?

I refereed the following link,
Link1
Link 2
In the above link1 it was mentioned in answer that "Pointers are of pointer type".
I just need to know is pointer is a data type or not.
No one has answered that question in single word. It is datatype or not?
This is the second link which i refered says that
Pointers are simply a variable that hold an address so one could argue that a pointer is a data type, but it is not defined as a data type (per "The C Programming Language". Kernighan & Ritchie).
Yes, a pointer is a data type. The purest form of which (mainly talking about C here) is void *. A void * can be used to pass a memory address around (which is what a pointer is), but it can't be dereferenced. Dereferencing a pointer is what you do to get at the data contained at the memory location the pointer is pointing at, which implies that you know what type of data you're reading from the memory. The type determines how much memory will be read, and because a void is "nothing". A void * can be set to point at any block of memory, that can contain any type, so you can cast a void * to any other pointer type (int *, for example), and dereference that, instead.
Each type we have is used to store a specific piece of data (value), we use a char to store a single character, an int to store an integer, double to store double precision decimals and so on. None of these types are used to store locations in memory, apart from pointers. So just like the other types, a pointer is used to store a specific piece of data. And the mother of all pointers is void *.
Sadly, this void * is rather restricted: you can't dereference it, you can't use it for pointer arithmetic (not according to the standard anyway). So C provides you with a series of derived pointer types, that make life easier: char *, int *, double * and so on.
What they are, really, is short-hand for: (char *) void * my_ptr;
Some more attempts at making my point as clearly as possible:
Pointers have their own size, irrespective of the type they're said to point at:
char a_character = 'a'; //type: a char
char *a_char_ptr = &a_character; //memory address, in this case, the one holding a_charachter
The distinction is probably best seen by looking at the sizes of both these vars:
printf("%zu <> %zu\n", sizeof a_character, sizeof a_char_ptr);
The code above will give you something like "1 <> 8" or "1 <> 4", depending on what system you're on. Numbers represent the size, in bytes.
Pointers also have their own printf format specifier: %p:
printf("%c is the value stored at %p\n", *a_char_ptr, (void *) a_char_ptr);
To print the actual memory address (the actual value of a pointer), you are required to cast the pointer to the generic void * type. A void pointer is sort of the generic pointer; it's the pointer that makes no assumptions as to the data it is pointing at. This is what malloc, calloc and realloc return, a generic pointer, that can be set to point at any other type. So what is a char *? It's a generic pointer type, set to point at blocks of memory of 1 byte in size (sizeof(char)). In a sense, a typed pointer, then, is a derived type, but think of it like this: char * is short for (char *) void *my_ptr;
But really, what is a type? The gist of it is that a type is way to determine how data in memory is supposed to be interpreted. A variable of the type char represents a character. A variable of the type int represents an integer. Same applies to pointers: char *x is not of the type char, it's of the type char * (pointer to char). This means that char *x itself is a location in memory we can use to read one or more char values.
I could rant on for a while but TL;TR:
Yes, a pointer is a data type (void * in its purest form). The pure form is quite unusable (because you can't dereference it). Instead of having to cast the pointer every time you decide to use it, C offers the convenience of derived pointer types (like char *, int * and so on). But really, they're pointers, and therefore a data-type in their own right.
You've asked two different questions.
Your title asks "What is the data type of pointer variables?". The answer is simple: a pointer variable is of some pointer type. For example, given:
int *ptr;
ptr is a pointer object, and its type is int*, which is a pointer type.
The body of your question asks whether "a pointer is a data type or not". By any reasonable definition of the phrase "data type", pointer types are data types.
The C standard never defines the phrase "data type", but it does use it (informally) in several places. It happens that none of the uses of the phrase "data type" in the standard refer to pointer types, but that doesn't tell us anything.
The standard says that all types are either function types or object types. Object types are further divided into a number of categories: integer types, array types, structure types, union types, pointer types, etc. A pointer type can be a pointer to an object type or a pointer to a function type. (It can be a pointer to an incomplete object type; as of the 2011 standard, incomplete types are classified as object types.)
Another ambiguity in your question is your use of the word "pointer". The word "pointer" by itself commonly refers to an object of pointer type, but it can also refer to a value of pointer type (for example, the standard says that malloc returns a pointer). It's better to use "pointer" as an adjective rather than as a noun, so you can have:
a pointer type;
a pointer object (an object of pointer type);
a pointer expression (an expression that yields a result of pointer type); or
a pointer value (the value, of pointer type, yielded by a pointer expression).
A pointer type is an object type. A pointer object is an object; an object is defined by the standard as a "region of data storage in the execution environment, the contents of which can represent values". So a pointer object is a region of data storage.
Following your Link 2, some random person on the Internet wrote that "Pointers are simply a variable that hold an address so one could argue that a pointer is a data type, but it is not defined as a data type (per "The C Programming Language". Kernighan & Ritchie)". I don't know whether K&R defines the term "data type"; since this person didn't provide a specific citation, it's difficult to tell without searching the book. But it's the standard, not K&R, that defines the language.
I'm curious: why would you think that a pointer type wouldn't be considered a data type?
For example in the C Standard there is no formal definition of the term data type. There are object types and function types. At the same time pointers are derived types constructed from object and function types.
Thus in general case pointers are data types that is they are data types that are constructed from object and function types.
Also there is definition of term object in the C Standard
3.15
1 object
region of data storage in the execution environment, the contents of which can represent
values
So there is some contradiction in the Standard. On the one hand pointers are objects because they occupy memory and the memory represents their values. So we may say that pointers are object types. On the other hand pointers are considered as derived types from object types.
In my opinion it would be better if there would be explicitly written in the Standard that pointers are derived object types or derived function types.
In any case you may bravely say that pointers are data types!:)
Yes, pointer is a data type and a pointer variable store that pointer data type.
Pointer is a data-type. So we can create pointer variables which can hold the address of memory location.
Pointer types are data types; they store pointer values.
There is no one single pointer type; a pointer to int is a different type from a pointer to char, which is a different type from a pointer to double, which is a different type from a pointer to a 10-element array of int, which is a different type from a pointer to an 11-element array of int, etc.
Different pointer types may have different sizes and representations; the only pointer types that are guaranteed to have the same sizes and representations are void * and char *.
Your question probably refers to the "data type of a pointer", in contrast to the data type of the pointed-to data, which is what one would understand in the first place.
Based on this assumption, then please look at type uintptr_t or void*.
To quote Drew Dorman's answer: "uintptr_t is an unsigned integer type that is capable of storing a pointer. Which typically means that it's the same size as a pointer"
Of course, its size is platform-dependant: 32 bit or 64 bit. So don't transport this variable across platforms of different size.
Please note, to assign it you have to cast from the 'specific' pointer type, to the 'generic' one:
int var = 1;
int* addrOfVar = &var; // pointer to variable
uintptr_t pVar = (uintptr_t)&var;
uintptr_t pVar2 = reinterpret_cast<uintptr_t>(&var); // alternative cast
See a pointer variable stores the address of another variable. And when we access this pointer variable it points to the address of variable which eventually directs us to the data stored inside that variable. Now depending upon the type of data stored inside the original variable we can specify the data type of that pointer variable.
I just need to know is pointer is a data type or not.
All pointer types are data types, but plain "pointer" is not a data type. More precisely, "pointer" is a type modifier that you can apply to absolutely any type. So you can have pointer-to-char, and pointer-to-long, and pointer-to-double, not to mention pointer-to-pointer-to-char, etc.
There are three of these type modifiers in C: pointer-to, array-of, and function-returning. These can be combined in almost any combination. (But, to be sure, a few combinations are invalid. You can't have arrays of functions, or functions returning arrays.)
The thing is Pointer itself is a data type of it's own. It basically store a memory address.
Now it can be used to store a memory address of any variable too.

Any type of pointer can point to anything?

Is this statement correct? Can any "TYPE" of pointer can point to any other type?
Because I believe so, still have doubts.
Why are pointers declared for definite types? E.g. int or char?
The one explanation I could get was: if an int type pointer was pointing to a char array, then when the pointer is incremented, the pointer will jump from 0 position to the 2 position, skipping 1 position in between (because int size=2).
And maybe because a pointer just holds the address of a value, not the value itself, i.e. the int or double.
Am I wrong? Was that statement correct?
Pointers may be interchangeable, but are not required to be.
In particular, on some platforms, certain types need to be aligned to certain byte-boundaries.
So while a char may be anywhere in memory, an int may need to be on a 4-byte boundary.
Another important potential difference is with function-pointers.
Pointers to functions may not be interchangeable with pointers to data-types on many platforms.
It bears repeating: This is platform-specific.
I believe Intel x86 architectures treat all pointers the same.
But you may well encounter other platforms where this is not true.
Every pointer is of some specific type. There's a special generic pointer type void* that can point to any object type, but you have to convert a void* to some specific pointer type before you can dereference it. (I'm ignoring function pointer types.)
You can convert a pointer value from one pointer type to another. In most cases, converting a pointer from foo* to bar* and back to foo* will yield the original value -- but that's not actually guaranteed in all cases.
You can cause a pointer of type foo* to point to an object of type bar, but (a) it's usually a bad idea, and (b) in some cases, it may not work (say, if the target types foo and bar have different sizes or alignment requirements).
You can get away with things like:
int n = 42;
char *p = (char*)&n;
which causes p to point to n -- but then *p doesn't give you the value of n, it gives you the value of the first byte of n as a char.
The differing behavior of pointer arithmetic is only part of the reason for having different pointer types. It's mostly about type safety. If you have a pointer of type int*, you can be reasonably sure (unless you've done something unsafe) that it actually points to an int object. And if you try to treat it as an object of a different type, the compiler will likely complain about it.
Basically, we have distinct pointer types for the same reasons we have other distinct types: so we can keep track of what kind of value is stored in each object, with help from the compiler.
(There have been languages that only have untyped generic pointers. In such a language, it's more difficult to avoid type errors, such as storing a value of one type and accidentally accessing it as if it were of another type.)
Any pointer can refer to any location in memory, so technically the statement is correct. With that said, you need to be careful when reinterpreting pointer types.
A pointer basically has two pieces of information: a memory location, and the type it expects to find there. The memory location could be anything. It could be the location where an object or value is stored; it could be in the middle of a string of text; or it could just be an arbitrary block of uninitialised memory.
The type information in a pointer is important though. The array and pointer arithmetic explanation in your question is correct -- if you try to iterate over data in memory using a pointer, then the type needs to be correct, otherwise you may not iterate correctly. This is because different types have different sizes, and may be aligned differently.
The type is also important in terms of how data is handled in your program. For example, if you have an int stored in memory, but you access it by dereferencing a float* pointer, then you'll probably get useless results (unless you've programmed it that way for a specific reason). This is because an int is stored in memory differently from the way a float is stored.
Can any "TYPE" of pointer can point to any other type?
Generally no. The types have to be related.
It is possible to use reinterpret_cast to cast a pointer from one type to another, but unless those pointers can be converted legally using a static_cast, the reinterpret_cast is invalid. Hence you can't do Foo* foo = ...; Bar* bar = (Bar*)foo; unless Foo and Bar are actually related.
You can also use reinterpret_cast to cast from an object pointer to a void* and vice versa, and in that sense a void* can point to anything -- but that's not what you seem to be asking about.
Further you can reinterpret_cast from object pointer to integral value and vice versa, but again, not what you appear to be asking.
Finally, a special exception is made for char*. You can initialize a char* variable with the address of any other type, and perform pointer math on the resulting pointer. You still can't dereference thru the pointer if the thing being pointed to isn't actually a char, but it can then be casted back to the actual type and used that way.
Also keep in mind that every time you use reinterpret_cast in any context, you are dancing on the precipice of a cliff. Dereferencing a pointer to a Foo when the thing it actually points to is a Bar yields Undefined Behavior when the types are not related. You would do well to avoid these types of casts at all costs.
Some pointers are more equal than others...
First of all, not all pointers are necessarily the same thing. Function pointers can be something very different from data pointers, for instance.
Aside: Function pointers on PPC
On the PPC platform, this was quite obvious: A function pointer was actually two pointers under the hood, so there was simply no way to meaningfully cast a function pointer to a data pointer or back. I.e. the following would hold:
int* dataP;
int (*functionP)(int);
assert(sizeof(dataP) == 4);
assert(sizeof(functionP) == 8);
assert(sizeof(dataP) != sizeof(functionP));
//impossible:
//dataP = (int*)functionP; //would loose information
//functionP = (int (*)(int))dataP; //part of the resulting pointer would be garbage
Alignment
Furthermore, there is problems with alignment: Depending on the platform some data types may need to be aligned in memory. This is especially common with vector data types, but could apply to any type larger than a byte. For instance, if an int must be 4 byte aligned, the following code might crash:
char a[4];
int* alias = (int*)a;
//int foo = *alias; //may crash because alias is not aligned properly
This is not an issue if the pointer comes from a malloc() call, as that is guaranteed to return sufficiently aligned pointers for all types:
char* a = malloc(sizeof(int));
int* alias = (int*)a;
*alias = 0; //perfectly legal, the pointer is aligned
Strict aliasing and type punning
Finally, there are strict aliasing rules: You must not access an object of one type through a pointer to another type. Type punning is forbidden:
assert(sizeof(float) == sizeof(uint32_t));
float foo = 42;
//uint32_t bits = *(uint32_t*)&foo; //type punning is illegal
If you absolutely must reinterpret a bit pattern as another type, you must use memcpy():
assert(sizeof(float) == sizeof(uint32_t));
float foo = 42;
uint32_t bits;
memcpy(&bits, &foo, sizeof(bits)); //bit pattern reinterpretation is legal when copying the data
To allow memcpy() and friends to actually be implementable, the C/C++ language standards provide for an exception for char types: You can cast any pointer to a char*, copy the char data over to another buffer, and then access that other buffer as some other type. The results are implementation defined, but the standards allow it. Use cases are mostly general data manipulation routines like I/O, etc.
TL;DR:
Pointers are much less interchangeable than you think. Don't reinterpret pointers in any other way than to/from char* (check alignment in the "from" case). And even that does not work for function pointers.

Is the aliasing rule symmetric?

I had a discussion with someone on IRC and this question turned up. We are allowed by the Standard to change an object of type int by a char lvalue.
int a;
char *b = (char*) &a;
*b = 0;
Would we be allowed to do this in the opposite direction, if we know that the alignment is fine?
The issue I'm seeing is that the aliasing rule does not cover the simple case of the following, if one considers the aliasing rule as a non-symmetric relation
int a;
a = 0;
The reason is, that each object contains a sequence of sizeof(obj) unsigned char objects (called the "object representation"). If we change the int, we will change some or all of those objects. However, the aliasing rule only states we are allowed to change a int by an char or unsigned char, but not the other way around. Another example
int a[1];
int *ra = a;
*ra = 0;
Only one direction is described by 3.10/15 ("An aggregate or union type that includes..."), but this time we need the other way around ("A type that is the element or non-static data member type of an aggregate...").
Is the other direction implied? This question also applies to C.
The aliasing rule simply states that there's one "effective type" (C99 6.5.7, plus footnote 73) for any given object in memory, and any accesses to such an object go through one of:
A type compatible with the effective type (qualifiers such as const and restrict, as well signed/unsigned-ness may vary)
A struct or union containing one such type
A character type
The effective type isn't specified in advanced, of course - it's just a construct that is used to specify aliasing. But the intent is simply that you don't access the same object with two different non-character types.
So the answer is, yes, you can indeed go the other direction.
The standard (C99 6.3.2.3 §7) defines pointer casts like this as "just fine", the casted pointer will point at the same address. (Unless the CPU has alignment which makes the cast impossible, then it is undefined behavior.)
That is, the actual cast in itself is fine. What will happen if you start to manipulate the data... now that's another implementation-defined story.
Here's from the standard:
"A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned (57) for the pointed-to type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer.
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."
"57) In general, the concept ‘‘correctly aligned’’ is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is
correctly aligned for a pointer to type C."
I think I'm a bit confused by the question, but the 2nd and 3rd examples are accessing the int through an lvalue that has the object's type (int in the examples).
C++ 3.10/15 states as it's first item that it's OK to access the object through an lvalue that has the the type of "the dynamic type of the object".
What am I misunderstanding in the question?