Casting type void to uint8_t

is it? ??

Yes. From the standard [expr.reinterpret.cast]:

  1. Conversions that can be performed explicitly using reinterpret_cast are listed below. No other conversion can be performed explicitly using reinterpret_cast.
  2. [...]
  3. [Note: The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value. — end note]
  4. A pointer can be explicitly converted to any integral type large enough to hold all values of its type. The mapping function is implementation-defined. [Note: It is intended to be unsurprising to those who know the addressing structure of the underlying machine. — end note] A value of type std::nullptr_t can be converted to an integral type; the conversion has the same meaning and validity as a conversion of (void*)0 to the integral type.
  5. A value of integral type or enumeration type can be explicitly converted to a pointer. A pointer converted to an integer of sufficient size (if any such exists on the implementation) and back to the same pointer type will have its original value; mappings between pointers and integers are otherwise implementation-defined.

Ignoring the standard, I explained why it would be a bug in practice as well in reply #17: you either get an integer that cannot store the original pointer, or you get back an integer that might be different than the one you put in. Both cases are equally useless.

well then it's not --

i don't understand what you're trying to explain.

Seems so but that if you are casting to int type, if you are casting to intptr type there is no problem

The standard doesn't allow you to write (char)some_pointer if sizeof(char) < sizeof(void*).
If you do write such an expression, then that's a bug (it will not even compile).

If you cast your way around this issue by doing something like (char)(intptr_t)some_pointer, then it will compile, but the result will be useless: you cannot make any assumptions about its value, and you cannot turn it back into a pointer. Hence, it's again a bug, even though it might compile, the results are meaningless.

meaningless ? it's just about copying bits from a sequence of bytes at one memory location in memory to another.

if i do this, test it and it works for me, why would it be meaningless? aren't all cast effectively telling the compiler "i know what i'm doing"?

Why bug? What if the point of this conversion is exactly to truncate pointer int value?

Yes, meaningless. Please see my previous posts:

A pointer converted to an integer of sufficient size and back to the same pointer type is guaranteed to have its original value, otherwise the resulting pointer cannot be dereferenced safely (the round-trip conversion in the opposite direction is not guaranteed [...])

If you cast an int → pointer → int, you might get back a different integer.

In other words, this might fail:

intptr_t i = 123456;
void *p    = (void *)i;
intptr_t j = (intptr_t)p;
assert(i == j); // this can fail

This is incorrect, it's not just copying bits. It performs a conversion, the value of the pointer does not necessarily have the same binary representation as the integer.

The mapping performed by reinterpret_cast might, or might not, produce a representation different from the original value.

Or in other words, this might fail:

intptr_t i = 123456;
void *p    = (void *)i;
assert(memcmp(&i, &p, sizeof(i)) == 0); // this can fail as well

i don't understand what this means.

does "might fail" mean for a specific program it can fail one time but not another.

this may be over extending what i'm suggesting
and bear in mind, i'm more specifically saying i can use fewer bits, not more bits

As a side note, not even NULL has to be represented as all zero bits: pointers - Is NULL always zero in C? - Stack Overflow

assert(nullptr == 0);                     // always passes
assert(bit_cast<intptr_t>(nullptr) == 0); // might fail

Even if the pointer actually points to something (i.e. is the result of e.g. the address-of operator, and not of some int to pointer cast), then the result of a cast to a smaller signed integer type would still be implementation defined because there's no guarantee that the integer value of the pointer can be represented as the smaller integer type, so it might overflow.

If the smaller integer type is unsigned, then I think the result would be valid (i.e. well-defined modulo 2n without undefined or implementation defined conversions). But I think it's safe to assume that this is not what the user intended in 99% of cases.

I wouldn’t assume anything and sure unsigned char and uintptr_t should suffice

If you were to look at the bits of a pointer stored in a register or in memory, you might see a different value than the one you get when you print the same pointer casted to an integer.

See the link about NULL in my previous post: if you have a NULL pointer and you look at it in a debugger, you might not see all zero bits in its representation (this is true in both C and C++).

Both.

It can depend on the compiler/platform, but also on the value.
For example, on 64-bit systems, the address space is too large to use all 64 bits, so on some architectures, the most significant bits of a pointer might be used for other purposes than just the memory location. E.g. on ARM, you have the Memory Tagging Extension, which uses the top 8 bits of pointers to detect memory safety violations.
Pointer arithmetic in C might only operate on the 56 least significant bits, using e.g. the specialized SUBP(S) instructions.
Who knows what will happen if you try to convert an integer of >56 bits to a pointer (and back).

I'm not saying that it cannot work in your specific test case on your specific machine, I'm saying that you cannot assume that this holds for all systems.

You can replace it by intptr_t i = 123;, the number is arbitrary. There's simply no guarantee made by the standard about these conversions, even if you use fewer bits than (CHAR_BIT * sizeof(void *)).

this i understand, because NULL may refer to a value or a pointer and if it's a pointer, it needs to be to an address that has a zero, usually maintained by the compiler

this i don't understand. doesn't the cast say interpret the symbol as described by the cast. i can't say anything about how printf formats a ptr value.

ok. so you need to understand what a ptr can be. i think for most embedded processors, it's most likely just the address.

i think you're more concerned with writing a library that can run on 64-bit machine such as the one you describe above or a simple 8-bit processor. i can understand that, C++ is of course used for many non-embedded very large applications where the issues you describe are real, where the "guard rails" kernighan mentions are helpful.

working with integer DSPs had many such restrictions, but you're writing at the assembly level (non-mnemonic)

i'm not convinced it's necessary to live with those restrictions on something like an Arduino.

in other words, casts can override those restriction and i think you're saying, within limits, or at least avoid using the ones that require additional processing

I'm not sure what you mean here. This might help clear things up: https://c-faq.com/null/varieties.html

No, it doesn't interpret the bit pattern of the pointer, it maps a pointer to a unique integer value (i.e., the bits used to represent it might change).
Sure, it might be the identity mapping, keeping the bits the same, but the standard explicitly states that there is no such guarantee.

I don't think it's a restriction, working within what's allowed by the standard is a liberation: you get peace of mind and it prevents nasty surprises, with portability as a bonus.

There are ways to solve this problem that do not violate the standard, so why would you not use them?

No, no, it has nothing to do with additional processing, all that happens under the hood.
The problem is that casts don't override restrictions, they silence all warnings about them. Just because the compiler no longer complains doesn't mean that the code is correct or does what you expect it to. Silently wrong code is possibly the worst kind of code.

I don't understand arguing so vociferously in favor of using a pointer to pass anything other than a pointer when that's what the receiving function clearly wants (i.e. 'void *').

If you want to pass say a char, pass a pointer to a char variable that contains the desired value. In the receiving function, cast the 'void *' to a 'char *' (or perhaps 'const char *'). Dereference that pointer to get the value.

1 Like

Exactly - the OP’s issue was passing a tentative pointer to a #defined value so there was no real variable to point at.

Everything else is just a long digression on what not to do with void*

so this is one of those threads where i hope to learn something. my experience is mostly with C. K&R was out when i started my career (85), but C++ was just translated into C.

you have to wonder why "void*" is even allowed. as mentioned, if the functions is just going to cast the ptr to a char*, why is the argument void* instead of char*

while C has limitations, it allows the knowledgeable programmer ways to work around them, C++ is more strict with language features for doing those things (e.g. function overloading)

"named" data is one example i can think of. a packet of data is received of unknown format, different messages have different sizes and formats. so a generic pointer is used. it allows access into the packet, the message type is discerned from the header and the ptr re-cast to the message format or used to cast the msg body to the msg format.

another example is a menu system where each menu item includes a set of function ptrs for each button and the menu item may have a ptr to another menu item or data it operates on. those functions operate on data ptr, hence the button function is defined as void()(void)

so is the use of void* just for backward compatibility with C or is there a specific problem (e.g. above) it solves? i left school being taught "goto" was bad. it took me a while to learn what problem it solved

If you are making a callback function but don’t know what type of argument user will pass to it, void pointer is universal and is fully supported by standard. you are allowed to cast any pointer to void pointer and then back

thanks, good answer