Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Community Proposals
Community Proposals
tag:snake search within a tag
answers:0 unanswered questions
user:xxxx search by author id
score:0.5 posts with 0.5+ score
"snake oil" exact phrase
votes:4 posts with 4+ votes
created:<1w created < 1 week ago
post_type:xxxx type of post
Search help
Notifications
Mark all as read See all your notifications »
Q&A

Welcome to Software Development on Codidact!

Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.

Post History

80%
+6 −0
Q&A Can you ever assume that casting pointers is safe?

You can assume that a cast is safe if, and only if, you do know all the various language rules at play. Unfortunately there is no shortcuts or a general simple rule here. You simply need to know a...

posted 2mo ago by Lundin‭  ·  edited 2mo ago by Lundin‭

Answer
#2: Post edited by user avatar Lundin‭ · 2026-07-07T15:15:34Z (2 months ago)
  • **You can assume that a cast is safe if, and only if, you do know all the various language rules at play.**
  • Unfortunately there is no shortcuts or a general simple rule here. You simply need to know about all the various types of conversions there are, or otherwise you can assume that any cast that you do is unsafe.
  • My general advise for beginners to intermediately skilled C programmers is therefore: never use the cast operator. It is reserved for experts only and believe me when I say that I'm not overly pedantic here.
  • If looking at pointer casts specifically, the first thing one must learn is the difference between _conversion_ and _dereferencing_. A conversion is the act of changing from one type to another. In this case from one pointer type to another, or from an arithmetic type to a pointer etc.
  • Dereferencing, in this context, is the act of actually using the new pointer type obtained through a pointer conversion.
  • C is surprisingly tolerant towards all manner pointer _conversions_ - there are not a lot of things that can go wrong in the conversation itself. (With some exceptions, as we will notice further down.)
  • It's when you start using the new pointer types by dereferencing them, that all manner of pitfalls might open up.
  • Lets start by looking at what types of conversions involving pointers there are.
  • **Conversions**
  • C has two manner of conversions: implicit or explicit. Implicit are things that go on between the lines, explicit conversions is when the programmer asks for it openly, normally by using a cast. (Another less common explicit conversion would be type punning.)
  • A common beginner mistake is to speak of "implicit vs explicit casts", there is no such thing. What they actually mean is conversions. A cast is _always_ an explicit conversion.
  • Furthermore, almost all pointer conversions need to be explicit by means of a cast. We can start there, at the (C23 6.5.5) standard definition of the cast operator:
  • > Conversions that involve pointers, other than where permitted by the constraints of 6.5.17.2, shall be specified by means of an explicit cast.
  • So when dealing with pointers, we always have to cast. Save for the exceptions pointed out in 6.5.17.2, which are the rules of assignment. The rules of assignment is a list with a number of cases when we do not need to cast, in order for an assignment to be valid and a conversion to happen implicitly. For example while assigning a null pointer constant such as `NULL` to a pointer.
  • Otherwise, the specific rules about conversions are covered by the chapter C23 6.3.3.3, where we can read various rules:
  • **void pointer conversions**
  • > A pointer to `void` can be converted to or from a pointer to any object type. A pointer to any object
  • type can be converted to a pointer to `void` and back again; the result shall compare equal to the original pointer.
  • So as far as `void` pointers go, we can tell that any conversion goes, pretty much. In the previously mentioned rules of assignment, we would also note that `void` pointers can even get assigned to/from other pointers without a cast and we still get a pointer conversion. But please note that the standard says _object_ pointers, as opposed to function pointers. We cannot mix `void*` and function pointers because that is not well-defined by the standard.
  • **Null pointer conversions**
  • C is also quite lenient towards null pointer constants. (I'll cite C17 6.3.2.3 here not to confuse the reader with C23's `nullptr_t`):
  • > 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.
  • This too is an allowed exception in the rules of assignment where we need no cast to trigger the conversion.
  • **Integer-to-pointer/pointer-to-integer conversions**
  • Then there are rules for going between pointers and integers:
  • > 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.
  • >
  • > 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.
  • Here we start to note a bunch of cases where the conversion itself might fail - these manner of conversions are clearly dangerous. And it's quite easy to imagine - consider something like `(int*)0x4321` on a system with 32 bit alignment. The address is clearly not on a 32 bit = 4 byte evenly aligned address.
  • So here is one of the cases where the conversion itself might fail - for example suppose that the given CPU has an instruction trap if you ever load a misaligned address into an index register reserved for pointers. There are a couple of real-world computers that will do just that.
  • Another obvious problem is when the integer type picked and the address bus used by the system are of different sizes - there will simply not be enough room to store the result.
  • And looking at `(int*)0x4321` alone with no context, we have no idea what's actually stored at that address, if anything. There are also concerns beyond the C standard here such as the address space is virtual and we try to do something at an address where we aren't supposed to be messing around: that could result in OS signals being raised or even hardware exceptions.
  • Also notably, integer to/from pointer conversions is _not_ one of the exceptions to assignment that we are allowed to do without a cast, which is a common misconception. See ["Pointer from integer/integer from pointer without a cast" issues](https://stackoverflow.com/questions/52186834/pointer-from-integer-integer-from-pointer-without-a-cast-issues)
  • **Pointer-to-pointer conversions**
  • Specifically _object_ pointer to _object_ pointer conversions, is when we convert between two different pointer types and they aren't function pointers:
  • > A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced 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.
  • The same concerns about alignment are here too - the conversion itself might fail in that case. Between the lines we also have the exotic case where two pointers need not necessarily have the same size and representation, C doesn't really make any guarantees there.
  • We note a special rule here too: whenever we convert any pointer into a pointer to character type (`char*`, `signed char*` or `unsigned char*`), we are allowed to use that type to inspect each byte of the pointed-at object, as if it was an array of characters. This is used for hardware-related programming, object serialization etc. Alignment is not a concern when we use character types, since they by definition always point at 1 byte and have no alignment (or 1 byte alignment if you will).
  • But notably there is no special exception for doing the opposite: going from a character type to a pointer to a larger type.
  • **Function pointer conversions**
  • Function pointers are a bit of a special snowflake. Sometimes we can convert to/from them, sometimes we can't. An observant reader of the quoted standard texts might note that the standard sometimes explicitly says object pointer, sometimes it just says pointer. As previously noted, we can't go from `void` pointers to/from function pointers, but as we can see from the previously quoted texts there is nothing stopping us from converting from a null pointer constant to a function pointer, or converting to/from integers to function pointers.
  • > 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. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
  • Here we can note that the conversion part itself is just fine. But if we use an incorrect function pointer type when calling the actual function, all bets are off. And since there is no generic function pointer like `void*`, that means that the only function pointer conversion followed by dereferencing/calling the function which is safe, is the case when we use two compatible function pointers. And two function pointers are only compatible if they match parameters and return types exactly (with some historic nowadays obsolete exceptions for "functions taking any parameter").
  • **Dereferencing**
  • Okay so we did a cast and the compiler didn't complain! Ship it? Not quite...
  • _Misalignment might still be a problem_
  • We have already picked up that misalignment is a notable danger which could already trigger an error upon the pointer conversion itself. But just because it didn't doesn't mean that misaligned access is fine on the given system - more likely the problem will happen when you dereference the pointer and try to access what's there, but do so with misaligned type. Some systems support this (most notably most 8/16 bit CPUs) but a whole lot of systems do not.
  • _Pointer/data size and format mismatches_
  • I also already mentioned the scenario where the data type and the pointer type are simply of different sizes, which may compile, but lead to bogus execution.
  • _Is the address even valid to use as we intend to?_
  • It was also mentioned that a lot of systems are guarded by a "Memory Mapping Unit" (MMU) which is hardware support to divide memory into different areas. This is highly CPU-specific. Some systems may prevent physical address accesses. Others may separate executable memory and data memory. Or we could simply be pointing at memory where nothing sensible is stored. All of these scenarios are obviously problematic.
  • _Strict pointer aliasing_
  • Additionally, there is the peculiar "strict pointer aliasing rules" which I explained in detail here: [How does the strict aliasing rule enable or prevent compiler optimizations?](https://software.codidact.com/posts/292985) The first part of the answer gives some explanations of why C is so lenient against pointer conversions - namely to allow type punning and generic programming, the presence of the `void*` and so on.
  • In case of hardware-related programming or in case we obtained a chunk of heap allocated memory, there will be cases where a fixed address in memory have no "effective type" and then we may ignore strict aliasing at least until we start using that memory. So if we know what's stored at a fixed address we may set a pointer there and access it. Within reason - there are rules for what we may do with pointers too.
  • _Pointer arithmetic is restricted_
  • Previously I mentioned the case where we may expect any object in C byte by byte using a character pointer. That rule assumes that we treat that object like a character array of exactly `sizeof(the_object)` bytes. Because in C we are _never_ allowed to do pointer arithmetic beyond the end of an allocated array. Tough luck - specially in embedded systems.
  • Pointer arithmetic as well as array indexing both boil down to the additive `+` operator: `ptr++` boils down to `ptr = ptr + 1`, and `ptr[i]` boils down to `*(ptr + i)`. We can read about the `+` operator when applied to pointers in a long and complex section of the standard 6.5.6. I will quote parts of it:
  • > For the purposes of these operators, a pointer to an object that is not an element of an array behaves
  • the same as a pointer to the first element of an array of length one with the type of the object as its
  • element type.
  • That is, if we have `int x;` then for the purpose of pointer arithmetic that one is equivalent to `int x[1];`.
  • Then if we do something like `ptr + n` where `ptr` is the pointer operand and we end up with a result:
  • > If both the pointer operand and the result point
  • to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past
  • the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.
  • So if we convert something to a pointer then commence to use any form of pointer arithmetic or indexing, the underlying type there must be an array. Curiously though, if the compiler doesn't actually know what's stored at that address, we probably get away with it, though that's obviously beyond the scope of the C standard. The problem is when the compiler does know what's stored there and we try to access it differently.
  • For example, a lot of programmers think that C allows you do something like this:
  • ```c
  • // BAD!
  • short arr[n];
  • int* ptr = (int*)arr;
  • ptr[4] = something;
  • ```
  • The conversion part is fine, we already noted that converting between two object pointer types like `short*` and `int*` in itself is probably just fine. _Unless_ there is an alignment requirement for `int`, then the conversion fail. Or otherwise we can fail when dereferencing the array using a type with different alignment, in case `short` is aligned at 2 bytes boundaries but `int` requires 4.
  • Furthermore, we are attempting to access something with effective type `short` as an `int`, which is likely a strict aliasing violation.
  • Furthermore, we may be going outside the bounds of the original array and in fact since `arr` is not an `int[]` array to begin with, what will happen when we cast and start to do crazy pointer arithmetic is anyone's guess.
  • So there's some 3-4 ways where that code could go severely wrong even though it passed compilation cleanly. _It doesn't matter_ that we know that `short` is 2 bytes, `int` is 4 bytes and `n` is some large number. C doesn't really promise anything for these kind of wild & crazy pointer stunts, except it does promise to be a nasty show-stopper in a number of cases.
  • ---
  • And that's all the scenarios I could think about when it comes to _pointer_ casts. There are many other things we can cast, but this answer is already way too long.
  • **You can assume that a cast is safe if, and only if, you do know all the various language rules at play.**
  • Unfortunately there is no shortcuts or a general simple rule here. You simply need to know about all the various types of conversions there are, or otherwise you can assume that any cast that you do is unsafe.
  • My general advise for beginners to intermediately skilled C programmers is therefore: never use the cast operator. It is reserved for experts only and believe me when I say that I'm not overly pedantic here.
  • If looking at pointer casts specifically, the first thing one must learn is the difference between _conversion_ and _dereferencing_. A conversion is the act of changing from one type to another. In this case from one pointer type to another, or from an arithmetic type to a pointer etc.
  • Dereferencing, in this context, is the act of actually using the new pointer type obtained through a pointer conversion.
  • C is surprisingly tolerant towards all manner pointer _conversions_ - there are not a lot of things that can go wrong in the conversion itself. (With some exceptions, as we will notice further down.)
  • It's when you start using the new pointer types by dereferencing them, that all manner of pitfalls might open up.
  • Lets start by looking at what types of conversions involving pointers there are.
  • **Conversions**
  • C has two manner of conversions: implicit or explicit. Implicit are things that go on between the lines, explicit conversions is when the programmer asks for it openly, normally by using a cast. (Another less common explicit conversion would be type punning.)
  • A common beginner mistake is to speak of "implicit vs explicit casts", there is no such thing. What they actually mean is conversions. A cast is _always_ an explicit conversion.
  • Furthermore, almost all pointer conversions need to be explicit by means of a cast. We can start there, at the (C23 6.5.5) standard definition of the cast operator:
  • > Conversions that involve pointers, other than where permitted by the constraints of 6.5.17.2, shall be specified by means of an explicit cast.
  • So when dealing with pointers, we always have to cast. Save for the exceptions pointed out in 6.5.17.2, which are the rules of assignment. The rules of assignment is a list with a number of cases when we do not need to cast, in order for an assignment to be valid and a conversion to happen implicitly. For example while assigning a null pointer constant such as `NULL` to a pointer.
  • Otherwise, the specific rules about conversions are covered by the chapter C23 6.3.3.3, where we can read various rules:
  • **void pointer conversions**
  • > A pointer to `void` can be converted to or from a pointer to any object type. A pointer to any object
  • type can be converted to a pointer to `void` and back again; the result shall compare equal to the original pointer.
  • So as far as `void` pointers go, we can tell that any conversion goes, pretty much. In the previously mentioned rules of assignment, we would also note that `void` pointers can even get assigned to/from other pointers without a cast and we still get a pointer conversion. But please note that the standard says _object_ pointers, as opposed to function pointers. We cannot mix `void*` and function pointers because that is not well-defined by the standard.
  • **Null pointer conversions**
  • C is also quite lenient towards null pointer constants. (I'll cite C17 6.3.2.3 here not to confuse the reader with C23's `nullptr_t`):
  • > 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.
  • This too is an allowed exception in the rules of assignment where we need no cast to trigger the conversion.
  • **Integer-to-pointer/pointer-to-integer conversions**
  • Then there are rules for going between pointers and integers:
  • > 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.
  • >
  • > 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.
  • Here we start to note a bunch of cases where the conversion itself might fail - these manner of conversions are clearly dangerous. And it's quite easy to imagine - consider something like `(int*)0x4321` on a system with 32 bit alignment. The address is clearly not on a 32 bit = 4 byte evenly aligned address.
  • So here is one of the cases where the conversion itself might fail - for example suppose that the given CPU has an instruction trap if you ever load a misaligned address into an index register reserved for pointers. There are a couple of real-world computers that will do just that.
  • Another obvious problem is when the integer type picked and the address bus used by the system are of different sizes - there will simply not be enough room to store the result.
  • And looking at `(int*)0x4321` alone with no context, we have no idea what's actually stored at that address, if anything. There are also concerns beyond the C standard here such as the address space is virtual and we try to do something at an address where we aren't supposed to be messing around: that could result in OS signals being raised or even hardware exceptions.
  • Also notably, integer to/from pointer conversions is _not_ one of the exceptions to assignment that we are allowed to do without a cast, which is a common misconception. See ["Pointer from integer/integer from pointer without a cast" issues](https://stackoverflow.com/questions/52186834/pointer-from-integer-integer-from-pointer-without-a-cast-issues)
  • **Pointer-to-pointer conversions**
  • Specifically _object_ pointer to _object_ pointer conversions, is when we convert between two different pointer types and they aren't function pointers:
  • > A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced 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.
  • The same concerns about alignment are here too - the conversion itself might fail in that case. Between the lines we also have the exotic case where two pointers need not necessarily have the same size and representation, C doesn't really make any guarantees there.
  • We note a special rule here too: whenever we convert any pointer into a pointer to character type (`char*`, `signed char*` or `unsigned char*`), we are allowed to use that type to inspect each byte of the pointed-at object, as if it was an array of characters. This is used for hardware-related programming, object serialization etc. Alignment is not a concern when we use character types, since they by definition always point at 1 byte and have no alignment (or 1 byte alignment if you will).
  • But notably there is no special exception for doing the opposite: going from a character type to a pointer to a larger type.
  • **Function pointer conversions**
  • Function pointers are a bit of a special snowflake. Sometimes we can convert to/from them, sometimes we can't. An observant reader of the quoted standard texts might note that the standard sometimes explicitly says object pointer, sometimes it just says pointer. As previously noted, we can't go from `void` pointers to/from function pointers, but as we can see from the previously quoted texts there is nothing stopping us from converting from a null pointer constant to a function pointer, or converting to/from integers to function pointers.
  • > 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. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.
  • Here we can note that the conversion part itself is just fine. But if we use an incorrect function pointer type when calling the actual function, all bets are off. And since there is no generic function pointer like `void*`, that means that the only function pointer conversion followed by dereferencing/calling the function which is safe, is the case when we use two compatible function pointers. And two function pointers are only compatible if they match parameters and return types exactly (with some historic nowadays obsolete exceptions for "functions taking any parameter").
  • **Dereferencing**
  • Okay so we did a cast and the compiler didn't complain! Ship it? Not quite...
  • _Misalignment might still be a problem_
  • We have already picked up that misalignment is a notable danger which could already trigger an error upon the pointer conversion itself. But just because it didn't doesn't mean that misaligned access is fine on the given system - more likely the problem will happen when you dereference the pointer and try to access what's there, but do so with misaligned type. Some systems support this (most notably most 8/16 bit CPUs) but a whole lot of systems do not.
  • _Pointer/data size and format mismatches_
  • I also already mentioned the scenario where the data type and the pointer type are simply of different sizes, which may compile, but lead to bogus execution.
  • _Is the address even valid to use as we intend to?_
  • It was also mentioned that a lot of systems are guarded by a "Memory Mapping Unit" (MMU) which is hardware support to divide memory into different areas. This is highly CPU-specific. Some systems may prevent physical address accesses. Others may separate executable memory and data memory. Or we could simply be pointing at memory where nothing sensible is stored. All of these scenarios are obviously problematic.
  • _Strict pointer aliasing_
  • Additionally, there is the peculiar "strict pointer aliasing rules" which I explained in detail here: [How does the strict aliasing rule enable or prevent compiler optimizations?](https://software.codidact.com/posts/292985) The first part of the answer gives some explanations of why C is so lenient against pointer conversions - namely to allow type punning and generic programming, the presence of the `void*` and so on.
  • In case of hardware-related programming or in case we obtained a chunk of heap allocated memory, there will be cases where a fixed address in memory have no "effective type" and then we may ignore strict aliasing at least until we start using that memory. So if we know what's stored at a fixed address we may set a pointer there and access it. Within reason - there are rules for what we may do with pointers too.
  • _Pointer arithmetic is restricted_
  • Previously I mentioned the case where we may expect any object in C byte by byte using a character pointer. That rule assumes that we treat that object like a character array of exactly `sizeof(the_object)` bytes. Because in C we are _never_ allowed to do pointer arithmetic beyond the end of an allocated array. Tough luck - specially in embedded systems.
  • Pointer arithmetic as well as array indexing both boil down to the additive `+` operator: `ptr++` boils down to `ptr = ptr + 1`, and `ptr[i]` boils down to `*(ptr + i)`. We can read about the `+` operator when applied to pointers in a long and complex section of the standard 6.5.6. I will quote parts of it:
  • > For the purposes of these operators, a pointer to an object that is not an element of an array behaves
  • the same as a pointer to the first element of an array of length one with the type of the object as its
  • element type.
  • That is, if we have `int x;` then for the purpose of pointer arithmetic that one is equivalent to `int x[1];`.
  • Then if we do something like `ptr + n` where `ptr` is the pointer operand and we end up with a result:
  • > If both the pointer operand and the result point
  • to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past
  • the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.
  • So if we convert something to a pointer then commence to use any form of pointer arithmetic or indexing, the underlying type there must be an array. Curiously though, if the compiler doesn't actually know what's stored at that address, we probably get away with it, though that's obviously beyond the scope of the C standard. The problem is when the compiler does know what's stored there and we try to access it differently.
  • For example, a lot of programmers think that C allows you do something like this:
  • ```c
  • // BAD!
  • short arr[n];
  • int* ptr = (int*)arr;
  • ptr[4] = something;
  • ```
  • The conversion part is fine, we already noted that converting between two object pointer types like `short*` and `int*` in itself is probably just fine. _Unless_ there is an alignment requirement for `int`, then the conversion fail. Or otherwise we can fail when dereferencing the array using a type with different alignment, in case `short` is aligned at 2 bytes boundaries but `int` requires 4.
  • Furthermore, we are attempting to access something with effective type `short` as an `int`, which is likely a strict aliasing violation.
  • Furthermore, we may be going outside the bounds of the original array and in fact since `arr` is not an `int[]` array to begin with, what will happen when we cast and start to do crazy pointer arithmetic is anyone's guess.
  • So there's some 3-4 ways where that code could go severely wrong even though it passed compilation cleanly. _It doesn't matter_ that we know that `short` is 2 bytes, `int` is 4 bytes and `n` is some large number. C doesn't really promise anything for these kind of wild & crazy pointer stunts, except it does promise to be a nasty show-stopper in a number of cases.
  • ---
  • And that's all the scenarios I could think about when it comes to _pointer_ casts. There are many other things we can cast, but this answer is already way too long.
#1: Initial revision by user avatar Lundin‭ · 2026-07-07T15:09:30Z (2 months ago)
**You can assume that a cast is safe if, and only if, you do know all the various language rules at play.**

Unfortunately there is no shortcuts or a general simple rule here. You simply need to know about all the various types of conversions there are, or otherwise you can assume that any cast that you do is unsafe. 

My general advise for beginners to intermediately skilled C programmers is therefore: never use the cast operator. It is reserved for experts only and believe me when I say that I'm not overly pedantic here.

If looking at pointer casts specifically, the first thing one must learn is the difference between _conversion_ and _dereferencing_. A conversion is the act of changing from one type to another. In this case from one pointer type to another, or from an arithmetic type to a pointer etc.

Dereferencing, in this context, is the act of actually using the new pointer type obtained through a pointer conversion. 

C is surprisingly tolerant towards all manner pointer _conversions_ - there are not a lot of things that can go wrong in the conversation itself. (With some exceptions, as we will notice further down.)
It's when you start using the new pointer types by dereferencing them, that all manner of pitfalls might open up.

Lets start by looking at what types of conversions involving pointers there are.

**Conversions**  
C has two manner of conversions: implicit or explicit. Implicit are things that go on between the lines, explicit conversions is when the programmer asks for it openly, normally by using a cast. (Another less common explicit conversion would be type punning.)

A common beginner mistake is to speak of "implicit vs explicit casts", there is no such thing. What they actually mean is conversions. A cast is _always_ an explicit conversion.

Furthermore, almost all pointer conversions need to be explicit by means of a cast. We can start there, at the (C23 6.5.5) standard definition of the cast operator:

> Conversions that involve pointers, other than where permitted by the constraints of 6.5.17.2, shall be specified by means of an explicit cast.

So when dealing with pointers, we always have to cast. Save for the exceptions pointed out in 6.5.17.2, which are the rules of assignment. The rules of assignment is a list with a number of cases when we do not need to cast, in order for an assignment to be valid and a conversion to happen implicitly. For example while assigning a null pointer constant such as `NULL` to a pointer.

Otherwise, the specific rules about conversions are covered by the chapter C23 6.3.3.3, where we can read various rules:

**void pointer conversions**

> A pointer to `void` can be converted to or from a pointer to any object type. A pointer to any object
type can be converted to a pointer to `void` and back again; the result shall compare equal to the original pointer.

So as far as `void` pointers go, we can tell that any conversion goes, pretty much. In the previously mentioned rules of assignment, we would also note that `void` pointers can even get assigned to/from other pointers without a cast and we still get a pointer conversion. But please note that the standard says _object_ pointers, as opposed to function pointers. We cannot mix `void*` and function pointers because that is not well-defined by the standard.

**Null pointer conversions**  
C is also quite lenient towards null pointer constants. (I'll cite C17 6.3.2.3 here not to confuse the reader with C23's `nullptr_t`):

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

This too is an allowed exception in the rules of assignment where we need no cast to trigger the conversion.

**Integer-to-pointer/pointer-to-integer conversions**  
Then there are rules for going between pointers and integers:

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

Here we start to note a bunch of cases where the conversion itself might fail - these manner of conversions are clearly dangerous. And it's quite easy to imagine - consider something like `(int*)0x4321` on a system with 32 bit alignment. The address is clearly not on a 32 bit = 4 byte evenly aligned address. 

So here is one of the cases where the conversion itself might fail - for example suppose that the given CPU has an instruction trap if you ever load a misaligned address into an index register reserved for pointers. There are a couple of real-world computers that will do just that.

Another obvious problem is when the integer type picked and the address bus used by the system are of different sizes - there will simply not be enough room to store the result.

And looking at `(int*)0x4321` alone with no context, we have no idea what's actually stored at that address, if anything. There are also concerns beyond the C standard here such as the address space is virtual and we try to do something at an address where we aren't supposed to be messing around: that could result in OS signals being raised or even hardware exceptions. 

Also notably, integer to/from pointer conversions is _not_ one of the exceptions to assignment that we are allowed to do without a cast, which is a common misconception. See ["Pointer from integer/integer from pointer without a cast" issues](https://stackoverflow.com/questions/52186834/pointer-from-integer-integer-from-pointer-without-a-cast-issues)

**Pointer-to-pointer conversions**  
Specifically _object_ pointer to _object_ pointer conversions, is when we convert between two different pointer types and they aren't function pointers:

> A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced 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.

The same concerns about alignment are here too - the conversion itself might fail in that case. Between the lines we also have the exotic case where two pointers need not necessarily have the same size and representation, C doesn't really make any guarantees there.

We note a special rule here too: whenever we convert any pointer into a pointer to character type (`char*`, `signed char*` or `unsigned char*`), we are allowed to use that type to inspect each byte of the pointed-at object, as if it was an array of characters. This is used for hardware-related programming, object serialization etc. Alignment is not a concern when we use character types, since they by definition always point at 1 byte and have no alignment (or 1 byte alignment if you will).

But notably there is no special exception for doing the opposite: going from a character type to a pointer to a larger type.

**Function pointer conversions**  
Function pointers are a bit of a special snowflake. Sometimes we can convert to/from them, sometimes we can't. An observant reader of the quoted standard texts might note that the standard sometimes explicitly says object pointer, sometimes it just says pointer. As previously noted, we can't go from `void` pointers to/from function pointers, but as we can see from the previously quoted texts there is nothing stopping us from converting from a null pointer constant to a function pointer, or converting to/from integers to function pointers.

> 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. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

Here we can note that the conversion part itself is just fine. But if we use an incorrect function pointer type when calling the actual function, all bets are off. And since there is no generic function pointer like `void*`, that means that the only function pointer conversion followed by dereferencing/calling the function which is safe, is the case when we use two compatible function pointers. And two function pointers are only compatible if they match parameters and return types exactly (with some historic nowadays obsolete exceptions for "functions taking any parameter").

**Dereferencing**  
Okay so we did a cast and the compiler didn't complain! Ship it? Not quite...

_Misalignment might still be a problem_  
We have already picked up that misalignment is a notable danger which could already trigger an error upon the pointer conversion itself. But just because it didn't doesn't mean that misaligned access is fine on the given system - more likely the problem will happen when you dereference the pointer and try to access what's there, but do so with misaligned type. Some systems support this (most notably most 8/16 bit CPUs) but a whole lot of systems do not.

_Pointer/data size and format mismatches_  
I also already mentioned the scenario where the data type and the pointer type are simply of different sizes, which may compile, but lead to bogus execution.

_Is the address even valid to use as we intend to?_  
It was also mentioned that a lot of systems are guarded by a "Memory Mapping Unit" (MMU) which is hardware support to divide memory into different areas. This is highly CPU-specific. Some systems may prevent physical address accesses. Others may separate executable memory and data memory. Or we could simply be pointing at memory where nothing sensible is stored. All of these scenarios are obviously problematic.

_Strict pointer aliasing_  
Additionally, there is the peculiar "strict pointer aliasing rules" which I explained in detail here: [How does the strict aliasing rule enable or prevent compiler optimizations?](https://software.codidact.com/posts/292985) The first part of the answer gives some explanations of why C is so lenient against pointer conversions - namely to allow type punning and generic programming, the presence of the `void*` and so on.

In case of hardware-related programming or in case we obtained a chunk of heap allocated memory, there will be cases where a fixed address in memory have no "effective type" and then we may ignore strict aliasing at least until we start using that memory. So if we know what's stored at a fixed address we may set a pointer there and access it. Within reason - there are rules for what we may do with pointers too.

_Pointer arithmetic is restricted_  
Previously I mentioned the case where we may expect any object in C byte by byte using a character pointer. That rule assumes that we treat that object like a character array of exactly `sizeof(the_object)` bytes. Because in C we are _never_ allowed to do pointer arithmetic beyond the end of an allocated array. Tough luck - specially in embedded systems.

Pointer arithmetic as well as array indexing both boil down to the additive `+` operator: `ptr++` boils down to `ptr = ptr + 1`, and `ptr[i]` boils down to `*(ptr + i)`. We can read about the `+` operator when applied to pointers in a long and complex section of the standard 6.5.6. I will quote parts of it:

> For the purposes of these operators, a pointer to an object that is not an element of an array behaves
the same as a pointer to the first element of an array of length one with the type of the object as its
element type.

That is, if we have `int x;` then for the purpose of pointer arithmetic that one is equivalent to `int x[1];`.

Then if we do something like `ptr + n` where `ptr` is the pointer operand and we end up with a result:

> If both the pointer operand and the result point
to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. If the result points one past
the last element of the array object, it shall not be used as the operand of a unary * operator that is evaluated.

So if we convert something to a pointer then commence to use any form of pointer arithmetic or indexing, the underlying type there must be an array. Curiously though, if the compiler doesn't actually know what's stored at that address, we probably get away with it, though that's obviously beyond the scope of the C standard. The problem is when the compiler does know what's stored there and we try to access it differently.

For example, a lot of programmers think that C allows you do something like this:

```c
// BAD!
short arr[n];
int* ptr = (int*)arr;
ptr[4] = something;
```

The conversion part is fine, we already noted that converting between two object pointer types like `short*` and `int*` in itself is probably just fine. _Unless_ there is an alignment requirement for `int`, then the conversion fail. Or otherwise we can fail when dereferencing the array using a type with different alignment, in case `short` is aligned at 2 bytes boundaries but `int` requires 4.

Furthermore, we are attempting to access something with effective type `short` as an `int`, which is likely a strict aliasing violation.

Furthermore, we may be going outside the bounds of the original array and in fact since `arr` is not an `int[]` array to begin with, what will happen when we cast and start to do crazy pointer arithmetic is anyone's guess.

So there's some 3-4 ways where that code could go severely wrong even though it passed compilation cleanly. _It doesn't matter_ that we know that `short` is 2 bytes, `int` is 4 bytes and `n` is some large number. C doesn't really promise anything for these kind of wild & crazy pointer stunts, except it does promise to be a nasty show-stopper in a number of cases.

---

And that's all the scenarios I could think about when it comes to _pointer_ casts. There are many other things we can cast, but this answer is already way too long.