简体   繁体   English

如何确保在运行时永远不会调用 constexpr 函数?

[英]How to ensure constexpr function never called at runtime?

Lets say that you have a function which generates some security token for your application, such as some hash salt, or maybe a symetric or asymetric key.假设您有一个函数可以为您的应用程序生成一些安全令牌,例如一些哈希盐,或者可能是对称或非对称密钥。

Now lets say that you have this function in your C++ as a constexpr and that you generate keys for your build based on some information (like, the build number, a timestamp, something else).现在假设您在 C++ 中有这个函数作为 constexpr,并且您根据一些信息(例如,内部版本号、时间戳、其他信息)为您的版本生成密钥。

You being a diligent programmer make sure and call this in the appropriate ways to ensure it's only called at compile time, and thus the dead stripper removes the code from the final executable.您是一名勤奋的程序员,确保并以适当的方式调用它以确保它只在编译时调用,因此死剥离器从最终可执行文件中删除代码。

However, you can't ever be sure that someone else isn't going to call it in an unsafe way, or that maybe the compiler won't strip the function out, and then your security token algorithm will become public knowledge, making it more easy for would be attackers to guess future tokens.但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将成为公共知识,使其成为攻击者更容易猜测未来的代币。

Or, security aside, let's say the function takes a long time to execute and you want to make sure it never happens during runtime and causes a bad user experience for your end users.或者,抛开安全性不谈,假设该函数需要很长时间才能执行,并且您希望确保它在运行时永远不会发生并给您的最终用户带来糟糕的用户体验。

Are there any ways to ensure that a constexpr function can never be called at runtime?有什么方法可以确保在运行时永远不会调用 constexpr 函数? Or alternately, throwing an assert or similar at runtime would be ok, but not as ideal obviously as a compile error would be.或者,在运行时抛出断言或类似的东西也可以,但显然不如编译错误那么理想。

I've heard that there is some way involving throwing an exception type that doesn't exist, so that if the constexpr function is not deadstripped out, you'll get a linker error, but have heard that this only works on some compilers.我听说有一些方法涉及抛出不存在的异常类型,因此如果 constexpr 函数没有被删除,您将收到链接器错误,但听说这仅适用于某些编译器。

Distantly related question: Force constexpr to be evaluated at compile time相关问题: 强制在编译时评估 constexpr

You can force the use of it in a constant expression:您可以强制在常量表达式中使用它:

#include<utility>

template<typename T, T V>
constexpr auto ct() { return V; }

template<typename T>
constexpr auto func() {
    return ct<decltype(std::declval<T>().value()), T{}.value()>();
}

template<typename T>
struct S {
    constexpr S() {}
    constexpr T value() { return T{}; }
};

template<typename T>
struct U {
    U() {}
    T value() { return T{}; }
};

int main() {
    func<S<int>>();
    // won't work
    //func<U<int>>();
}

By using the result of the function as a template argument, you got an error if it can't be solved at compile-time.通过将函数的结果用作模板参数,如果在编译时无法解决,则会出现错误。

A theoretical solution (as templates should be Turing complete) - don't use constexpr functions and fall back onto the good-old std=c++0x style of computing using exclusively struct template with values .一个理论上的解决方案(因为模板应该是图灵完备的)——不要使用 constexpr 函数,而是使用只使用struct template with values的老式std=c++0x计算风格。 For example, don't do例如,不要做

constexpr uintmax_t fact(uint n) {
  return n>1 ? n*fact(n-1) : (n==1 ? 1 : 0);
}

but

template <uint N> struct fact {
  uintmax_t value=N*fact<N-1>::value;
}
template <> struct fact<1>
  uintmax_t value=1;
}
template <> struct fact<0>
  uintmax_t value=0;
}

The struct approach is guaranteed to be evaluated exclusively at compile time. struct方法保证只在编译时进行评估。

The fact the guys at boost managed to do a compile time parser is a strong signal that, albeit tedious, this approach should be feasible - it's a one-off cost, maybe one can consider it an investment. boost 人员设法进行 编译时解析器的事实是一个强烈的信号,尽管很乏味,但这种方法应该是可行的 - 这是一次性成本,也许可以将其视为一项投资。


For example:例如:

to power struct:给结构供电:

// ***Warning: note the unusual order of (power, base) for the parameters
// *** due to the default val for the base
template <unsigned long exponent, std::uintmax_t base=10>
struct pow_struct
{
private:
  static constexpr uintmax_t at_half_pow=pow_struct<exponent / 2, base>::value;
public:
  static constexpr uintmax_t value=
      at_half_pow*at_half_pow*(exponent % 2 ? base : 1)
  ;
};

// not necessary, but will cut the recursion one step
template <std::uintmax_t base>
struct pow_struct<1, base>
{
  static constexpr uintmax_t value=base;
};


template <std::uintmax_t base>
struct pow_struct<0,base>
{
  static constexpr uintmax_t value=1;
};

The build token构建令牌

template <uint vmajor, uint vminor, uint build>
struct build_token {
  constexpr uintmax_t value=
       vmajor*pow_struct<9>::value 
     + vminor*pow_struct<6>::value 
     + build_number
  ;
}

In the upcoming C++20 there will be consteval specifier .在即将到来的 C++20 中,将会有consteval说明符

consteval - specifies that a function is an immediate function, that is, every call to the function must produce a compile-time constant consteval - 指定一个函数是一个立即函数,也就是说,每次调用该函数都必须产生一个编译时常量

Since now we have C++17, there is an easier solution:因为现在我们有了 C++17,所以有一个更简单的解决方案:

template <auto V>
struct constant {
    constexpr static decltype(V) value = V;
};

The key is that non-type arguments can be declared as auto .关键是非类型参数可以声明为auto If you are using standards before C++17 you may have to use std::integral_constant .如果您使用的是 C++17 之前的标准,则可能必须使用std::integral_constant There is also a proposal about the constant helper class.还有一个关于constant助手类的提案

An example:一个例子:

template <auto V>
struct constant {
    constexpr static decltype(V) value = V;
};

constexpr uint64_t factorial(int n) {
    if (n <= 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

int main() {
    std::cout << "20! = " << constant<factorial(20)>::value << std::endl;
    return 0;
}

Have your function take template parameters instead of arguments and implement your logic in a lambda.让您的函数采用模板参数而不是参数,并在 lambda 中实现您的逻辑。

#include <iostream>

template< uint64_t N >
constexpr uint64_t factorial() {
    // note that we need to pass the lambda to itself to make the recursive call
    auto f = []( uint64_t n, auto& f ) -> uint64_t {
        if ( n < 2 ) return 1;
        return n * f( n - 1, f );
    };
    return f( N, f );
}

using namespace std;

int main() {
    cout << factorial<5>() << std::endl;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM