问题描述
我知道constexpr
关键字可用于在C ++中执行编译时计算。例如:
constexpr int factorial(int n)
{
return n <= 1 ? 1 : (n * factorial(n - 1));
}
(摘自https://en.cppreference.com/w/cpp/language/constexpr)
可以将编译时计算视为C ++ v。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编译器可以很好地将每个 statement 分别汇编为机器代码,而不必记住前面的语句做了什么。 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
循环,而且还解析了常量的阶乘,而且都没有任何特殊的关键字。
可以将编译时计算视为C ++ v。C的主要优势吗?
实际上,重要的不是编译,而是software building。
请参阅build automation上的Wikipedia页面。
然后,请注意许多软件项目(包括github或gitlab上的许多开源项目)正在生成 C (甚至C ++)来自更抽象的内容的代码(例如,使用软件工具)。一个典型的例子显然是解析器生成器(也称为compiler-compilers),例如GNU bison或ANTLR。另一个示例是粘合代码生成器,例如rpcgen
或SWIG。并且GNU autoconf使您的构建适应计算机上可用的软件包。请注意,Chicken-Scheme和Bigoo都从Scheme源代码生成C代码。当然请参见this。在某些情况下,巨大的C文件是通过微小的输入生成的(另请参见XBM格式)。 Maple能够生成大型C文件,并且在某些情况下会生成大量C代码-例如五百万行-很有道理(如Pitrat的书Artificial beings: the conscience of a conscious machine中所述)和blog。
最后,whole-program optimization可以存在(请参见最新GCC中的Link-Time-Optimization的-flto
标志;您实际上将使用gcc -Wall -O2 -flto
进行编译和link),并且在“链接时”需要一些编译器支持。
在某些情况下,编译时间并不那么重要(例如,编译Firefox或Linux kernel或LibreOffice或Gnome或GTK(从其源代码库中获取),但是构建时间可以持续数小时,或者甚至可以是数十分钟(因为有许多不同的translation units-具体来说,{{1} }或*.c
文件-必须先进行编译然后链接)。
有传言称,Google内部会花费数小时的计算机时间来构建其大部分内部软件。
请注意,第一个C ++编译器(例如Cfront)已被实现为C代码生成器,并且大型软件(例如{{3 }}编译器有数十个专用的C或C ++代码生成器。尝试通过可用的源代码在笔记本电脑上构建针对您的GCC板的GCC交叉编译器(该板太小且功能不足,无法在其上直接编译GCC)。这样,在RaspBerryPi上的构建说明就有意义了。
有关生成C代码的C程序的示例,请参见我的LinuxFromScratch Linux版代码或manydl.c报告中描述的Bismon程序。过时的this draft项目的过去版本确实生成了一百万行C或C ++代码。 *.cc
可以在几天内生成然后编译C代码,并说明GCC MELT可以被多次使用。有关在Linux上生成C ++的C ++软件的示例,请参见我的dlopen(3)项目。另请参阅RefPerSys,以获取与元编程和C或C ++代码生成有关的讨论。
还要考虑tunes.org情况
例如可能使用cross-compilation在笔记本电脑上为Arduino编译C代码或为RaspberryPi编译C ++代码。或在遥远的GCC超级计算机上编译您的PC代码。
关于C ++ top500 C
我对C ++标准versus的理解是,未在此处指定编译时计算(但我并不声称自己是C ++专家)。 特别是,没有什么禁止您编写C ++ n3337 (您可以在C,C ++,Ocaml,Java等语言中进行编码)。将该想法视为有趣的编程练习(但请在尝试之前先阅读interpreter)。
我的观点是,按照C ++标准中的规定,学习C ++的学生课堂可以被视为C ++的实现。教授C ++的一种好方法是向教室询问几个C ++程序的Dragon book,可以用铅笔,纸或semantics来教。我实际上以这种方式(在巴黎第六大学)教过whiteboard的一门课程。 黑板是黑色的,我用了各种颜色的粉笔。
还查看诸如operational semantics或Frama-C之类的软件工具。两者均为Clang static analyzer,因此您可以研究其来源。
与同等的C程序相比,C ++程序是否可以通过这种方式获得更好的性能(例如速度)?
表示您的意见,并且我不同意。是什么让您认为,如果open source或Ocaml的运行时是用C ++编写的,它将更快(您应该下载并研究源代码)? 一个有趣的练习可能是使用SBCL编译器以C ++进行编码(对于C语言,针对Linux上的x86 32位和x86-64位,以C语言进行编码),然后对所有改进进行基准测试。那个简单而又聪明的编译器可以非常快速地编译C代码,但是却很少tinyCC。