【问题标题】:Compile-time computation (C++ v. C) [closed]编译时计算(C++ v.C)[关闭]
【发布时间】:2020-08-30 19:19:58
【问题描述】:

我了解constexpr 关键字可用于在 C++ 中执行编译时计算。例如:

constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

(取自https://en.cppreference.com/w/cpp/language/constexpr

是否可以将编译时计算视为 C++ 与 C 的主要优势?

据我了解,编译时计算在 C 中是不可能的。constexpr 不可用,我相信代码必须在运行时进行评估。

这是 C++ 程序与同等 C 程序相比可以获得更好性能(例如速度)的一种方式吗?

【问题讨论】:

  • 没有constexprconsteval 并不意味着如果所有值都已知,编译器就不能在编译时进行计算。使用关键字告诉编译器意图是什么,以便编译器可以提醒您您犯的错误以确保您的意图。
  • 有趣的事实:constexpr 的存在也不能保证编译时计算。
  • 当然,我同意编译时评估可以在没有constexpr 的情况下进行。另外,我同意关键字并不意味着在所有情况下都进行编译时评估。但是,据我所知,当使用常量表达式参数调用声明的函数时,它会保证编译时评估。

标签: c++ c metaprogramming constexpr compile-time


【解决方案1】:

只有一件事是肯定的 - 编译时计算使 C++ 编译器必然更复杂编译速度必然变慢,因为编译器需要在编译期间执行此操作;见例子

constexpr int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

int main(void) {
    static_assert(factorial(10) == 3628800, "factorial 10 was correct");
    static_assert(factorial(3) == 42, "factorial 3 was 42");
}

由于后者static_assert而不是前者,它必须编译失败。


C 编译器不需要这种复杂性,因为没有要求 C 编译器必须能够在编译期间计算递归函数的值。一个简单的 C 编译器可以很好地将每个 语句 分别组装成机器代码,而不必记住前面的语句做了什么。 C 标准当然不要求它能够在编译期间评估递归函数。

但这并不是说没有 C 编译器会在编译期间这样做。看这个例子:

#include <stdio.h>

int factorial(int n) {
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

int main(void) {
    printf("%d\n", factorial(10));
}

Compiled with GCC 10.2 as a C program with -O3,感谢the as-if rule,程序变成了

factorial:
        mov     eax, 1
        cmp     edi, 1
        jle     .L4
.L3:
        mov     edx, edi
        sub     edi, 1
        imul    eax, edx
        cmp     edi, 1
        jne     .L3
        ret
.L4:
        ret
.LC0:
        .string "%d\n"
main:
        sub     rsp, 8
        mov     esi, 3628800
        mov     edi, OFFSET FLAT:.LC0
        xor     eax, eax
        call    printf
        xor     eax, eax
        add     rsp, 8
        ret

哪个更直接对应

unsigned factorial(unsigned n) {
     unsigned i = 1;
     while (n > 1) {
         i *= n;
         n --;
     }
     return i;
}

int main(void) {
    printf("%d\n", 3628800);
}

即编译器不仅将递归展平为一个简单的while 循环,而且还解析了常量的阶乘,而且都没有任何特殊关键字。

【讨论】:

  • 感谢您的回复。我看到“as-if”规则允许编译器在某些条件下推断值。但是,如果我们只看两种语言的编译器需要支持什么,我们可以说 C++ 在这里有优势吗?例如,所有 C++17 编译器都必须支持 constexpr 的编译时计算。但是,C 语言本身并不能以任何形式保证这一点。因此,正如您所展示的,一些 C 编译器将具有这种高度优化的能力,而另一些则没有。但对于 C++,这是一种保证。
  • @thatmarkdude:如果您正在寻找关于 C 和 C++ 之间更好 的意见,那么您就找错了树……而且也跑题了。您得到的唯一保证是 C++ 至少比 C 复杂得多。这种复杂性也带来了一些优势和一些成本。尝试比较一级方程式赛车和拖拉机牵引,并确定哪个更强大......
  • @thatmarkdude 是的,我认为所有 C++ 开发人员都会同意这一点,而所有 C 开发人员都不同意(您不应该知道自己在编写代码时在做什么,并且还知道它将如何编译吗? :)。这就是比较语言的问题,这是一个见仁见智的问题......
  • 在我看来,C++ 的问题在于人们发现模板系统和编译时评估还不够足够强大,他们在其中添加了一些更复杂的东西每次迭代。
【解决方案2】:

是否可以将编译时计算视为 C++ 与 C 的主要优势?

其实重要的不是编译,而是software building

请参阅build automation 上的维基百科页面。

然后,请注意许多软件项目(包括githubgitlab 上的许多开源项目)正在生成 C (甚至 C++)来自更抽象的代码(例如,使用软件工具)。一个典型的例子显然是解析器生成器(又名compiler-compilers),例如GNU bisonANTLR。另一个例子是胶水代码生成器,如rpcgenSWIG。并且GNU autoconf 使您的构建适应您计算机上可用的软件包。注意Chicken-SchemeBigoo 都从Scheme 源代码生成C 代码。当然见this。在某些情况下,巨大的 C 文件是由微小的输入生成的(另请参见 XBM 格式)。 Maple 能够生成大型 C 文件,并且在某些情况下会生成大量 C 代码 -e.g.半百万行 - 有意义(如 Pitrat 的书 Artificial beings: the conscience of a conscious machineblog 中所述。

最后,whole-program optimization 可以存在(请参阅最近的 GCC 中的 -flto 标志以获取 Link-Time-Optimization;您实际上可以编译 linkgcc -Wall -O2 -flto)并且在“链接时”需要一些编译器支持.

在某些情况下,编译时间并不那么重要(例如,从源代码编译 FirefoxLinux kernelLibreOfficeGnomeGTK base),但是构建时间可以持续几个小时,或者肯定是几十分钟(因为有很多不同的 translation units - 具体是 *.c*.cc 文件 - 必须先编译然后链接) .

有传言称,Google 会在内部花费数小时的计算机时间来构建大部分内部软件。

请注意,第一个 C++ 编译器(例如 Cfront已实现为 C 代码生成器,并且诸如 GCC 编译器之类的大型软件已经几十个 专门的 C 或 C++ 代码生成器。尝试从可用的源代码在您的笔记本电脑上构建针对您的RaspBerryPi 板的 GCC 交叉编译器(它太小且功能不足,无法在其上直接编译 GCC)。 LinuxFromScratch 上的构建说明是相关的。

有关生成 C 代码的 C 程序示例,请参阅我的用于 Linux 的 manydl.c 代码,或在 this draft 报告中描述的 Bismon 程序。过时的 GCC MELT 项目的过去版本确实生成了一百万行 C 或 C++ 代码。 manydl.c 能够在几天内生成然后编译 C 代码,并说明 dlopen(3) 可以多次使用。有关在 Linux 上生成 C++ 的 C++ 软件的示例,请参阅我的 RefPerSys 项目。还请查看tunes.org,了解与元编程和 C 或 C++ 代码生成相关的讨论。

还要考虑cross-compilation 情况

例如在笔记本电脑上为Arduino 编译C 代码,或为RaspberryPi 编译C++ 代码,可能使用GCC。或者在您的 PC 上为遥远的top500 超级计算机编译代码。

关于 C++ versusC

我对 C++ 标准 n3337 的理解是编译时计算并未在此处指定(但我并不声称自己是 C++ 专家)。 特别是,没有什么禁止您使用 C++ interpreter(您可以使用 C、C++、Ocaml、Java 等编写代码)。将此想法视为一个有趣的编程练习(但在尝试之前请阅读Dragon book)。

我的观点是,按照 C++ 标准的规定,可以将学习 C++ 的学生的课堂视为 C++ 实现。教授 C++ 的一个好方法是向教室询问几个 C++ 程序的 semantics,这可以用铅笔和纸或 whiteboard 来教授。我实际上以这种方式在operational semantics 上教授了一门课程(在巴黎第六大学)。 板子是黑色的,我用了各种颜色的粉笔。

还可以查看Frama-CClang static analyzer 等软件工具。两者都是open source,所以你可以研究它们的来源。

这是 C++ 程序与同等 C 程序相比可以获得更好性能(例如速度)的一种方式吗?

这是你的意见,我不同意。是什么让您认为OcamlSBCL 的运行时会更快(您应该下载并研究源代码)如果它是用 C++ 编写的? 一个有趣的练习可能是用 C++ 重新编码 tinyCC 编译器(对于 C,针对 Linux 上的 x86 32 位和 x86-64 位,用 C 编码),并对任何改进进行基准测试。那个简单但聪明的编译器编译 C 代码的速度非常快,但编译的太少了compiler optimizations

【讨论】:

  • 我认为你错过了问题的关键点,即 v.
  • 什么意思?哪个v?我正在阅读它为versus。我确实学过拉丁语,但英语不是我的母语。
  • 嗯,是的,与此相反,问题更像是“C++ 相对于 C 的优势在于它在编译时计算事物吗?”
  • 如此详尽且有据可查的答案!然而并没有完全解决这个问题,无论如何这似乎离题了。对不起,我不能多次紫外线:)
猜你喜欢
  • 2012-10-09
  • 1970-01-01
  • 2013-02-23
  • 2015-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 2021-03-15
相关资源
最近更新 更多