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.

Casting a non-`void` pointer to `uintptr_t`

+5
−0

I came across the following code snippet on the SEI CERT C Coding Standard wiki:

Compliant Solution

Any valid pointer to void can be converted to intptr_t or uintptr_t and back with no change in value. (See INT36-EX2.) The C Standard guarantees that a pointer to void may be converted to or from a pointer to any object type and back again and that the result must compare equal to the original pointer. Consequently, converting directly from a char * pointer to a uintptr_t, as in this compliant solution, is allowed on implementations that support the uintptr_t type.

#include <stdint.h>
  
void f(void) {
 char *ptr;
 /* ... */
 uintptr_t number = (uintptr_t)ptr; 
 /* ... */
}

The first two sentences are referring to N3220 §7.22.1.4(1) and §6.3.2.3(1), respectively (reproduced below for reference). However, I am not sure if these clauses permit us to cast a non-void pointer directly to uintptr_t as the third sentence seems to suggest.

My understanding is that §7.22.1.4(1) and §6.3.2.3(1) only apply to (uintptr_t)(void *)ptr. Once we drop the (void *), we end up with (uintptr_t)ptr. §7.22.1.4(1) and §6.3.2.3(1) no longer apply and we have to fall back on §6.3.2.3(6), which says that the result of the cast is implementation-defined. There does not seem to be any guarantee that this result is the same as that of (uintptr_t)(void *)ptr. Nor is there any guarantee that (char *)(uintptr_t)ptr recovers the original pointer, since §6.3.2.3(6) does not make any roundtrip guarantees like those of §7.22.1.4(1) and §6.3.2.3(1).

So I don't think (uintptr_t)ptr can be said to be equivalent to (uintptr_t)(void *)ptr and I wonder if it is really safe to omit the intermediate cast to void *.


References:

N3220 §7.22.1.4(1):

  1. The following type designates a signed integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:

    intptr_t

    The following type designates an unsigned integer type with the property that any valid pointer to void can be converted to this type, then converted back to pointer to void, and the result will compare equal to the original pointer:

    uintptr_t

N3220 §6.3.2.3(1):

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

N3220 §6.3.2.3(6):

  1. 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.
History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

1 answer

+3
−0

If reading the standard strictly by the letter then you are correct. And therefore both CERT and MISRA are picky with these kind of conversions because they strive to cover all poorly-defined behavior.

However, I think this is one of the cases when one can assume a sensible compiler implementation. And the MISRA rule forbidding conversions like these is on my permanent deviation list, because it is simply not practical when doing hardware-related programming.

In order for a void* to be convertible from any other object pointer and back, the only sensible implementation in practice is for all object pointers to have the same size and presentation. It's not mandated by the C standard that they are, but to solve the conversion requirement in any other way would be strange and cumbersome.

Therefore it is fairly safe to assume that we can convert directly to/from uintptr_t to any object pointer type. The main reason uintptr_t exists is because of situations when the address bus is of a different size than the CPU word size and the code needs to be portable.

The reason why other integer to/from pointer types is implementation-defined is because of several reasons: pointer size as mentioned above, but also alignment and hardware limitations. An integer can hold any value - not necessarily corresponding to an aligned address of the pointer type we covert it to. Furthermore there are of course a lot of hardware restrictions requiring that the address must be one where an object is allowed to be stored.

Also the wording in the standard is "An integer may be converted to any pointer type" meaning both object pointers and function pointers.

The void* can be allowed to be more lenient than that, because it has the same alignment (none) as a character type (ISO C23 6.25), it is always an object pointer and it is never directly used for de-referencing an actual object.

C has never made an explicit guarantee that various pointer types have the same representation or size. However this is one of those cases when they thought they made the language more portable with a lenient wording, yet on targets where it actually matters, every compiler I know of has not taken advantage of this. Rather, on system with extended addressing, non-standard extensions near/far are used as pointer qualifiers to state if the pointer is in the normal or extended address space. So the flexibility made possible by vague wording in ISO C23 6.3.3.3 (previously 6.3.2.3) isn't actually used in practice.

So while not required by the C standard, in practice we can very likely convert directly to/from uintptr_t and everyone is using the type like that too. I use it for function pointers too - out of all integer types, it is the best and most portable one. I also have yet to encounter a (post C99) compiler which does not implement it.

History
Why does this post require attention from curators or moderators?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »