【问题标题】:ANSI-C constant-expression function like C++ constexpr?ANSI-C 常量表达式函数,如 C++ constexpr?
【发布时间】:2018-01-08 14:53:38
【问题描述】:

简单地说,有没有一种 ANSI-C 方法可以使函数成为常量表达式?

  1. 可以接受纯 ANSI-C 但 GNU 扩展 - 但不能使用 C++。
  2. 最好不依赖宏。
  3. 某些行为肯定类似于 C++ constexpr,并且不会在运行时解决。

背景:

我需要在没有浮点的嵌入式处理器中实现大量数学运算,因此我在我的应用程序中使用定点。

不过,我不喜欢在我的头文件中看到神秘的常量。 我的硬件需要几个浮点常量(例如130.7 microseconds0.2503 mJ),并且我真的希望能够读取(和更改)我的常量,因为零件数据表值已列出。

在给定的时刻,我的硬件需要使用这个常量,例如,填写计时器重载值,并且由于这些值是常量,我希望有类似的东西:

// Header file.
static const int values_table[] =
{
    _Time( 123.45 ),    // 123.45 microseconds.
    // ...
};

然后:

// Application source file.

int conv_to_timer( x ) { /* my calculations - all const. */ }

// ...

void my_code( void )
{
    // ...
    timer_reload = conv_to_timer( values_table[ index ] );

一种方法是让我的_Time( x ) 宏执行计时器值所需的所有计算,但它不灵活(即无法与外部的某些东西相比),也不便携(不同的硬件需要不同的计算)。

请给我一些优雅的方法的建议?

【问题讨论】:

  • 我不太赞同您对使用宏的反对意见。特别是,是什么阻止了宏的扩展与“外部事物”进行比较?你是说它没有联系的事实吗?如果不同的硬件需要不同的计算,那么函数如何比宏更好?
  • 没有。 constexpr 是 C++ 中的一个额外功能,而不是(标准)C 中的 - 没有类似的东西。
  • 感谢您的评论@JohnBollinger。宏的要点是它必须与我声明常量的位置相同,而函数可以位于不同的翻译单元中,因此不会因应用程序特定目的而污染头文件。我解释了吗?
  • 而不是_Time( 123.45 ), // 123.45 microseconds.,考虑一个宏ns_TO_RELOAD(123450),并删除FP,当代码应该不言自明时删除cmets。
  • @j4x 放弃 FP 的一个价值是宏数学在宏转换时不能很好地工作,如果有的话。此外,通过使用整数数学,可以精确控制到reload 单位的转换,而无需四舍五入和其他 FP 问题。喜欢#define ns_TO_RELOAD(ns) (((ns) + TICKS_PER_ns/2)/TICKS_PER_ns - 1)

标签: c c++11 constants ansi-c


【解决方案1】:

TL;DR:使用宏。

标准 C 没有 C++ 的constexpr 的直接模拟。最接近的东西是宏和内联函数,在这两者中,宏可以以某些不能使用内联函数的方式使用——特别是,不能在 C 需要常量表达式的情况下使用内联函数(嗯……听起来很相似到任何你知道的 C++ 关键字?),但另一方面,宏可以扩展为合适的表达式。

constexpr 被引入 C++ 主要是为了提供宏的替代方案,宏需要非平凡但编译时可计算的表达式。我相信它们也有一些优势,例如它们可能导致在编译时计算的值的类型,但这些似乎与您的特定情况无关。在 C 中,constexpr 从未被引入,宏仍然是标准方法。

您对使用宏的主要反对似乎本质上是关于代码风格。您观察到 C++ constexpr 函数的主体可以位于与对它的“调用”不同的翻译单元中,这显然对您很有吸引力。但是请注意,如果这是您的一个选项,那么您仍然需要在使用它们的翻译单元中至少 声明 此类功能,因此您实际上不会在术语上节省太多的清洁度。此外,虽然每个翻译单元必须包含它使用的每个宏的主体,但您仍然可以将宏定义分离到单独的标题中。

总体而言,我认为避免使用宏(如果可以的话)不会真正为您带来任何好处。我倾向于认为 C++ 社区对宏的普遍厌恶是constexpr 的一大动力,就像任何功能上的好处一样。如果碰巧你被这种特殊的厌恶所折磨,那么你真的需要克服它才能在标准 C 中有效地编程。

【讨论】:

  • 再次感谢@JohnBollinger。我不打算提出任何关于“大病”的讨论,我认为你的回应实际上是唯一的出路。不过,我必须评论一下确定宏混乱代码的难度。编译器消息变得臃肿,类型安全消失,维护变得更加困难。嗯……就是生活……
  • @John Bollinger 甚至宏也不是直接替换只是一个例子:constexpr int f(int number) { return number == 1? 1 : (number * f(nnumber - 1)); } 很难想象宏和内联函数具有相同的功能。所以 f(10) 将计算编译时间和 f(variable) 运行时间或 c++14 迭代版本constexpr int f(int n) { int result = 1; for(int i = 2 ; i <= n; i++) result *= i; return result; }
  • @PeterJ,我承认constexpr 函数可以做宏不能做的事情,但这不是重点,因为它们不是 OP 的选项。但是,我稍微调整了答案的措辞,以减少对 constexpr 函数可以做的事情的性质的限制。
【解决方案2】:

相信您的优化器,然后进行验证。

C++ constexpr 是基于编译器已经进行的优化而设计的。如果你写:

int fac( int n ) {
  int r = 1;
  for (int i = 2; i < n; ++i)
    r*=i;
  return r;
}

然后做

printf("%d", fac(10));

适度优化设置下的编译器将评估对fac 的调用,并将调用替换为fac(10) 的常量返回值。

constexpr 的存在主要是为了允许 fac(10) 用于语义恒定的地方,例如 C++ 中数组的大小。所以在 C++ 中你可以这样做:

char buffer[fac(10)];

当且仅当fac(10)constexpr。 (C 有可变大小的数组)。

您可以将_Time 编写为宏,也可以将其编写为inline 函数并告诉编译器进行优化。然后检查生成的二进制文件,看看它是否存储了包含最终值的数组。

也可以使用宏,但宏存在通常的调试问题(我不知道有一个编译器可以在宏出错时为您提供适当的单步调试),这使得它们不太适合复杂的代码。

了解 C++ 为您提供零保证 constexpr 函数实际上是在编译时评估的。它只是允许您在其他上下文中使用它,并且大多数编译器会为您预先计算它。

【讨论】:

    猜你喜欢
    • 2016-07-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-05
    • 1970-01-01
    • 2015-06-15
    相关资源
    最近更新 更多