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的消息交互,对机器的性能要求更高。

邪恶的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转换。

apollo-11的AGC

Twitter上Lin Clark骄傲的宣布自己老妈Magret Hamilton 50年前写的Apollo 11登月代码被人推上了GitHub库。

的确是一件让人骄傲的事情。我挖了下,Hamilton当时写的的显示接口部分。详细见: http://www.ibiblio.org/apollo/
但同时,她是AGC的Programming Leader。

也很感叹网上一堆人才们开发了AGC(Apollo Guidance Computer)的模拟器,https://github.com/virtualagc/virtualagc。
毕竟是人类第一次登月。第一次开发core rope这种原始的存储设备,第一次提出软件工程的概念,第一次利用我们现在看来原始的代码
帮助人类送到从未触及的月球。放在现在,我们很多技术人员还没有当年这拨程序员的水平。即使,我们有更先进的设备和技术。
Apollo-11是一个组织,一个政府,以及人类共同的集体合作结果。

以此纪念AGC提出的软件工程概念,在还没有C语言的年代:https://github.com/chrislgarry/Apollo-11

使用指针7年的C程序员问了一个问题

今天小Y发来消息,说自己被一个代码弄的焦头烂额。大概是这个样子


unsigned char phSessionHandle[20] = {0};
SDF_GenerateRandom(phSessionHandle,uiLength,ucRandom);//1
SDF_GenerateRandom(*(void**)phSessionHandle,uiLength,ucRandom);//2
SDF_GenerateRandom(*(void**)&phSessionHandle,uiLength,ucRandom);//3

函数SDF_GenerateRandom的原型是:
unsigned int SDF_GenerateRandom(void* hSessionHandle, unsigned int uiLength, unsigned char ucRandom)

就是这么简单的一段代码,小Y说:“1,3方式的调用我得到了期望的结果。但是2方式错了”。别忘了,小Y可是一个有7年C语言编程经验的程序员。
这种传指针的把戏怎么可能难住TA。我们来看看。[read]
1符合大家的使用习惯,一个数组unsigned char [20]类型传入到函数,自动退化为一个unsigned char*指针类型,最终变成void*类型。不会出现什么问题。

