Anders Hejlsberg解析现代compiler

Anders Hejlsberg on modern compiler architecture

Anders大神曾是TurboPascal作者以及Delphi的首席架构师,后来在微软负责Visual J++和MFC的架构。再后来成为C#和.NET的主要设计师和Typescript的核心贡献者。这个视频是他在channel9对现代编译器的一个white board talker。我只略懂一些编译原理的知识。总结下:现代的编译器基于language server, 修改即解析,复用sytax tree。多了很多Json的消息交互,对机器的性能要求更高。

C++的左值与右值

C++11之前,lvalue,rvalue可以简单的理解为等号两边的值类型。但C++11后,语言对用户开放了更多的辅助类型以帮助设计者开发更好用的STL库。比较典型的就是引入glvalue(general left value), xvlaue( eXtrem value, I got no idea why it named xvalue,probably xman), prvalue( pure right value).

StackOverFlow有一个不错的图来解释左值和右值。

Taxonomy
C++11引入的move语改变传统lvalue与rvalue概念

move本身不对其中的值进行操作,它是配合STL模板函数来完成将A容器的引用指针数据直接转交给B容器。如:

std::vector A(2,3);
std::vector B(std::move(A));

转移发生在B的右值拷贝构造函数中: std::vector(std::vector&& a){ this.data_pointer = a.data_pointer; a.reset();}

xvalue的存在就像薛定谔的猫,在某些场景下,xvalue既可以左又可以右。(这里会列出更多场景)

forward仅仅是为了在参数传递过程中,保持&&类型或&类型而引入。毕竟我们要区分到底是一个 1,还是一个int i=1。

参数在传递过程中,允许退化(decay)。退化的场景有如下:

T&& -> T&
T&& & -> T&
T& && -> T&
T&& && -> T&&

因此当Foo(&)传入一个&&类型时,我们为了保持右值特性,使用forward保持。在Foo(&&)传入一个&类型时,退化为左值类型T&,也使用forward保持。

B.S对lvaue,rvalue的解释: http://www.stroustrup.com/terminology.pdf

std::move/std::forward的一篇非常好文章: http://bajamircea.github.io/coding/cpp/2016/04/07/move-forward.html

ROS的序列化设计

    这篇文章主要讲ROS如何做消息的序列化设计:

1, ROS如何做消息的多类型适配。(MessageFormat,MessageAdapter)

2, C++的模板和智能指针在ROS消息模块的应用。(shared_ptr)

3, 如何通过去除序列化和反序列化来增强ROS的实时性。(自定义allocator)

 

消息的多类型适配

         ROS提供基本类型( uint8_t, uint16_t, uint32_t, double ,float)外,还提供duration,Time等自定义的定长类型。此外Header,String,Array这种变长类型的消息。

       为什么需要typename,因为在引用时,编译器需要知道class::typename 到底是嵌套类型,还是静态函数或成员变量。

       Serializer<T>::write/read接口的设计把模板的能力发挥的恰到好处,对每个基础类型的数据implement一个对应的序列化(write)/反序列化(read)操作函数。符合类型根据类型T,去找到对应的函数模板类型去Ser/deSer。

      大多数的库包括Adaptive AutoSar,DDS都是按照类似的思路完成这种操作。C++的STL编程模式在处理Serialization/DeSerialization这类功能时特别常见。

     ROS的*.msg生成的头文件中带有该类型的序列化/反序列化入口。该入口将调用serialization<T>中的函数对每个成员做序列化/反序列化。

     在回调的时候,为了适配多个参数类型,也带有Message Adapter这个类。

 

ROS中智能指针的使用

ROS中使用了weak_ptr,unique_ptr,shared_ptr, 其中shared_ptr在进程内通信的publish接口中使用了。进程内通信利用shared_ptr可以有效减少多线程数据的申请释放问题带来的bug。

ROS的实时性改造

事实上,ROS1.0实时性存在很大问题。主要来源于:1,内存的频繁动态申请,包括STL。2,事件通知(condition_variable,mutex)引起的线程切换。

序列化本身只是memcpy和对应类型的赋值操作,其时间是可以有上界的。为了让用户编程方便,内部会有较多的临时堆内存,内存申请引发的page fault会是一个让车载应用不放心的地方。

另外linux本身的cfs调度是一个动态调度机制,它虽然试图给每个应用程序均分CPU时间片。但因为系统负载和应用程序之间本身的逻辑(比如通知,阻塞,抢占,同步)会导致完成一个具体的任务(比如通信任务)时间上界不可知。在不同场景下处理同一个任务的时间抖动范围也不是平稳的。

如果要确保一个Safety-Critical的应用,要从应用层面和中间件层面以及操作系统层面上都要满足时延的可预测能力。具体要解决的问题就是:内存要静态申请,阻塞任务要有超时机制,调度的时间片要完全可控。这些问题也是要求,适用到所有软件层面。

邪恶的proxy

    在具有成熟规模的公司里,Proxy配置的颇为艰难。注意,我在这里说的是带用户名和密码的proxy,而不是默认proxy。 大多数应用程序的Use system proxy settings选项在这种场景下形同虚设。。

你以为你export了全局proxy变量就没事了。

结果应用A跳出来不能访问网络,因为不能使用系统proxy,你只好在应用里配置好proxy。以此类推应用B, 应用C。

你以为使用cnNTLM之类的软件在windows上把代理配置127.0.0.1:8080就没事了。。。不。。proxy不允许你这么干了。。。两边协议版本一升级可能用户自己都不知道发生了什么,也不想去折腾。。。

我的工作环境是win7+ubuntu,那apt,clion,android studio甚至firefox等等。。一一要照顾到它的proxy配置。

比如添加一个key:sudo apt-key adv –keyserver-options http-proxy=http://USER:PASSWORD@PROXY_URL:PORT/ –keyserver keyserver.ubuntu.com –recv-keys GPG_KEY

比如apt的proxy竟然要单独的创建: acquire::http::proxy “http://USER::PASSWARD#PROXY_URL:PORT”

比如android studio需要在gradle里配置proxy参数…..

不管到哪都带着一个叫做proxy的恶魔参数。。。

好歹今天你学习到一点: Sudo -E xxxx-command. 把系统环境变量带入命令行,期望大多数工具能够自己读取系统proxy吧。

只是期望。邪恶仍将无处不在。

密码保护:note-private

这是一篇受密码保护的文章,您需要提供访问密码:

远程桌面

即使是多年经验的工程师也需要尴尬的承认,配置软件环境比想象的需要多一些时间(特别是不常用的工具)

VNC

1, 使用RealVNC,TigerVNC之类的软件远程桌面。我较强的诉求是调整桌面大小。通常这类软件提供一些图像显示的depth和size设置。比如RealVNC会推荐:

vncserver-virtual :1 -depth 24 -geometry 1440×900

:1 是桌面的虚拟显示器ID。登录时端口为:5900+该ID

depth: 图像显示深度

geometry: 桌面的显示大小

按照realvnc官网这篇文章介绍,你通常并不能顺利的设置桌面大小。我的环境是Ubuntu 16.04 Server(gnome,lxde,xfce)。 因为一些不知道的原因,导致这条命令配置完,使用realvnc的客户端登录上去时显示为灰色不可用的桌面。偶尔有几次却又是正常的。不得不反复去查log。

所以,退而求其次的使用

systemctl start vncserver-x11-serviced

但这种只能使用默认大小。

update: 使用tigerVNC的最新版本,根本不用考虑太多。而且还很方便。其自带的java客户端简单且方便使用。

1,安装tigerVNC的deb包。

2,vncserver :1 -depth 24 -geometry 1380×900 (随意设置大小)

3, 如果使用的是Ubuntu,那么很有可能一进去是没有侧边栏之类显示的。原因是~/.vnc/xstartup的脚本确实需要仔细调试。经过一番测试,使用以下配置没有问题,并且可以使用上unity。

xrdb $HOME/.Xresources
xsetroot -solid grey
x-window-manager &
unity &

其余脚本统统多余。。

X11-Forwarding

如果没有对桌面有很强烈的美感需求,使用x11-forwarding真的是一个很好的选择。

1,配置/etc/ssh/sshd_config下的

X11Forwarding yes
X11DisplayOffset 10
X11UseLocalhost yes

2,使能Putty的 Connection/SSH/X11/Enable X11 forwarding。

3,在X display location填上:localhost:10(sshd_config里的默认偏移号)。

4, 使用xming或者mobaXterm直接输入带GUI程序的二进制。

使用x11-forwarding过程中发现,除了Visual Studio Code的菜单显示有问题外。其它的GUI程序基本正常,甚至Android Studio的调试模拟器也能很好的显示出来。我认为基本能够满足日常需要。

总结:

1, 对x11的启动脚本要有一些认识,要了解xstartup每行脚本的具体作用。

2,realvnc的老版本不要使用,非常不方便。可能收费版本会好些。(我用的是一个老的有license的版本)。

3,客户端是通用的,用realvnc的客户端也可以访问tigervnc的server。因为遵循一样的通信协议(Virtual Network Computing)。

4,网上的一些声音:xfce桌面会快一些,容易配置一些。

5,x11-forwarding基本够用。颜值党上vnc。

xamarin的一点调研

跨平台开发一直很fancy。实际情况是有经验的开发者通常能够猜到他们需要付出的代价。

xamarin声称自己能够复用几乎所有的代码。这意味着ios上写的代码逻辑可以复用到android和windows phone上,听起来有点不可思议。怎么做到的呢?

答案就是提供调用本地库, 第三方库的能力。

1,但xamarin要适配最新的os,一定要会比native开发更慢,它提供的sdk要去适配新的native api。

2,而且中间肯定有适配层,如果一个跨平台的小白直接使用这种能力,就意味着做出来的应用效率更差。

3,如果对性能有要求,就必须同时设计开发两个平台的应用程序。考虑到复用逻辑的效率,库的使用是否满足跨平台的要求。

4,如果只是有一个android程序,想迁移到ios版本,重新使用xamarin封装。那基本就是从头再写。从网上的反应看来,代价不少。

