5

The following works:

void* p = reinterpret_cast<void*>(0x1000);

But looks 'incorrect/unsafe' eg. 0x1000 is an int and not even uintptr_t, I could fix this, but is there a better/safer method of casting ?

darune
  • 10,480
  • 2
  • 24
  • 62
  • 1
    why is p `void*` and not e.g. `char*` ? what do you plan to to with p? – nivpeled Oct 23 '19 at 07:50
  • Usually you intialize a pointer with the address of something or `nullptr`, not with some arbitrary value. – Lukas-T Oct 23 '19 at 07:52
  • what you describe and the fact that you use `reinterpret_cast` are only the symptoms, the actual problem is that you need such a cast. Maybe you do, but you should think twice – 463035818_is_not_an_ai Oct 23 '19 at 07:52
  • Im using for a sort of address search with VirtualAlloc https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc – darune Oct 23 '19 at 07:55
  • @Someprogrammerdude Im using for a sort of address search with VirtualAlloc https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc – darune Oct 23 '19 at 07:57
  • @formerlyknownas_463035818 Im using for a sort of address search with VirtualAlloc https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc – darune Oct 23 '19 at 07:58
  • @nivpeled Im using for a sort of address search with VirtualAlloc https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualalloc – darune Oct 23 '19 at 07:58
  • Yes, this is 'low level' but even more so would like as much safety as possible – darune Oct 23 '19 at 08:00
  • 1
    An underlying problem may be, e.g, related to pointer tagging. For instance, setting a tag in lsb for a null pointer basically implies its intergral value 1. – Daniel Langr Oct 23 '19 at 09:46
  • Why do you need a pointer to a specific numerical address? According to [examples](https://learn.microsoft.com/en-us/windows/win32/memory/reserving-and-committing-memory) online you call the function first with a null pointer and later, using different options, with actual pointer values. If you want to use it as MEM_COMMIT for the flAllocationType parameter you should simply use a DWORD number. – Peter - Reinstate Monica Oct 23 '19 at 10:31
  • What is incorrect/unsafe about this? It is implementation defined, with a supporting remark "It is intended to be unsurprising to those who know the addressing structure of the underlying machine.". – geza Oct 23 '19 at 10:39
  • @geza Iwonder what the "typical addressing structure of the underlying machine" on which a Win32 system call is performed may be ;-). – Peter - Reinstate Monica Oct 23 '19 at 10:46
  • @PeterA.Schneider this is for a sort of searching - you can supply with your own chosen address and then try to allocate with that. – darune Oct 23 '19 at 10:46
  • @darune So you try to call VirtualAlloc with different address values until the function succeeds? From reading the [documentation](https://learn.microsoft.com/en-us/windows/win32/memory/reserving-and-committing-memory) (even though I have exactly zero Win32 programming experience) I suppose that won't work: "First, VirtualAlloc is called to reserve a block of pages with **NULL** specified as the base address parameter, forcing the system to determine the location of the block. Later, VirtualAlloc is called whenever it is necessary to commit a page **from this reserved region**..." – Peter - Reinstate Monica Oct 23 '19 at 10:51
  • @PeterA.Schneider I am aware that this is not the usual way of using the function. It may surprise you, but you can actually use it in this way. In the same way I could use VirtualQuery to get information about the memory state at some arbitrary address. – darune Oct 23 '19 at 10:57
  • You may want to reconsider your "correct solution" check mark. The answer was incorrect before it was edited and is just noise now without adding much benefit. – Peter - Reinstate Monica Oct 23 '19 at 12:06

3 Answers3

5

0x1000 is an int and not even uintptr_t, I could fix this, but is there a better/safer method of casting

0x1000 is int, and in reinterpret_cast<void*>(0x1000) the compiler emits a sign extension instruction (sign is 0 here) or a plain register load instruction with an immediate operand to make the value as wide as void*.

For many reasons the compiler cannot possibly know whether 0x1000 represents a valid address, so it has to comply and assume that it is a valid address.

Casting integers representing addresses to pointers with reinterpret_cast is currently the existing practice.

darune
  • 10,480
  • 2
  • 24
  • 62
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
1

You can create a pointer from an integer with reinterpret_cast.

However a pointer that does not point to to an existing object (or 1 past the last element in an array with an object being considered the sole element in an imaginary array for this purpose, or is the null pointer) has an invalid pointer value. Dereferencing is UB and other operations with invalid pointer values are implementation specific so you need to make sure your compiler allows the operations you do with these pointers.

void* p = reinterpret_cast<void*>(0x1000); // invalid pointer,
                                          // operations on it are implementation defined

§6.7.2 Compound types [basic.compound]

  1. [...] Every value of pointer type is one of the following:

    3.1 — a pointer to an object or function (the pointer is said to point to the object or function), or

    3.2 — a pointer past the end of an object (8.5.6), or

    3.3 — the null pointer value (7.11) for that type, or

    3.4 — an invalid pointer value

§8.5.1.10 Reinterpret cast [expr.reinterpret.cast]

  1. A pointer can be explicitly converted to any integral type large enough to hold it. The mapping function is implementation-defined. [...]
  2. 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.

You are allowed to convert from integer to pointer, but if the resulting pointer value doesn't point to an existing object (or one past an object) the resulting pointer has an invalid value.

Now regarding what you can do with invalid pointers:

§6.6.4 Storage duration [basic.stc]

  1. [...] Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior 35

35) Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault.


