c++ constexpr rng()始终返回零

qzwqbdag  于 2023-03-20  发布在  其他
关注(0)|答案(3)|浏览(132)

在c++中实现rng的一种方法是这样的:

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

我决定尝试这种方法的constexpr变体:

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

但是零总是returned,我的问题是,标准的哪些章节规定了这一点?
编辑:将rng()更改为consteval,这样愚蠢的asm就不会再到处乱扔了。
EDIT2:可以使用clang编译问题的变体:

template <auto C> constexpr auto c() noexcept { return C; }
consteval auto rng1() noexcept { return &c<1>;}
consteval auto rng2() noexcept { return &c<2>;}
pftdvrlh

pftdvrlh1#

您的wandbox链接中的代码格式错误。
函数rng返回一个无效的指针值(因为它所指向的局部变量的存储持续时间在函数返回时结束)。
但是,导致无效指针值的表达式绝不能是常量表达式(只接受空指针值、指向或具有静态存储持续时间的一次过去对象的指针),因此不允许作为模板参数。
GCC不应该接受它。(尽管从技术上讲,生成任何诊断信息都足以满足标准需求,并且它确实提供了一些警告,即使与错误格式的原因没有真正联系。)
将函数从constexpr更改为consteval实际上并没有改变任何东西,只是该函数现在 * 永远 * 不能被调用(因为consteval函数的每次调用本身必须是常量表达式)。
(假设再次删除consteval:)
是否允许以任何方式使用无效指针值,例如将其转换为整数并将其用作“随机”值,这是由实现定义的。其语义是由实现定义的。标准没有定义结果值应该是什么。实现还可能禁止 * 任何 * 使用无效指针值,所以你不能期望它在任何意义上是“随机的”,除非你的编译器对实现定义的行为的定义使你有理由期望它(但是在实践中编译器经常缺乏对它们的实现定义的行为的描述)。
然而,解引用无效指针值 * 总是 * 具有未定义的行为(不是实现定义的)。编译器在编译这样的代码时不必遵循任何规则。
因此,该函数对实现RNG没有帮助。

h5qlskok

h5qlskok2#

constexpr的全部意义在于它可以在编译时被求值,因为变量实际上并没有在编译时被示例化,所以它们没有地址。
这是 * 除了 * 这一点,这种类型的RNG,当以一种可以在运行时计算地址的方式完成时,可能会有一个非常有限的范围,甚至可能在从同一个函数多次调用时有相同的值。
对于语言律师来说,[basic.stc](在本例中是C++20)有这样的话要说(我强调了):
当达到存储区域的持续时间结束时,表示该存储区域任何部分地址的所有指针值变为无效指针值(6.7.2)。通过无效指针值的间接访问和将无效指针值传递给解除分配函数具有未定义的行为。无效指针值的任何其他使用具有实现定义的行为。
这意味着对rng()返回值&c的任何使用(除解引用或解分配之外)都要服从于实现的突发奇想,因为c的生命周期已经结束。
有趣的是,虽然gcc记录了一些实现定义的项,但这似乎不是其中之一(顺便说一下,clang也没有更好),尽管事实上它在标准中作为一个特定项any use of an invalid pointer other than to perform indirection or deallocate出现在“实现定义的行为索引”中。
有趣的是,对代码的 * 轻微 * 修改将导致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

在第二个定义中,您不会得到关于返回局部变量地址的警告,并且在运行时会得到一个非零值。
不幸的是,这只是gcc的轶事证据,所以不能保证。在一次程序运行中,每次从相同的堆栈“级别”调用函数时,都会得到相同的值。
你也会注意到上面没有constexpr,除了你需要初始化c到某个值之外,添加它对结果没有影响,换句话说,提示这个问题的技巧并不总是有效,即使 * 没有 * 使用constexpr(另一个不依赖它的原因)。
clang编译器似乎可以在没有临时指针的情况下处理这种情况(当然,也会产生相同的非随机结果):

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

底线:尽管实现 * 应该 * 记录它,但它仍然可以自由地做它想做的任何事情。这毕竟是“实现定义”的意思。只是缺少了“定义”位:-)
这 * 包括 * gcc在您试图直接返回地址的情况下返回零,以及在您首先将其放入指针变量时返回非零值。
回复您的评论:
我不是真的在找一个rng,只是从窍门中得到了这个问题的想法。
即使您删除了我对这个技巧在随机数生成方面的有用性的所有评论,但它并不适用(也不能保证适用)于所有情况的事实使它不可移植,因此可能是不正确的代码。

7rtdyuoh

7rtdyuoh3#

rng函数返回无效指针值。取消引用或释放无效指针值具有未定义的行为,而其他使用具有实现定义的行为。
因此,期望rng返回任何特定的值是不明智的,除非您的实现文档明确定义了无效指针值的其他用途。
g和clang似乎都没有为它们的实现定义的行为提供定义,而任何符合的实现都应该提供定义。例如,一个假设的符合的实现可以定义访问一个无效的指针值与访问一个空指针值是一样的。或者它可以定义访问一个无效的指针值是一个运行时错误。或者任何它喜欢的东西。

相关问题