2是一个 unsigned char[20] 类型被强转为 void** 类型。最终我们用*解引用,作者目的是想让它强制为一个 void* 类型,这样就和函数原型声明一致了(难道是因为静态检查让作者做出的改动?奇妙的是,调用2并没有返回期望的结果。

3是一个 unsigned char[20] 类型被取地址后变成一个 unsigned char* [20] 类型。很明显,作者在发现2调用出现问题后,觉得可能是因为自己没有取地址的原因,特意加上&,让(&phSessionHandle)看起来是一个二级指针。这样名正言顺的变成 void **,最后解引用为void*。小Y顺利运行成功,感觉“对”了。

有过这次经验,小Y每次碰到数组就这么用。在一次使用memcpy的代码中,TA这样写。


memcpy(&phSessionHandle,&source,sizeof(source))

小Y的领导发火了,你干啥在数组前加一个取地址符号&啊。他可是一个“二级”指针啊。你应该这样用。
memcpy(phSessionHandle,&source,sizeof(source))

对于一个专注业务逻辑的程序员,小Y即使当了这么多年的码农,仍然很讨厌指针。
第一:同样是在数组上使用&,TA第一次用“对”了,第二次却不被领导认可。
第二:到底数组和指针在编译器内部如何被识别,又发生了哪些类型转换。

为了解答小Y的疑问。我们先来看看C11标准里怎么解释&运算符号。

查阅C11标准文档:

6.5.3 The unary & operator yields the address of its operand. If the operand has type ‘‘type’’,
the result has type ‘‘pointer to type’’. If the operand is the result of a unary * operator,
neither that operator nor the & operator is evaluated and the result is as if both were
omitted, except that the constraints on the operators still apply and the result is not an
lvalue. Similarly, if the operand is the result of a [] operator, neither the & operator nor
the unary * that is implied by the [] is evaluated and the result is as if the & operator
were removed and the [] operator were changed to a + operator. Otherwise, the result is
a pointer to the object or function designated by its operand.

翻译如下:

&单目操作符得到它操作数的一个地址。
如果操作数的类型是‘type’,那么结果是一个指向type的指针。
如果操作数是单目运算符*的结果。那么*和&都会被编译器忽略。除非操作符上叠加其它操作而且结果不是一个左值。
同样,如果操作数是[]运算符的结果。那么&和[]所隐含的*操作都不会被执行。结果和去掉&运算并且把[]变成+操作一样。(意思是可能存在[]偏移的情况)
其它情况,&操作符返回的结果是指向对象的指针或者操作数所指定的函数。

再来看C99的阐述,
rationale for the C99 standard C99标准补充提到取地址符号时说明

6.5.3.2 Address and indirection operators
Some implementations have not allowed the & operator to be applied to an array or a function.
(The construct was permitted in early versions of C, then later made optional.) The C89
Language Committee endorsed the construct since it is unambiguous, and since data abstraction is
enhanced by allowing the important & operator to apply uniformly to any addressable entity.

翻译如下:

一些(编译器)实现不允许&操作符用在数组和函数上。(这种搭配结构在早期的C版本中是允许的,后来变成一个可选项)。C89语言委员会认为这种搭配含义并不模糊,表示支持。因此,基于数据抽象概念被加强的原因,委员会允许&这个重要操作符用在任何可以寻址的实体对象上。

再回到我们的代码

SDF_GenerateRandom(*(void**)phSessionHandle,uiLength,ucRandom);//2
SDF_GenerateRandom(*(void**)&phSessionHandle,uiLength,ucRandom);//3

2的类型转换过程是:(2a) unsigned char [20] (编译器维护的一个类型)-> (2b) void ** -> (2c) void*
3的类型转换过程是:(3a) unsigned char [20] (编译器维护的一个类型)-> (3b) unsigned char* [20] (编译器维护的一个类型) -> (3c) void** -> (3d) void *

通过上面标准的解释我们最多也就知道在对3a->3b的过程中并没有发生实质性的类型变化。只是编译器维护的类型发生了变化,本质上[]隐含了一个一级指针在编译器看来做了点改动(为什么编译器要这样做?我们后面看)。

而实际上2得到了错误的结果,因为2a到2b这一个过程,将一个*强制变成了**的二级指针。这个二级指针是非法的。再去解引用(dereference *)是一个未定义的行为。对此标准也有这样的解释。

The unary * operator denotes indirection. If the operand points to a function, the result is
a function designator; if it points to an object, the result is an lvalue designating the
object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an
invalid value has been assigned to the pointer, the behavior of the unary * operator is
undefined.102)

而为什么3的运行结果是对的。我能想到的是小Y的编译环境正好对这种未定义行为解释到一个合法的地址返回了合法值,并没有发生段错误罢了。3a到3b的过程中并未发生实质的类型变化。程序不可能记住了这个非法的二级指针,在解引用后指回。

于是我在Mac上写了一段类似的程序。dereference以后指针并没按照期望变回原来的地址,每次运行都是一个变化的地址,并导致了段错误。说明这种代码是完全不可移植。我们在平时要完全禁止。

值得一说的是,编译器在报错是经常会带出 type*[] 和 type* 不匹配的警告。
所以我们可以再上升一下:看看Edwin Brady 在《Type-driven Development with Idris》怎么描述不同“物体”怎么观察到程序的类型的

Types serve several important roles:
For a machine, types describe how bit patterns in memory are to be interpreted.
For a compiler or interpreter, types help ensure that bit patterns are interpreted consistently when a program runs.
For a programmer, types help name and organize concepts, aiding documentation and supporting interactive editing environments.

翻译:

类型扮演几个不同的重要角色:
从一台机器看来(0和1),类型用来描述内存中0,1排列组合而成比特流怎么被解释。(我给翻译多加了点料)
对编译器和解释器而言(它们看到int [],char[]),类型用来保证0,1排列组合而成的比特流在程序运行期间有一致的解释结果。(比如 char a + int b 到底得到一个char还是int,是否允许这种操作。说大白话就是让解释器可以做很多中间转换,编译检查等)
对程序员而言(我们看到int,char),类型帮助我们命名和组织概念,书写文档说明支持交互式编辑环境(有具体类型来承载变量,向代码阅读者解释说明,支持IDE等给出提示检查)

[/read]