This post was heavily edited because it was wrong in its first iterations. Warm thanks to the community for correcting and challenging me.

Marc.2377
  • 7,807
  • 7
  • 51
  • 95
bolov
  • 72,283
  • 15
  • 145
  • 224
  • 2
    "A value of integral type or enumeration type can be explicitly converted to a pointer.". This seems to contradict with your second example. – geza Oct 23 '19 at 11:19
  • I think this is plain wrong. Correct me if you can with a standard quote. (What you indeed cannot do is *dereference* an invalid pointer.) – Peter - Reinstate Monica Oct 23 '19 at 11:37
  • I think that you are wrong. Don't mix "invalid program" with programs containing invalid pointer value. It is completely fine that a pointer has invalid pointer value, it is not UB. Accessing a pointer whose value is invalid pointer value is implementation-defined. – geza Oct 23 '19 at 12:00
  • @geza yes, I am wrong here. You are allowed to create invalid pointers. I edited. – bolov Oct 23 '19 at 12:02
  • @geza Exactly. What does "invalid pointer" mean? Par. 6.7. Storage duration [basic.stc] in the draft gives a hint: "When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values (6.9.2). **Indirection** through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has **implementation-defined** behavior." Ah, I see that bolov found that too. – Peter - Reinstate Monica Oct 23 '19 at 12:02
  • Now, your first example is wrong :) It is UB to add 2 to that pointer, as it is not pointing to an array. – geza Oct 23 '19 at 12:04
  • qgeza Re "add 2 to a ptr" is UB -- is it? I think it is at most implementation defined -- after all, the address may be considered "invalid", even though I'd try to defend it -- unless dereferenced. – Peter - Reinstate Monica Oct 23 '19 at 12:09
  • @geza Surprising. Probably a defect in the draft ;-). (Only half joking, given that you could convert the original address to an integer, add two and convert it back with *implementation defined* results (where IB of course includes a trap). – Peter - Reinstate Monica Oct 23 '19 at 12:23
  • @geza I knew it's UB to form a pointer outside of an array. What I didn't know was that it was about pointer arithmetic. Didn't know you can do it with integer conversions. – bolov Oct 23 '19 at 12:24
  • @PeterA.Schneider: yes, an implementation may choose that it supports out of bounds pointer arithmetic this way. But note, that an implementation may choose to define the meaning of `pointer+index` (where index is out of bounds) as well. UB doesn't mean that it must be kept undefined. It is just the standard doesn't define it. But an implementation may give some definition. About being a defect: as far as I know, this rule "always" existed. It is in C89 as well. – geza Oct 23 '19 at 12:45
1

tl;dr : You probably just shouldn't do this.

The following ... looks 'incorrect/unsafe' ... is there a better/safer method of casting ?

As another answer pointed out, this is "incorrect" to the extent of having implementation-defined behavior. Also, you're using a magic number.

But the issue is not the casting, I believe. I really kind of doubt you need to initialize a void* variable with literal address. Why?

  • If there's some kind of typed value at that address, then don't use void *, but rather a typed pointer. Even if you later want to pass that pointer to memset() or memcpy() or even some callback function which takes void * - delay the type erasure.

  • Where do you get that number from? Surely you know magic numbers are bad, right? Well, at the very least use something like

      constexpr const uintptr_t sound_card_memory_mapped_buffer_address { 0x1000 };
    

    this takes care of one of your concerns (not uintptr_t), and is also clearer to read, even if you stay with a void *:

      void* p = reinterpret_cast<void*>(sound_card_memory_mapped_buffer_address);
    
  • p is a poor choice of name for a variable. Rename it according to its use.

  • Do you really need to initialize p at all? :

    • If you're not using it at the moment, why even initialize it? Try just not declaring it until it's about to be used (if at all).

    • If you're about to use it, why even declare it? Try:

          do_something_with(sound_card_memory_mapped_buffer_address);
      

      and no p in sight.

Obviously I have more questions than answers for you, since you only provided us a one-liner.

Community
  • 1
  • 1
einpoklum
  • 118,144
  • 57
  • 340
  • 684