【问题标题】:How to ensure constexpr function never called at runtime?如何确保在运行时永远不会调用 constexpr 函数?
【发布时间】:2017-01-31 16:27:10
【问题描述】:

假设您有一个函数可以为您的应用程序生成一些安全令牌,例如一些哈希盐,或者可能是对称或非对称密钥。

现在假设您在 C++ 中将此函数作为 constexpr 使用,并且您根据某些信息(如内部版本号、时间戳等)为构建生成密钥。

作为一个勤奋的程序员,请确保并以适当的方式调用它,以确保它只在编译时被调用,因此死剥离器会从最终的可执行文件中删除代码。

但是,您永远无法确定其他人不会以不安全的方式调用它,或者编译器可能不会删除该函数,然后您的安全令牌算法将成为公众知识,使攻击者更容易猜测未来的令牌。

或者,抛开安全性不谈,假设该函数需要很长时间才能执行,并且您希望确保它在运行时不会发生,并且不会给最终用户带来糟糕的用户体验。

有什么方法可以确保永远不会在运行时调用 constexpr 函数?或者,在运行时抛出一个断言或类似的东西是可以的,但显然不如编译错误那么理想。

我听说有一些方法涉及抛出一个不存在的异常类型,所以如果 constexpr 函数没有被删除,你会得到一个链接器错误,但听说这只适用于一些编译器。

相关问题:Force constexpr to be evaluated at compile time

【问题讨论】:

  • 一种可能的解决方案:您严格按照template <...> struct xyz { static constexpr long long value=...; } 实现该功能。不,真的,我的意思是不要使用constexpr function,而是严格在结构模板中实现计算。
  • 请注意,人们普遍认为,如果知道你的算法足以让它被破解,那么你的算法就是垃圾。
  • 对于可能遇到此问题并想尝试一下的人来说,这是一个很好的一般性评论,但我的需求与安全无关。
  • 在构建系统中简单地运行算法并将其值导出到程序可能更容易且更易于维护。在 CMake 中,这将是 config_file()。这样一来,您的算法就根本不在编译后的代码中。

标签: c++ constexpr


【解决方案1】:

理论上的 解决方案(因为模板应该是图灵完备的)- 不要使用 constexpr 函数并退回到仅使用 struct template with values 的老式 std=c++0x 计算风格。例如,不要这样做

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

但是

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;
}

struct 方法保证仅在编译时进行评估。

boost 的人设法做到了compile time parser 的事实是一个强烈的信号,尽管很乏味,但这种方法应该是可行的 - 这是一次性成本,也许人们可以将其视为一种投资。


例如:

给结构体供电:

// ***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;
};

构建令牌

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
  ;
}

【讨论】:

  • 一个。 0!=1。湾。 1! 的特化是多余的。
  • @SomeWittyUsername 好的,那么我的fact 实际上并没有计算阶乘的数学表达式。它仍然是该技术的有效示例,不是吗?
  • 我希望有其他选择(现在或将来使用 constexpr),但感谢您的回答!
  • @AdrianColomitchi 当然,你的建议对我来说听起来很合理。
【解决方案2】:

在 C++20 中,您只需将 constexpr 替换为 consteval 即可强制函数始终在编译时进行评估。

例子:

          int    rt_function(int v){ return v; }
constexpr int rt_ct_function(int v){ return v; }
consteval int    ct_function(int v){ return v; }

int main(){
    constexpr int ct_value = 1; // compile value
    int           rt_value = 2; // runtime value

    int a = rt_function(ct_value);
    int b = rt_ct_function(ct_value);
    int c = ct_function(ct_value);

    int d = rt_function(rt_value);
    int e = rt_ct_function(rt_value);
    int f = ct_function(rt_value); // ERROR: runtime value

    constexpr int g = rt_function(ct_value); // ERROR: runtime function
    constexpr int h = rt_ct_function(ct_value);
    constexpr int i = ct_function(ct_value);
}

C++20 之前的解决方法

您可以强制在常量表达式中使用它:

#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>>();
}

通过将函数的结果用作模板参数,如果在编译时无法解决,则会出现错误。

【讨论】:

  • 这是个好技巧!这很有用,但你知道是否有办法让它在运行时不能被调用?
  • @AlanWolfe 这样它就不能在运行时被调用,你会在编译时得到一个错误。当然,您只需将细节隐藏在定义良好的界面后面即可。
  • ct 在 C++11 中可以用 std::integral_constant 代替。见en.cppreference.com/w/cpp/types/integral_constant
  • 我没有从这段代码中得到它的作用或如何使用它......你能为body提供一个简单的例子吗? return x/3; ?我不知道该放在哪里。
  • @SergeRogatch 它只适用于那些你可以用作非类型模板参数的类型,我同意。话虽如此,如果您直接使用StaticFloorLog2,则无法确保在常量表达式中调用它。对不起。这就是constexpr 函数的工作原理。
【解决方案3】:

现在我们有了 C++17,有一个更简单的解决方案:

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

关键是非类型参数可以声明为auto。如果您使用 C++17 之前的标准,则可能必须使用 std::integral_constant。还有关于 constant 助手类的 a proposal

一个例子:

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;
}

【讨论】:

  • 这里不需要struct,你可以使用template &lt;auto V&gt; inline constexpr auto value = V;,放弃::value
  • 你也可以省略 "inline",因为它是由 constexpr 隐含的。
  • @NotSaying 对于非静态变量,情况并非如此。更多信息请参考this答案。
【解决方案4】:

让您的函数采用模板参数而不是参数,并在 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;
}

【讨论】:

  • 这仅限于非类型模板参数的约束。例如,浮点类型为not allowed
【解决方案5】:

在即将到来的 C++20 中将会有consteval specifier

consteval - 指定一个函数是一个立即函数,即每次调用该函数都必须产生一个编译时常量

【讨论】:

    猜你喜欢
    • 2012-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-06
    • 2018-04-17
    • 1970-01-01
    • 2011-01-20
    • 2013-03-13
    相关资源
    最近更新 更多