众所周知,C/C++是编译型语言,但若有人向你提及C++的解释器,你也不必认为他在痴人说梦——确实有一个C++的解释器项目存在,这就是cling。
Cling是一个基于LLVM和Clang的现代C++解释器,作为CERN基于C++的数据分析框架Root的一部分被开发。CERN就是用大型强子对撞机(aka LHC)证实了希格斯玻色子存在的那个CERN,Root是负责对包括这项工作在内的LHC产生的实验数据进行分析的套件。Cling是用户和Root动态交互的界面,不同于那些prove-of-concept的玩具,cling实现了stack trace,variable shadowing(新声明的变量覆盖已有的同名变量)和语义化的对象打印等脚本语言解释器的功能。Cling是我见过的最具想象力的程序之一,因为兼容C ABI,他可以直接执行系统调用,这基本上意味着你可以利用他以一种动态的、交互式的模式来做任何事!
令人稍感困惑的是这么nb的一个程序似乎并不太为人所知。除开别的应用不谈,对于嵌入式开发来说,一个C/C++解释器应该有非比寻常的意义:对SoC上各个外设的控制本质上是通过kernel提供的系统调用实现的,而通常开发这种C程序的流程是:写代码—编译—debug—再编译—debug……当项目很大时,编译这一步会很耗时,无法敏捷地迭代真正感兴趣的那部分代码。如果有一个C++解释器的话,你可以动态地执行外设的API,写完一行代码敲下回车就能实时获得外设的响应。这和microPython之类的项目的概念有些类似,但因为这里你直接面对的是最底层的C API,就不会受限于只能调用别的框架封装好的API。就像上面提到的,你可以动态地做任何事!
遗憾的是,CERN Root官网只提供了X86/64平台上预编译的压缩包下载。为了能让cling运行在arm平台上,我在一个chroot环境下从源码编译了cling可独立运行的二进制,和编译脚本一起发布在GitHub上:
仓库链接
目前只编译了armhf(armv7)的版本,可以直接进入release下载,也可以clone仓库后运行build.sh自己编译。 cling的可执行文件是
./release/bin/cling
最近编辑记录 SdtElectronics (2021-02-18 12:59:48)
离线
nb的功能,nb的开销
和cling强大的功能相称的是其巨大的体积(我编译出来的版本整个文件夹达到了400+M,这个大小包括了一个完整的LLVM和clang,因此可能有可以精简的文件)以及相对低的解释执行性能,所以尽量不要在一个配置过低的平台上运行它。我在一张A20(dual a7@1G)+128M的板子上可以正常运行,一条简单语句的解释时间接近1s。
从源码编译
如果你想从源码编译,有一些要注意的地方:
依赖:libxml2 cmake git python2 libffi(libffi-dev on Debian-based distros)
系统配置:编译llvm是一项极其消耗计算资源的工作,我在i5-8300H平台上的QEMU下开j4进行编译,消耗五个半小时左右。之所以不开j8是因为编译的空间开销也很大,线程过多容易爆内存。注意编译脚本默认是开
$nproc
个线程的,如果要指定线程数,可以传一个参数给脚本:
./build.sh 4
网络:整个项目的源码体积在100+M,建议shell开代理,不然会很慢。
串口设备上的问题
cing似乎会打印一些和串口不兼容的字符,如果你在使用串口终端的设备上遇到问题,可以尝试用以下命令运行cling:
script -qc ./cling | cat
离线
感谢分享,学习下
离线
cling快速入门:
hello world:
[cling]$ #include <stdio.h>
[cling]$
[cling]$ printf("hello world\n");
hello world
cling可以直接include在系统搜索路径里的头文件,包括kernel headers,其中声明的函数就能立即使用了
获取表达式的值:
表达式的末尾不写分号会怎么样?报错?不,cling会回显该表达式的值。写过matlab的人应该对此不陌生:语句末带分号,静默语句的值在console中的输出,反之则不静默:
[cling]$ __cplusplus
(long) 201703
cling可以语义化地回显一些对象和对象引用表达式的值,这些对象基本是stl容器:
[cling]$ #include <string>
[cling]$
[cling]$ std::string {"str"}
(std::string) "str"
[cling]$
[cling]$ #include <vector>
[cling]$
[cling]$ std::vector<char> ve{'c', 'h', 'a', 'r'}
(std::vector<char> &) { 'c', 'h', 'a', 'r' }
[cling]$
[cling]$ #include <map>
[cling]$
[cling]$ std::map<int, char> mp{std::pair<int,char>()}
(std::map<int, char> &) { 0 => '0x00' }
函数名也是表达式。输入函数名会怎么样呢?
[cling]$ atoi
(int (*)(const char *) throw()) Function @0xb6c7bcd1
at /usr/include/stdlib.h:104:
extern int atoi (const char *__nptr)
__THROW __attribute_pure__ __nonnull ((1))
函数签名被打印了出来!
我编译的版本开启了RTTI,因此还可以通过typeid动态审查变量的类型:
[cling]$ auto b = std::string ("typed")
(std::basic_string<char, std::char_traits<char>, std::allocator<char> > &) "typed"
[cling]$ typeid(b).name()
(const char *) "NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE"
离线
cling进阶:
除了标准的C++语法,cling还支持一些内置的命令。我们来看的第一条命令,是可以列出其他命令用法的:
.help
[cling]$ .help
Cling (C/C++ interpreter) meta commands usage
All commands must be preceded by a '.', except
for the evaluation statement { }
==============================================================================
Syntax: .Command [arg0 arg1 ... argN]
.L <filename> - Load the given file or library
.(x|X) <filename>[args] - Same as .L and runs a function with
signature: ret_type filename(args)
.> <filename> - Redirect command to a given file
'>' or '1>' - Redirects the stdout stream only
'2>' - Redirects the stderr stream only
'&>' (or '2>&1') - Redirects both stdout and stderr
'>>' - Appends to the given file
.undo [n] - Unloads the last 'n' inputs lines
.U <filename> - Unloads the given file
.I [path] - Shows the include path. If a path is given -
adds the path to the include paths
.O <level> - Sets the optimization level (0-3)
(not yet implemented)
.class <name> - Prints out class <name> in a CINT-like style
.files - Prints out some CINT-like file statistics
.fileEx - Prints out some file statistics
.g - Prints out information about global variable
'name' - if no name is given, print them all
.@ - Cancels and ignores the multiline input
.rawInput [0|1] - Toggle wrapping and printing the
execution results of the input
.dynamicExtensions [0|1] - Toggles the use of the dynamic scopes and the
late binding
.printDebug [0|1] - Toggles the printing of input's corresponding
state changes
.storeState <filename> - Store the interpreter's state to a given file
.compareState <filename> - Compare the interpreter's state with the one
saved in a given file
.stats [name] - Show stats for internal data structures
'ast' abstract syntax tree stats
'asttree [filter]' abstract syntax tree layout
'decl' dump ast declarations
'undo' show undo stack
.help - Shows this information
.q - Exit the program
英文看得头晕转向?没事,常用的命令只有几个:
.q, 退出cling。等效于Ctrl+D。
.L, 加载文件,之后这个文件里的符号就可以被调用了。
# echo 'int val = 666;' > tst.cpp
# ./cling
****************** CLING ******************
* Type C++ code and press enter to run it *
* Type .q to exit *
*******************************************
[cling]$
[cling]$ .L tst.cpp
[cling]$ val
(int) 666
注意,如果你加载的文件还依赖别的文件中的符号,你需要把依赖的文件也通过.L加载。cling没法帮你处理依赖关系,毕竟它不知道依赖的文件在哪。
类似的命令还有一个.x,他会加载文件之后执行文件中和文件同名的那个函数:
# echo 'int tst(){return 666;}' > tst.cpp
# ./cling
****************** CLING ******************
* Type C++ code and press enter to run it *
* Type .q to exit *
*******************************************
[cling]$
[cling]$ .x tst.cpp
(int) 666
.I 显示或添加包含路径
[cling]$ .I
-cxx-isystem
/usr/include/c++/10
-cxx-isystem
/usr/include/arm-linux-gnueabihf/c++/10
-cxx-isystem
/usr/include/c++/10/backward
-isystem
/usr/local/include
-isystem
/home/cling-src/release/bin/../lib/clang/5.0.0/include
-extern-c-isystem
/usr/include/arm-linux-gnueabihf
-extern-c-isystem
/include
-extern-c-isystem
/usr/include
-I
/home/cling-src/release/include
-I
/home/cling-src/llvm/tools/cling/include
-I
.
-resource-dir
/home/cling-src/release/bin/../lib/clang/5.0.0
-nostdinc++
.class, 非常强大的命令,打印出某个class的layout:
[cling]$ #include<limits>
[cling]$
[cling]$ .class std::numeric_limits<char>
===========================================================================
struct std::numeric_limits<char>
SIZE: 1 FILE: limits LINE: 453
List of member variables --------------------------------------------------
limits 455 0x0 public: static constexpr _Bool is_specialized
limits 468 0x0 public: static constexpr int digits
limits 469 0x0 public: static constexpr int digits10
limits 471 0x0 public: static constexpr int max_digits10
limits 473 0x0 public: static constexpr _Bool is_signed
limits 474 0x0 public: static constexpr _Bool is_integer
limits 475 0x0 public: static constexpr _Bool is_exact
limits 476 0x0 public: static constexpr int radix
limits 484 0x0 public: static constexpr int min_exponent
limits 485 0x0 public: static constexpr int min_exponent10
limits 486 0x0 public: static constexpr int max_exponent
limits 487 0x0 public: static constexpr int max_exponent10
limits 489 0x0 public: static constexpr _Bool has_infinity
limits 490 0x0 public: static constexpr _Bool has_quiet_NaN
limits 491 0x0 public: static constexpr _Bool has_signaling_NaN
limits 492 0x0 public: static constexpr enum std::float_denorm_style has_denorm
limits 494 0x0 public: static constexpr _Bool has_denorm_loss
limits 508 0x0 public: static constexpr _Bool is_iec559
limits 509 0x0 public: static constexpr _Bool is_bounded
limits 510 0x0 public: static constexpr _Bool is_modulo
limits 512 0x0 public: static constexpr _Bool traps
limits 513 0x0 public: static constexpr _Bool tinyness_before
limits 514 0x0 public: static constexpr enum std::float_round_style round_style
List of member functions :---------------------------------------------------
filename line:size busy function type and name
(compiled) (NA):(NA) 0 public: static constexpr char min() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char max() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char lowest() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char epsilon() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char round_error() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char infinity() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char quiet_NaN() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char signaling_NaN() noexcept;
(compiled) (NA):(NA) 0 public: static constexpr char denorm_min() noexcept;
最近编辑记录 SdtElectronics (2021-02-18 17:38:52)
离线
还想了解更多?
令人哭笑不得的是,Root团队好像全然没有意识到他们搞出了个什么神仙玩意,他们的主要精力投入在了Root本体的开发上,而cling则一直被当成一个附属项目对待。因此,想探索cling的更多功能,最好的地方是CERN Root的官网,以及CERN Root的官方community。Root包括cling至今还在被高度活跃的社区不断地开发和完善中,在其组织的一些会议中的幻灯片展示了将来的Roadmap,还会有更多的黑魔法被加入到其中,最受关注的一些特性包括C++20的modules。
离线
C++本地编译的速度都那么慢了,解释C++的速度能让人满意吗?解释型C脚本的速度都比js/lua慢那么多,何况是C++?
离线
C++本地编译的速度都那么慢了,解释C++的速度能让人满意吗?解释型C脚本的速度都比js/lua慢那么多,何况是C++?
你说的很对,总体来说解释C++的开销是很大的,但是否让人满意要看实际场景了。一方面,C++有模板、constexpr之类的编译开销很大的特性,大量使用这些特性的代码会很慢。另一方面,cling宣称自己是jit解释器,这意味着热路径上的代码不会有重复解释的开销,一定程度上优化了性能。cling的目的不是代替编译好的程序,而是在原型验证阶段提供一个强大的武器:写过脚本语言的人都知道,有一个REPL对于开发阶段来说是多大的帮助,这种交互式的开发体验是一般的编译型语言难以得到的,而cling为C/C++提供了这个宝贵的能力
离线
搞一个C++解释器意义何在?都用了脚本了,Lua python之类的东西应该更适应存在
离线
C++解释器的意义,特别是对嵌入式开发领域而言,我已经在1楼说得很清楚了。当然,缺少脚本语言或者和硬件交互的开发经历的人可能不太好理解。
补充几个特性:
其实cling也支持tab补全,但他的match机制还不是很完善,现在不太好用。
cling支持自动的多行模式,当输入一个 { 而不在本行闭合它的时候,就会自动进入多行模式。多行模式的提示符会多一个'?'
.L 命令除了支持加载C/C++源码,还支持直接加载动态库,前提是先导入了对应的头文件,才能使用动态库里的符号。
来一个展示上面两个特性的例子,用cling和内核新的GPIO接口libgpiod交互:
[cling]$ #include <gpiod.hpp>
[cling]$
[cling]$ .L /usr/lib/arm-linux-gnueabihf/libgpiod.so.2
[cling]$
[cling]$ .L /usr/lib/arm-linux-gnueabihf/libgpiodcxx.so
[cling]$
[cling]$ gpiod::chip chip("/dev/gpiochip0")
(gpiod::chip &) @0xb6fa100c
[cling]$
[cling]$ auto lines = chip.get_line(236)
(gpiod::line &) @0xb6fa1014
[cling]$
[cling]$ lines.request({.consumer = std::string("tst"),
[cling]$ ?
[cling]$ ? .request_type = gpiod::line_request::DIRECTION_OUTPUT,
[cling]$ ?
[cling]$ ? .flags = gpiod::line_request::FLAG_OPEN_DRAIN})
[cling]$
离线