-7

One way of implementing a rng in c++ is like so:

void const* rng() noexcept { char c; return &c; }

I decided to try out a constexpr variant of this approach:

template <auto C> constexpr auto c() noexcept { return C; }
consteval void const* rng() noexcept { char c; return &c; }

But zero is always returned. My question is, what sections of the Standard specify that this be so?

EDIT: Changed rng() to consteval so silly asm will not be thrown around anymore.

EDIT2: A variant of the question is possible that compiles with clang:

template <auto C> constexpr auto c() noexcept { return C; }
consteval auto rng1() noexcept { return &c<1>;}
consteval auto rng2() noexcept { return &c<2>;}
user1095108
  • 14,119
  • 9
  • 58
  • 116
  • 11
    A way of implementing rng? That code is UB. Plus, even if it works it is likely to be far from random. – freakish Mar 18 '23 at 21:04
  • 2
    Quite aside from the fact a stack address is likely to have a very limited range of values (and probably the *same* value if called twice in the same function, isn't the whole point of `constexpr` to have something calculated at compile time? Objects won't have addresses at that point, they only get those at run time. That's possibly why you always get zero. – paxdiablo Mar 18 '23 at 21:04
  • @paxdiablo from my experience the rng works well for quick tests and the function may well get inlined. – user1095108 Mar 18 '23 at 21:05
  • @freakish ok, you can cite the Standard and write an answer, if this is so. – user1095108 Mar 18 '23 at 21:06
  • 12
    "One way of implementing a rng in c++ is like so" Whoever told you that is on crack. Don't listen to them. – n. m. could be an AI Mar 18 '23 at 21:08
  • 1
    Your program [doesn't compile with Clang](https://godbolt.org/z/nEsPY3WPY). GCC seems to allow some UB in `constexpr` code, which is probably a bug. – IlCapitano Mar 18 '23 at 21:10
  • @n.m. How about trying it out for yourself and never mind that the rng issue is if no importance as far as the question is concerned. – user1095108 Mar 18 '23 at 21:10
  • @IlCapitano sadly, clang has problems with template , for example when C is a float, so I am not sure clang is right. – user1095108 Mar 18 '23 at 21:12
  • "How about trying it out for yourself" [Sure why not?](https://godbolt.org/z/48Mvr9W7a) Or should I do [this](https://godbolt.org/z/vYe6odMe6) instead? – n. m. could be an AI Mar 18 '23 at 21:12
  • @user1095108 [Here's a slightly different example](https://godbolt.org/z/4W1s5ssEq) without `template`. It has a better error message too. – IlCapitano Mar 18 '23 at 21:13
  • @IlCapitano GCC issues a diagnostic, which is all it is required to do. – n. m. could be an AI Mar 18 '23 at 21:14
  • @n.m. my question is neither about clang, nor gcc, but as of late clang seems to be lagging behind gcc. If there is UB someone can put this in their answer. – user1095108 Mar 18 '23 at 21:16
  • 7
    What's wrong with 0? It was chosen by throwing a fair 20-sided die. – StoryTeller - Unslander Monica Mar 18 '23 at 21:18
  • 1
    *"One way of implementing a rng in c++ is like so"* -- It does not seem all that random to me. Initially, I kept getting zero (for the initial version). I [tweaked it a bit](https://wandbox.org/permlink/ceRXafLq1N2rEnoS) and got non-zero results, but it was the same non-zero result 20 times in a row. Admittedly, this is too small a sample set to conclude that it is not random, but it does not bode well. – JaMiT Mar 18 '23 at 21:37
  • 2
    Of course it is not random. It is basically treating current stack address as the random value. In fact if the OS has [ASLR](https://en.wikipedia.org/wiki/Address_space_layout_randomization) disabled (unlikely but possible) it won't be even random between different program runs. – freakish Mar 18 '23 at 21:55
  • @JaMiT lol, it does well in some cases, particularly when inlined, but this was not my point. The rng trick merely inspired my question. – user1095108 Mar 18 '23 at 22:15
  • 1
    @freakish: you're spot on there but, even *with* ASLR, you'll likely get the same random number if calling multiple times in the *same* run. – paxdiablo Mar 18 '23 at 22:25
  • none of the answers cite the Standard :( – user1095108 Mar 18 '23 at 22:30
  • 1
    Now calling the last snippet a variant of another snippet in this question is like calling brie cheese a variant of portland cement, because both are made of atoms. – n. m. could be an AI Mar 18 '23 at 22:49
  • user1095108, my answer currently cites the relevant parts of the standard. – paxdiablo Mar 19 '23 at 22:17

3 Answers3

2

The code in your wandbox link is ill-formed.

The function rng returns an invalid pointer value (because the storage duration of the local variable it points to ends when the function returns).

An expression resulting in an invalid pointer value can however never be a constant expression (only null pointer values or pointers to or one-past objects with static storage duration are acceptable) and is therefore not permitted as a template argument.

GCC shouldn't accept it. (Although technically producing any diagnostic is sufficient to satisfy standard requirements and it does provide some warning, even if not really related to the reason for it being ill-formed.)


Changing the function from constexpr to consteval doesn't really change anything, except that the function can now never be called (as every invocation of a consteval function must itself be a constant expression).


(Assuming consteval is removed again:)

Whether it is permitted to use an invalid pointer value in any way, e.g. to convert it to an integer and use that as a "random" value is implementation-defined. What the semantics of this would be is implementation-defined. It is not defined by the standard what the resulting value should be. The implementation may also prohibit any use of an invalid pointer value, including e.g. copying. So you can't expect it to be "random" in any sense except if your compiler's definition of the implementation-defined behavior gives you reason to expect it. (But in practice compilers often lack descriptions of their implementation-defined behavior.)

However, dereferencing the invalid pointer value would always have undefined behavior (not implementation-defined). The compiler does not have to follow any rules when compiling such code.

Therefore the function can't be helpful in implementing a RNG.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • There is no dereferencing happening in the linked code, the output is of the pointer value itself, not what it points to. – paxdiablo Mar 18 '23 at 21:41
  • @paxdiablo Yes correct. I got confused by the function calls. Fixed. – user17732522 Mar 18 '23 at 21:43
  • @paxdiablo sure, this is not UB in such case (why not return an int type in such case, btw?), but you can't possibly assume anything about randomness of such thing. – freakish Mar 18 '23 at 21:44
  • @freakish: I made no comment here on its usefulness, in fact I stated how useless it would be in my own answer :-) This comment was only to clear up that the was no dereference taking place. We need to separate the legality of the code from the usefulness. – paxdiablo Mar 18 '23 at 21:46
1

The entire point of constexpr is so that it can be evaluated at compile time. Since variables aren't actually instantiated at that point, they won't have an address.

That's aside from the point that this type of RNG, when done in a way that the addresses can be calculated at run-time, will likely have a very limited range, even probably even the same value when calling multiple times from the same function.


For the language lawyers, [basic.stc] (C++20 in this case) has this to say (my emphasis):

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

That means any use of the rng() return value &c (other than dereferencing or deallocation) is subject to the whims of the implementation, since the lifetime of c is over.

Interestingly, while gcc documents a few implementation-defined items, this does not appear to be one of them (clang isn't any better, by the way). This is despite the fact it shows up in the standard as a specific item, any use of an invalid pointer other than to perform indirection or deallocate, in the "Index of implementation-defined behavior".


It's interesting that a slight modification to code will result in different code being generated by gcc:

void const * rng() noexcept { char c; return &c; }
// rng():
//    push  rbp                       ; Stack set-up.
//    mov   rbp, rsp
//
//    mov   eax, 0                    ; Return zero, always.
//
//    pop   rbp                       ; Stack tear-down.
//    ret

void const * rng() noexcept { char c; char *x = &c; return x; }
// rng():
//    push  rbp                       ; Stack set-up.
//    mov   rbp, rsp
//
//    lea   rax, [rbp-9]              ; Get address of c.
//    mov   QWORD PTR [rbp-8], rax    ; Put into x.
//    mov   rax, QWORD PTR [rbp-8]    ; Return x.

//    pop   rbp                       ; Stack tear-down.
//    ret

In the second definition, you don't get the warning about returning the address of a local variable, and you do get a non-zero value when run.

Unfortunately, that's only anecdotal evidence of what gcc does, so cannot be guaranteed. You also get the same value every time you call the function from the same stack "level" in a single program run.

You'll also notice there the absence of constexpr above. Adding that makes no difference to the outcome other than the fact you seem to need to initialise c to some value. In other words, the trick that prompted this question doesn't always work even without using constexpr (another reason to not rely on it).


The clang compiler appears to handle the case without the temporary pointer (but with the same non-random results, of course):

void const * rng() noexcept { char c; return &c; }
// rng():
//    push    rbp                     ; Stack set-up.
//    mov     rbp, rsp

//    lea     rax, [rbp - 1]          ; Return address.

//    pop     rbp                     ; Stack tear-down.
//    ret

Bottom line: though an implementation should document it, it is still free to do whatever it wants. That is, after all, what "implementation-defined" means. It's just that the "defined" bit is missing :-)

That includes gcc returning zero in the case where you attempt to return the address directly, and a non-zero value if you first put it into a pointer variable.

Re your comment:

I am not really looking for a rng, just got the idea for this question from the trick.

Even if you remove all my comments about the usefulness of this trick for random number generation, the fact that it doesn't work (and is not guaranteed to work) in all situations makes it non-portable and therefore potentially incorrect code.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • ok, but I can also return the address of the function `rng()` itself. – user1095108 Mar 18 '23 at 22:00
  • @user1095108 what makes you think that address has anything to do with randomness? – freakish Mar 18 '23 at 22:01
  • @freakish my experiments – user1095108 Mar 18 '23 at 22:04
  • @user1095108, even if you could return the address of the function (I haven't checked but it may be it's not fully defined until the closing brace of the body), that would likely be the same value each time. I have to ask: why not just use the real random functions in a non-const way? Or, if you're happy with const values, just return `42`? – paxdiablo Mar 18 '23 at 22:06
  • @user1095108 yeah, rng returning the same value each time sounds like a good implementation. Btw, if you are looking for entropy source (which sounds more reasonable in this context), then read this: https://en.wikipedia.org/wiki/Entropy_(computing) and use /dev/random on linux. That's literally what it was designed for. Then you can plug that value to any fast prng like linear congruential generator. – freakish Mar 18 '23 at 22:07
  • @paxdiablo I am not really looking for a rng, just got the idea for this question from the trick. – user1095108 Mar 18 '23 at 22:09
  • @user1095108 there's a reason why people use well designed mechanisms like /dev/random instead of those "tricks". And the reason is that those "tricks" either don't work at all or are unreliable. – freakish Mar 18 '23 at 22:11
  • @freakish it's a language-lawyer question, not a practical question. – user1095108 Mar 18 '23 at 22:12
  • @paxdiablo I appreciate the asm, but it wasn't (as is probably clear) what I was looking for :) – user1095108 Mar 18 '23 at 23:51
  • @user1095108, the asm was just bonus showing what was happening under the covers. The meat of the answer was the reason behind constexpr and the quotes from iso c20, explaing why an implementation is free to do it. – paxdiablo Mar 19 '23 at 03:04
0

The rng function returns an invalid pointer value. Dereferencing or deallocating an invalid pointer value has undefined behaviour, while other uses have implementation-defined behaviour.

Thus it is unwise to expect that rng returns anything in particular, unless the documentation for your implementation clearly defines what other uses of an invalid pointer value do.

Neither g++ nor clang++ seem to provide definitions for their implementation-defined behaviour, which any conforming implementation should. A hypothetical conforming implementation may define accessing an invalid pointer value to be the same as accessing a null pointer value, for example. Or it may define accessing an invalid pointer value to be a run-time error. Or anything else it pleases.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243