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



我知道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++程序可以获得更好性能(例如速度)的一种方式吗?

只有一件事是肯定的——编译时计算使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("%dn", factorial(10));
}

使用GCC 10.2编译为带有-O3的C程序,并且由于-assif-规则,该程序成为

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 "%dn"
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("%dn", 3628800);
}

即编译器不仅将递归平坦化为简单的while循环,而且在没有任何特殊关键字的情况下解析了常数的阶乘。

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

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

请参阅维基百科关于构建自动化的页面。

然后,请注意许多软件项目(包括github或gitlab上的许多开源项目)正在从更抽象的东西生成C代码(甚至是C++)。一个典型的例子显然是像GNUbison或ANTLR这样的解析器生成器(也称为编译器编译器)。另一个例子是像rpcgen或SWIG这样的粘合代码生成器。GNU autoconf使您的构建适应计算机上可用的软件包。请注意,ChickenScheme和Bigoo都是从Scheme源代码生成C代码的。当然可以看到这个。在某些情况下,巨大的C文件是由微小的输入产生的(另请参阅XBM格式)。Maple能够生成大型C文件,在某些情况下,生成大量C代码(例如50万行)是有意义的(正如Pitrat的书《人造人:有意识机器的良心》中所解释的那样)和博客。

最后,整个程序优化可以存在(参见最近GCC中的链接时间优化的-flto标志;您实际上会使用gcc -Wall -O2 -flto进行编译和链接),并且需要一些编译器支持;链接时间";。

在某些情况下,编译时间并不那么重要(例如,从源代码库编译Firefox或Linux内核、LibreOffice、Gnome或GTK),但编译时间可能会持续数小时,甚至数十分钟(因为必须编译许多不同的翻译单元,具体是*.c*.cc文件,然后才能链接)。

据传,谷歌内部要花费数小时的计算机时间来构建大部分内部软件。

请注意,第一批C++编译器(例如Cfront)已被实现为C代码生成器,并且像GCC编译器这样的大型软件具有数十个的专用C或C++代码生成器。试着在你的笔记本电脑上用可用的源代码构建一个针对RaspBerryPi板的GCC交叉编译器(它太小,功率不足,无法直接在上面编译GCC)。因此,LinuxFromScratch上的构建说明是相关的。

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

还应考虑交叉编译情况

例如,为Arduino编译C代码,或者在笔记本电脑上为RaspberryPi编译C++代码,也许可以使用GCC。或者在你的电脑上为一台排名前500的超级计算机编译代码。

关于C++与C

我对C++标准n3337的理解是那里没有指定编译时计算(但我并不声称自己是C++专家)。特别是,没有什么可以阻止你制作C++解释器(你可以用C、C++、Ocaml、Java等编写代码)。把这个想法看作是一个有趣的编程练习(但在尝试之前请阅读Dragon的书)。

我的观点是,学生学习C++的课堂可以被视为C++实现,正如C++标准中所规定的那样。教C++的一个好方法是向课堂询问几个C++程序的语义,这可以用铅笔、纸或白板来教授。实际上,我用这种方式(在巴黎第六大学)教了一门关于操作语义的课程。黑板是黑色的,我用了各种颜色的粉笔。

还可以查看Frama-C或Clang静态分析器等软件工具。两者都是开源的,所以你可以研究它们的源代码。

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

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

最新更新