编译时计算(C ++ v. C)

6

我理解在C++中可以使用constexpr关键字执行编译时计算,例如:

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程序更好的性能(例如速度)的一种方式吗?


2
缺少constexprconsteval并不意味着编译器不能在编译时进行计算,如果所有值都已知。使用关键字告诉编译器您的意图,以便编译器可以警告您是否犯了错误,以确保您的意图。 - t.niese
2
有趣的事实:constexpr 的存在也不能保证编译时计算。 - rustyx
1
当然,我同意编译时评估可以在没有“constexpr”的情况下发生。此外,我同意该关键字并不意味着在所有情况下都进行编译时评估。但是,据我所知,当声明的函数使用常量表达式参数调用时,它确保了编译时评估。 - thatmarkdude
2个回答

7

只有一件事是确定的 - 编译时计算使得 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));
}

使用GCC 10.2编译成C语言程序,使用-O3优化参数,并由于as-if规则的帮助,该程序得以优化。

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
1
@thatmarkdude:如果你正在寻找关于C和C++哪个更好的意见,那么你是在错误的方向上吠叫……而且也离题了。你唯一能得到的保证就是C++比C复杂得多,这是毋庸置疑的。这种复杂性带来了一些优点,同时也有一些成本。试着比较一下F1赛车和拖拉机拉力比赛,看看哪一个更强大…… - chqrlie
@thatmarkdude 是的,我认为所有的C++开发者都会同意这一点,而所有的C开发者则不同意(当你编写代码并且知道它将如何编译时,你不应该知道自己在做什么吗?:)。这就是比较语言的问题,这是一个观点问题... - rustyx
我认为C++的问题在于,人们发现模板系统和编译时评估还不够强大,因此在每次迭代中都会添加更复杂的内容。 - Antti Haapala -- Слава Україні

3

编译时计算是否可以被认为是C++相对于C的一个关键优势?

实际上,重要的不是编译,而是软件构建

请参考维基百科上的构建自动化页面。

注意,许多软件项目(包括githubgitlab上的许多开源项目)从更抽象的东西(例如使用软件工具)生成C(甚至是C ++)代码。一个典型的例子显然是解析器生成器(又称编译器编译器),如GNU bisonANTLR。另一个例子是像rpcgenSWIG这样的粘合代码生成器。而GNU autoconf则适应于计算机上可用的软件包。请注意,Chicken-SchemeBigoo都将C代码从Scheme源代码生成。当然还有this。在某些情况下,从微小的输入产生大量的C文件(也可以参见XBM格式)。Maple能够生成大量的C文件,在某些情况下生成大量的C代码-例如50万行-是有意义的(如Pitrat的书《人工存在:有意识机器的良知》中所解释的那样)以及博客

最后,整个程序优化 可以存在(参见最近GCC中的 -flto 标志,用于 链接时优化;您实际上需要使用 gcc -Wall -O2 -flto 编译并进行链接),并且需要一些编译器在“链接时间”上的支持。
在某些情况下,编译时间并不那么重要(例如编译FirefoxLinux内核LibreOfficeGnomeGTK等源代码库),但是构建时间可能需要数小时,甚至几十分钟(因为许多不同的翻译单元 - 具体来说是*.c*.cc文件 - 必须编译然后链接)。
据传谷歌公司内部消耗了数小时的计算机时间来构建他们大部分的内部软件。
请注意,最早的C++编译器(例如Cfront)是以C代码生成器的形式实现的,并且像GCC编译器这样的大型软件有数十个专门的C或C++代码生成器。尝试从可用的源代码在您的笔记本电脑上构建一个GCC交叉编译器,以针对您的RaspBerryPi板进行编译(该板太小且性能不足,无法直接编译GCC)。然后可以参考LinuxFromScratch上的构建说明。

如果你想要看一个生成C代码的C程序的例子,可以查看我为Linux编写的manydl.c代码,或者我的Bismon程序,该程序在this draft报告中有所描述。过去版本的已废弃的GCC MELT项目确实生成了一百万行的C或C++代码。manydl.c能够在几天内生成并编译C代码,并且说明dlopen(3)可以被多次使用。如果你想要看一个在Linux上生成C++代码的C++软件的例子,可以查看我的RefPerSys项目。此外,还可以参考tunes.org网站上与元编程和生成C或C++代码相关的讨论。

还需考虑交叉编译情况

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

关于C++和C 之间的区别

我理解C++标准n3337并没有规定编译时计算(但我不敢说自己是C++专家)。 特别地,没有任何禁止你制作C++解释器(可以用C、C ++、Ocaml、Java等编码)。将这个想法视为一个有趣的编程练习(但在尝试之前请阅读Dragon book)。

我的观点是,一个学习C ++的学生班级可以被视为符合C ++标准规定的C ++实现。教授C ++的好方法是询问教室关于几个C ++程序的语义(链接1),并且可以用铅笔和纸或白板进行教学。我曾经以这种方式在巴黎第六大学教授过一门 操作语义学 课程。黑板是黑色的,我用各种颜色的粉笔。
还可以查看软件工具,例如Frama-CClang静态分析器。两者都是开源的,因此您可以研究它们的源代码。
“这是C ++程序可以实现更好性能(例如速度)相对于等效C程序的一种方式吗?”
那是你的观点,我不同意。如果用C++编写OcamlSBCL,你认为它们的运行时速度会更快(你应该下载并研究源代码),请问你的理由是什么?一个有趣的练习是将tinyCC编译器重新编写成C++(用于C语言,在Linux上针对x86 32位和x86-64位进行目标编译,使用C编写),并测试任何改进。这个简单但聪明的编译器可以非常快速地编译C代码,但缺少编译器优化

1
我认为你在问题中漏掉了关键点,那就是 **v.**。 - Antti Haapala -- Слава Україні
2
你是什么意思?哪个 v?我将其解读为 versus。我学过拉丁语,但英语不是我的母语。 - Basile Starynkevitch
嗯,是的,问题更像是“C++相对于C的优势在于它可以在编译时计算东西吗?” - Antti Haapala -- Слава Україні
1
这是一个非常详尽和有文献支持的回答!然而并没有完全解决问题,而且似乎离题了。很抱歉我只能点赞一次 :) - chqrlie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接