综上所述:使用visual studio for mac 开发跨平台的应用,更适合那些已经熟练使用C#的开发者。让这些开发者快速搭建原型,享受一次开发多平台部署的快感。毕竟这种感觉,目前只有xamarin才能带给你。

c++的{}和()

{} 提供列表初始化的功能,最终调用的是构造函数。比如

Point{ }

调用的是Point::Point()构造函数。而

Point{x,y}

调用的是Point::Point(x,y)构造函数。

所以,{ } 以初始化列表的方式explicit的调用了构造函数。提供了除了New,Operator New, placement New以外的创造新类的方法。

构造出一个匿名的类直接使用 Point{}即可,它调用构造函数,但是临时的,没有名字来指向它。所以调用完再也无法找到这个匿名类。

Point{}; // 调用构造,收工。

通常情况下我们使用有名字的类 ,这样后面的代码可以引用:

Point a{}; // a这个类可以在后面用。

为了使用类的这种匿名功能,通常标准库会这样玩,比如<utility>头文件中的std::hash就可以这样使用:

std::hash<std::string>{}(“/sensor/percept/lidar_topic”);

它得到一个size_t的hash字符串。

TLS协议和HTTP的嵌套

抓了一个TLSv1.2的协议包

协议可以互相嵌套

— ethernet II (SA,DA)

— Internet Protocol(SIP,DIP)

— Transmission Control Protocol( sport,dport,seq,ack,len)

— Hypertext Transfer Protocol( Proxy 443)

— Secure Socket Layer(Application Data Protocol http2, encrypted app data)

— Hypertext Transfer Protocol

— Secure Socket Layer

mojave下c++ functional

如果在Mojave下工作,可以发现Mac的可选的编译器种类比较多:

简单来说,如果使用了brew+安装了xcode。至少有以下几个编译器:

1, gcc-5/g++-5 : 不支持functional

2, x86_64-apple-darwin14.3.0-g++6 / x86_64-apple-darwin14.3.0-gcc-6:正常使用。

3, clang++/clang: 版本10.0.1, 不支持std::bind

测试代码如下:

#include <iostream>
#include <functional>

/*---- c function pointer ------*/
typedef  int (*myfunc)(int a,int b);

int abc(int a, int b )
{
  std::cout<<"i am original fp: "<< a << " " << b<<std::endl;
  return 0;
}

/*----class function ------*/
class test{
public: 
   test(int mem):member(mem){};
    ~test(){};
  int m_func(int a, int b)
  {
     std::cout<<"member function:  " << a << "  " << b<<std::endl; return 0;
  };
 private:
    int member;
};


int main()
{
    // use class instance member to init a std::function
    test ins(2);
    std::function<int(int,int)> a = std::bind(&test::m_func,&ins,std::placeholders::_1,std::placeholders::_2);
    a(1,2);
    
    // use raw c function pointer to init a std::function
    std::function<int(int,int)> b(abc);
    b(3,3);

    // use a lamda expr. to init a std::function
    std::function<int(int,int)> c( [](int a, int b)->int{ std::cout<<"lamda oks"<<std::endl; return 0;} );
    c(4,4);

    /* amazing, you can get raw function point from std::function
      but this not recommended by ISO c++ */
    myfunc* d = b.target<myfunc>();
    (*d)(1,2);
    std::cout<< "function address:  " << d<<std::endl;
    std::cout<<b.target_type().name()<<"  ==vs=== " <<typeid(myfunc).name()<<std::endl;
   
    // but this will cause a segmentation fault !    
    myfunc* e = a.target<myfunc>();
    //(*e)(666,666);
    std::cout<< e<<std::endl;
    std::cout<<a.target_type().name()<<"  ==vs=== " <<typeid(myfunc).name()<<std::endl;
    
    // but this will cause a segmentation fault ! 
    myfunc* f = c.target<myfunc>();
    std::cout<< f<<std::endl;
    std::cout<<c.target_type().name()<<"  ==vs=== " <<typeid(myfunc).name()<<std::endl;
     
    //(*f)(999,999);
}

输出如下:

member function: 1 2
i am original fp: 3 3
lamda oks
i am original fp: 1 2
function address: 0x7b52c2300e80
PFiiiE ==vs=== PFiiiE
0
St5_BindIFSt7_Mem_fnIM4testFiiiEEPS1_St12_PlaceholderILi1EES6_ILi2EEEE ==vs=== PFiiiE
0
Z4mainEUliiE_ ==vs=== PFiiiE

总结:

1,std::function 友好的封装了函数指针,适用于函数回调的场景。

2,内部使用了多态,同时🈶很好的类型检查。如果在嵌入式场景下使用,引入的库带来的负担较重,需要仔细斟酌。

3,成员函数和Lambda表达式不能通过target获取指针,原因是不知道该函数类型。这意味着不能随意的把一个成员函数指针和Lamda变成一个C函数指针。这会给C++库赋值给C库的回调函数带来一定的使用限制。

4,静态函数和普通全局函数可以很方便的和std::function转换。