【问题标题】:How to make GCC evaluate functions at compile time?如何让 GCC 在编译时评估函数?
【发布时间】:2015-03-21 12:27:15
【问题描述】:

我正在考虑以下问题:我想用一个使用某种查找表的程序对微控制器(比如 AVR 巨型类型)进行编程。

第一次尝试是在单独的文件中找到表并使用任何其他脚本语言/程序/....创建它。在这种情况下,为 C 创建必要的源文件需要付出相当多的努力。

我现在的想法是使用预处理器和编译器来处理事情。我尝试使用正弦值表来实现这一点(仅作为示例):

#include <avr/io.h>
#include <math.h>

#define S1(i,n) ((uint8_t) sin(M_PI*(i)/n*255))
#define S4(i,n) S1(i,n), S1(i+1,n), S1(i+2,n), S1(i+3,n)

uint8_t lut[] = {S4(0,4)};

void main()
{
    uint8_t val, i;

    for(i=0; i<4; i++)
    {
        val = lut[i];
    }
}

如果我编译此代码,我会收到有关 sin 函数的警告。在程序集中,.data 部分没有任何内容。如果我只是删除第三行中的sin,我会得到程序集中的数据。显然,所有信息在编译时都可用。

您能否告诉我是否有办法实现我的意图:编译器会尽可能多地离线计算值?或者是使用外部脚本/程序/...计算表条目并将它们添加到一个单独的文件中的最佳方法,该文件将只是 #included?

【问题讨论】:

  • “相当努力” - 使用良好的脚本语言?肯定比用 C 解决问题要少......
  • C++11(使用 C++14 改进)有 constexpr 作为编译器在编译时执行函数的提示。
  • @johannes: constexpr 没有优势。它只是允许在编译时评估一个表达式(甚至强制编译器对其进行评估,例如通过分配给一个枚举并不能阻止它在运行时在不同位置重新评估相同的源文件!)。它不强制或暗示任何事情。也就是说,我的 GCC 将 OP 中的代码 sn-p 优化为编译时评估的查找表(没有任何特殊的特殊舞蹈)。
  • commented below 如果您使用-fno-builtin,关于sin 的警告将变为错误,这是因为gcc 当前将大多数数学函数视为常量表达式,如果它使用内置版本.
  • 您的sin 只会返回-1/255,01。也许你会使用`((uint8_t) (sin(M_PI*(i)/n)+1)/2*255)`

标签: c gcc pre-compilation


【解决方案1】:

您正在尝试的不是 C 语言的一部分。在这种情况下,我按照这种模式编写代码:

#if GENERATE_SOURCECODE
int main (void)
{
    ... Code that uses printf to write C code to stdout
}
#else
    // Source code generated by the code above
    ... Here I paste in what the code above generated

    // The rest of the program
#endif

每次您需要更改它时,运行定义了 GENERATE_SOURCECODE 的代码,然后粘贴到输出中。如果您的代码是自包含的并且生成的输出仅在生成它的代码发生更改时才会更改,则效果很好。

【讨论】:

  • 这不是最佳的,因为我正在交叉编译。因此,它可能会或可能不会好。我想这种努力比维护两个不同的文件要大得多,因为所有头文件都必须交换,因此必须在 #if 块内。对我来说看起来很讨厌,对不起。
【解决方案2】:

这里的一般问题是 sin 调用使这个初始化事实上非法,根据 C 语言的规则,因为它本身不是 常量表达式 并且你正在初始化 静态存储持续时间,这需要。这也解释了为什么您的数组不在.data 部分。

C11 (N1570) §6.6/2,3 常量表达式(强调我的)

常量表达式可以在翻译过程中求值,而不是 运行时,因此可以在常量可能的任何地方使用 是。

常量表达式不应包含赋值、增量、 减量、函数调用或逗号运算符,除非它们是 包含在未计算的子表达式中。115)

但是,正如@ShafikYaghmour 的评论,GCC 将用其内置对应项替换sin 函数调用(除非存在-fno-builtin 选项),这很可能被视为常量表达式。根据6.57 Other Built-in Functions Provided by GCC

GCC 包括许多函数的内置版本 标准 C 库。以__builtin_ 为前缀的版本总是 被视为与 C 库函数具有相同的含义,即使 您指定 -fno-builtin 选项。

【讨论】:

  • 这可能有效,因为当前gcc treats math builtins as it they were constant expressions,使用-fno-builtin 使类似的代码使用gcc 失败,但它只会生成警告,否则。
  • @ShafikYaghmour:那为什么反汇编中没有数据。我完全不确定,如果我尝试访问数据会发生什么...
  • @ChristianWolf:你可以通过__builtin_sin 强制它,但我同意Shafik 的观点,它应该可以工作。也许你正在使用一些不支持的 avr-gcc 端口?
  • @GrzegorzSzpetkowski:如果我尝试这个(用__builtin_sin 替换sin),数据将在.bss 部分注册并在启动时归零——我通过扩大数组并寻找数组大小。
  • 啊,我明白了。我忘记了一对paramtesis。因此,我总是评估 pi 的倍数(实际上是 0 并且得到优化)。在更正它正常工作时。
【解决方案3】:

首先,不言而喻,您应该评估(可能通过实验)这是否值得做。您的查找表将增加您的数据大小和程序员的工作量,但可能会也可能不会提供您需要的运行时速度提升。

如果你还想这样做,我不认为 C 预处理器可以直接做到这一点,因为它没有迭代或递归的设施。

解决这个问题的最可靠的方法是用 C 或其他语言编写一个程序来打印表的 C 源代码,然后使用预处理器将该文件包含在您的程序中。如果您使用make 之类的工具,您可以创建规则来生成表格文件,并让您的.c 文件依赖于该文件。

另一方面,如果您确定永远不会更改此表,您可以编写一个程序来生成它一次,然后将其粘贴进去。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-12-24
    • 1970-01-01
    • 2017-07-03
    • 2021-02-13
    • 1970-01-01
    • 2011-09-27
    • 2015-04-23
    • 1970-01-01
    相关资源
    最近更新 更多