如何关闭ubuntu安装gcc编译器器的优化,使缓冲区溢出

arm-linux-gcc&常用参数讲解&gcc编译器使用方法
我们需要编译出运行在ARM平台上的代码,所使用的交叉编译器为
arm-linux-gcc。下面将arm-linux-gcc编译工具的一些常用命令参数介绍给大家。
在此之前首先介绍下编译器的工作过程,在使用GCC编译程序时,编译过程分为四个阶段:
1. 预处理(Pre-Processing)
2. 编译(Compiling)
3. 汇编(Assembling)
4. 链接(Linking)
Linux程序员可以根据自己的需要让
GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为
今后的调试做好准备。和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。
以文件example.c为例说明它的用法
0. arm-linux-gcc -o example example.c
不加-c、-S、-E参数,编译器将执行预处理、编译、汇编、连接操作直接生成可执行代码。
-o参数用于指定输出的文件,输出文件名为example,如果不指定输出文件,则默认输出a.out
1. arm-linux-gcc -c -o example.o example.c
-c参数将对源程序example.c进行预处理、编译、汇编操作,生成example.0文件
&& 去掉指定输出选项"-o
example.o"自动输出为example.o,所以说在这里-o加不加都可以
2.arm-linux-gcc -S -o example.s example.c
-S参数将对源程序example.c进行预处理、编译,生成example.s文件
&& -o选项同上
3.arm-linux-gcc -E -o example.i example.c
-E参数将对源程序example.c进行预处理,生成example.i文件(不同版本不一样,有的将预处理后的内容打印到屏幕上)
就是将#include,#define等进行文件插入及宏扩展等操作。
4.arm-linux-gcc -v -o example example.c
加上-v参数,显示编译时的详细信息,编译器的版本,编译过程等。
5.arm-linux-gcc -g -o example example.c
-g选项,加入GDB能够使用的调试信息,使用GDB调试时比较方便。
6.arm-linux-gcc -Wall -o example example.c
-Wall选项打开了所有需要注意的警告信息,像在声明之前就使用的函数,声明后却没有使用的变量等。
7.arm-linux-gcc -Ox -o example example.c
-Ox使用优化选项,X的值为空、0、1、2、3
0为不优化,优化的目的是减少代码空间和提高执行效率等,但相应的编译过程时间将较长并占用较大的内存空间。
8.arm-linux-gcc&& -I
/home/include -o example example.c
-Idirname:
将dirname所指出的目录加入到程序头文件目录列表中。如果在预设系统及当前目录中没有找到需要的文件,就到指定的dirname目录中去寻找。
9.arm-linux-gcc&& -L
/home/lib -o example example.c
-Ldirname:将dirname所指出的目录加入到库文件的目录列表中。在默认状态下,连接程序ld在系统的预设路径中(如/usr/lib)寻找所需要的库文件,这个选项告诉连接程序,首先到-L指定的目录中去寻找,然后再到系统预设路径中寻找。
10.arm-linux-gcc &static -o libexample.a example.c
静态链接库文件
gcc在命令行上经常使用的几个选项是:
只预处理、编译和汇编源程序,不进行连接。编译器对每一个源程序产生一个目标文件。
确定输出文件为file。如果没有用-o选项,缺省的可执行文件的输出是a.out,目标文件和汇编文件的输出对source.suffix分别是source.o和source.s,预处理的C源程序的输出是标准输出stdout。
-Dmacro 或-Dmacro=defn&&
其作用类似于源程序里的#define。例如:% gcc -c -DHAVE_GDBM -DHELP_FILE=\"help\"
cdict.c其中第一个-
D选项定义宏HAVE_GDBM,在程序里可以用#ifdef去检查它是否被设置。第二个-D选项将宏HELP_FILE定义为字符串“help”(由于
反斜线的作用,引号实际上已成为该宏定义的一部分),这对于控制程序打开哪个文件是很有用的。
某些宏是被编译程序自动定义的。这些宏通常可以指定在其中进行编译的计算机系统类型的符号,用户可以在编译某程序时加上
-v选项以查看gcc缺省定义了哪些宏。如果用户想取消其中某个宏定义,用-Umacro选项,这相当于把#undef
macro放在要编译的源文件的开头。
将dir目录加到搜寻头文件的目录列表中去,并优先于在gcc缺省的搜索目录。在有多个-I选项的情况下,按命令行上-I选项的前后顺序搜索。dir可使用相对路径,如-I../inc等。
对程序编译进行优化,编译程序试图减少被编译程序的长度和执行时间,但其编译速度比不做优化慢,而且要求较多的内存。
允许比-O更好的优化,编译速度较慢,但结果程序的执行速度较快。
产生一张用于调试和排错的扩展符号表。-g选项使程序可以用GNU的调试程序GDB进行调试。优化和调试通常不兼容,同时使用-g和-O(-O2)选项经常会使程序产生奇怪的运行结果。所以不要同时使用-g和-O(-O2)选项。
-fpic或-fPIC&&
产生位置无关的目标代码,可用于构造共享函数库。
上是gcc的编译选项。gcc的命令行上还可以使用连接选项。事实上,gcc将所有不能识别的选项传递给连接程序ld。连接程序ld将几个目标文件和库程
序组合成一个可执行文件,它要解决对外部变量、外部过程、库程序等的引用。但我们永远不必要显式地调用ld。利用gcc命令去连接各个文件是很简单的,即
使在命令行里没有列出库程序,gcc也能保证某些库程序以正确的次序出现。
gcc的常用连接选项有下列几个:
将dir目录加到搜寻-l选项指定的函数库文件的目录列表中去,并优先于gcc缺省的搜索目录。在有多个-L选项的情况下,按命令行上-L选项的前后顺序搜索。dir可使用相对路径。如-L../lib等。
在连接时使用函数库libname.a,连接程序在-Ldir选项指定的目录下和/lib,/usr/lib目录下寻找该库文件。在没有使用-static选项时,如果发现共享函数库libname.so,则使用libname.so进行动态连接。
-static&& 禁止与共享函数库连接。
-shared&& 尽量与共享函数库连接
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。相关文章推荐:
gcc 缓冲区 溢出
前面说过,gcc4以上版本会对缓冲区溢出做出保护,那么这篇文章旨在使大家明白gcc到底是怎么做保护的。
《缓冲区溢出系列二之缓冲区溢出实例详解》
中提到过,用gcc4以上版本编译的时候,可以用选项-fno-stack-protector来关闭对缓冲区溢出的保护,那么我们现在就先来对比一下没做保护和做了保护到底有什么区别。
分别加上保护和不加保护来编译
《缓冲区溢出系列二之缓冲区溢出实例详解》
中的源程序p.c
xulei@xulei-deskt...
阅读(1108) 回复(0)
其他文章推荐
我在调用gcc -g -I nbap.sparc/ -I /vobs/wds/swt/oss/ossasn1/solaris-2.4/include/ -ansi -c nbap.sparc/nbap.c的时候
打出了一些warning,如下
/var/tmp//ccGmV6xk.s: Assembler messages:
/var/tmp//ccGmV6xk.s:333290: Warning: .stabs: description field '12def' too big, try a different debug format
/var/tmp//ccGmV6xk.s:333291: Warning: .stabs: description field '12def' too big, try a different debug for...
阅读(1471) 回复(0)
一个有问题的程序:
* test.c for test overflow under FC7 with gcc 4.1.2
* you must echo "0">/proc/sys/kernel/exec-shield
* you must
"0">/proc/sys/kernel/randomize_va_space
long get_esp()
__asm__("movl %esp,%eax");
void func(unsigned char *from)
unsigned char dest[512];
esp = get_esp();
printf("esp : 0x%x\n",esp);//打印当前ESP内...
阅读(2086) 回复(4)
    预处理程序cpp是来展开宏等其他操作,(-E选项表示预处理后停止编译过程)
    $gcc CE hello.c Co hello.cpp
2. 适当编译
    将hello.cpp 编译为目标代码,(-x选项声明从指定的步骤开始编译,这里为,cpp-output)
阅读(1131) 回复(2)
在/home目录下安装了一个新版的gcc,可是要使用了?用的还是原来的啊?
gcc -v显示也还是原来的版本?
哪位能详细的说下该怎么才能使用新的编译,感激了
阅读(4046) 回复(5)
请问,用gcc 编译多个源文件时是用哪个命令啊。
#gcc -c testfun.c //将testfun.c编译成testfun.o
#gcc -c test.c //将test.c编译成test.o
#gcc -o testfun.o test.o -o test //将testfun.o和test.o链接成test
类似以上这样子去编译对吗,我照以上的方法也不能通过,我是有好多个源文件。请问方法是正确的吗?
阅读(849) 回复(3)
gcc编译流程及编译选项分析
摘自《嵌入式linux应用程序开发详解》第三章
GNU CC(简称为 gcc)是 GNU 项目中符合 ANSI C 标准的编译系统,能够编译用 C、C++和 Object C 等语言编写的程序。gcc 不
仅功能强大,而且可以编译如 C、C++、Object C、Java、Fortran、Pascal、Modula-3 和 Ada 等多种语言,而且 gcc 又是一个交
叉平台编译器,它能够在当前 CPU 平台上为多种不同体系结构的硬件平台开发软件, 因此尤其适合在嵌入式领域的开发编...
阅读(729) 回复(0)
和这个问题,是一样的,/article.asp?id=879。。 可是按他的方法解决不了
sudo apt-get install build-essential,然后就出现30.2m的空间将被使用,我输入y同意,然后有个百分数,到100%后,突然跳到一个界面,最后说 update
fix-missing
阅读(3192) 回复(16)
我用gcc -o test test.c编译test.c为什么执行test文件时没有任何结果,
而使用gcc -o abc test.c则可以得到正确结果?
是不是指定的文件名不能与源文件相同?
阅读(2295) 回复(10)
makefile写法
gcc_egcs使用
gcc常用选项对代码的影响
-O 编译选项
-O2 编译选项
-fomit-frame-pointer 编译选项
-fomit-frame-pointer && -O2
-fPIC 编译选项
-static 编译选项
AT&T的汇编格式
x86内联汇编
修饰寄存器列表
操作数约束
寄存器约束
内存操作数约束
修饰寄存器
阅读(1002) 回复(0)
GNU gcc参考:
http://www.shanghai.ws/gnu/gcc_1.htm
===================================================================================
Machine Dependent Options:
M680x0 Options
-m68020-40
-m68020-60
-mbitfield
-mnobitfield
-msoft-float
阅读(663) 回复(0)
盛拓传媒:
北京皓辰网域网络信息技术有限公司. 版权所有
北京市公安局海淀分局网监中心备案编号:
广播电视节目制作经营许可证:编号(京)字第1149号
ITPUB推荐文章解答你所有技术难题§7.1.2/4 C++03 / C++11 / C++14 &br&&blockquote&A &b&static&/b& local variable in an &b&extern &/b&&b&inline&/b& function always refers to the same object.&/blockquote&很明确,如果是 extern inline 里的 static 变量,那么就一定是同一个对象。所以问题中的「编译器进行展开的话就有了多份static变量」不成立。&br&&br&至于非「extern」的 inline 函数,已经是 internal linkage / no linkage 的函数了,不同翻译单元里的版本之间也已经是不相干的东西,和题目中的疑问不冲突了。&br&&br&p.s. 明明有标准文档,为什么要通过各种「实验」来猜测结论呢?即便做实验、看汇编,忙了半天最后发现结果与标准里的描述不符,那么其实也只能是某个实现出错了,而不是标准出错了呀。编译器也要按照基本法嘛。
§7.1.2/4 C++03 / C++11 / C++14 A static local variable in an extern inline function always refers to the same object.很明确,如果是 extern inline 里的 static 变量,那么就一定是同一个对象。所以问题中的「编译器进行展开的话就有了多份static…
上面各位大大说得很对了,但我在这里想黑一下龙书。&br&&br&龙书的一大特点就是parsing部分讲得特别多(足足有半本啊),但这也导致了一个最大的缺点:对于新手来说,细节掩盖了本质。&br&&br&另一本书《Programming Language Pragmatic》把parsing 部分讲得很简略,就是简单粗暴的三步:需要做什么(描述问题)、怎么做(描述算法)、怎么写成程序。比如词法分析部分讲完automaton后直接告诉你,DFA有两种实现方式:&br&1. Nested case statement (手写)&br&2. table-driven (自动生成)&br&&br&然后给了你代码框架(这里以nested case statement 为例):&br&&img data-rawheight=&2048& data-rawwidth=&1536& src=&/8bd75aec91f1030ebb0d84e_b.jpg& class=&origin_image zh-lightbox-thumb& width=&1536& data-original=&/8bd75aec91f1030ebb0d84e_r.jpg&&&br&然后告诉你最外层case覆盖DFA的所有状态,内层每个case覆盖这个状态的所有转换。特别清晰简单有木有!!!编程时候直接填空就行了!!&br&&br&所以,龙书就是专门坑新手的,不懂不是你的错。&br&&br&或者你了解一些原理之后可以去看《编程语言实现模式》,这本书里的词法分析直接用了LL(1),教你直接从正则表达式转换程序,连DFA都省了,你聪明的话顺便还悟出了scannerless parser 的原理,多好。
上面各位大大说得很对了,但我在这里想黑一下龙书。 龙书的一大特点就是parsing部分讲得特别多(足足有半本啊),但这也导致了一个最大的缺点:对于新手来说,细节掩盖了本质。 另一本书《Programming Language Pragmatic》把parsing 部分讲得很简略,就是…
&p&不是有这本书吗?&/p&&br&&p&&a href=&///?target=http%3A//.cn/book/1215& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&图灵社区 : 图书
: 两周自制脚本语言&i class=&icon-external&&&/i&&/a&&/p&&br&&p&还有这本:&/p&&br&&p&&a href=&///?target=http%3A//.cn/book/1159& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&图灵社区 : 图书
: 自制编程语言&i class=&icon-external&&&/i&&/a&&/p&
不是有这本书吗?
还有这本:
我不知道你的系统哪里有问题,刚刚测试过,win10完全正常编译程序,昨天刚从win8改成win10,刚看到你的问题时,正在床上迷迷糊糊,你的问题吓得我从床上滚了下来,开机键差点把电脑按翻,保存文件时的路径错了五六次,吓得室友拿我当精神病,说这胖子今天疯了,当我的代码正常运行的时候,我不由得感叹,题主我[此处被举报不友善]…&br&&img src=&/64c1c6d5d21a9fe7f885e15439a67bac_b.jpg& data-rawheight=&583& data-rawwidth=&720& class=&origin_image zh-lightbox-thumb& width=&720& data-original=&/64c1c6d5d21a9fe7f885e15439a67bac_r.jpg&&&br&&br& 重装系统吧题主…&br&感谢轮子哥么么哒…
我不知道你的系统哪里有问题,刚刚测试过,win10完全正常编译程序,昨天刚从win8改成win10,刚看到你的问题时,正在床上迷迷糊糊,你的问题吓得我从床上滚了下来,开机键差点把电脑按翻,保存文件时的路径错了五六次,吓得室友拿我当精神病,说这胖子今天疯…
编译器优化以后,这种wrap不会带来额外开销。&br&&br&对于这种纯粹的wrap,也就是wrap_func简单的调用原函数时,编译器会优化处理为一个函数(vs 2012)或者复制一份(gcc 4.8)。不论是哪种方式,对效率都没有影响。比如如下代码:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&cp&&#include &stdio.h&&/span&
&span class=&kt&&int&/span& &span class=&nf&&add&/span&&span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&y&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&n&&y&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kt&&int&/span& &span class=&nf&&wrap_add&/span&&span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&kt&&int&/span& &span class=&n&&y&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&n&&add&/span&&span class=&p&&(&/span&&span class=&n&&x&/span&&span class=&p&&,&/span& &span class=&n&&y&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&kt&&int&/span& &span class=&nf&&main&/span&&span class=&p&&(&/span&&span class=&kt&&int&/span& &span class=&n&&argn&/span&&span class=&p&&,&/span& &span class=&kt&&char&/span& &span class=&o&&*&/span&&span class=&n&&argv&/span&&span class=&p&&[])&/span&
&span class=&p&&{&/span&
&span class=&n&&printf&/span&&span class=&p&&(&/span&&span class=&s&&&add = %p&/span&&span class=&se&&\n&/span&&span class=&s&&&&/span&&span class=&p&&,&/span& &span class=&n&&add&/span&&span class=&p&&);&/span&
&span class=&n&&printf&/span&&span class=&p&&(&/span&&span class=&s&&&wrap_add = %p&/span&&span class=&se&&\n&/span&&span class=&s&&&&/span&&span class=&p&&,&/span& &span class=&n&&wrap_add&/span&&span class=&p&&);&/span&
&span class=&n&&printf&/span&&span class=&p&&(&/span&&span class=&s&&&r = %d&/span&&span class=&se&&\n&/span&&span class=&s&&&&/span&&span class=&p&&,&/span& &span class=&n&&wrap_add&/span&&span class=&p&&(&/span&&span class=&mi&&1&/span&&span class=&p&&,&/span& &span class=&mi&&2&/span&&span class=&p&&));&/span&
&span class=&k&&return&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&如果是vs,我们会看到add和wrap_add是同一个地址(注意使用release模式编译);&br&如果是gcc,我们可以看到wrap_add函数如下(注意打开O2)&br&&div class=&highlight&&&pre&&code class=&language-text&&(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x &+0&: mov
0x8(%esp),%eax
0x &+4&: add
0x4(%esp),%eax
0x &+8&: ret
&/code&&/pre&&/div&&br&如果考虑更复杂的情况,比如wrap_add在调用add做了一些简单的处理,如使用printf进行输出,编译器会有几种优化方式:&br&1. 直接将额外操作(如printf)放到调用处(如main),然后再直接调用add&br&2. 直接复制一份add的代码在wrap_add调用add的地方&br&3. 处理额外操作(如printf)以后直接通过jmp跳转到add处&br&不论是哪种,对效率都不会有什么影响。&br&比如如下代码:&br&&div class=&highlight&&&pre&&code class=&language-text&&int add(int x, int y)
return x +
int wrap_add(int x, int y)
printf(&Wrap!\n&);
return add(x, y);
&/code&&/pre&&/div&&p&我们看到汇编代码如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x &+0&: sub
$0x1c,%esp
0x &+3&: movl
$0x8048570,(%esp)
0x080484ca &+10&: call
0x8048330 &puts@plt&
0x080484cf &+15&: mov
0x20(%esp),%eax // Add的实现
0x &+19&: add
0x24(%esp),%eax // Add的实现
0x &+23&: add
$0x1c,%esp
0x080484da &+26&: ret
&/code&&/pre&&/div&如果add很长,复制不划算则会转为jmp:&br&&div class=&highlight&&&pre&&code class=&language-text&&int add(int x, int y)
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; }
if ((x & 1) == 0) { y += x && 1; x &&= 1; } // 让函数变长
return x +
int wrap_add(int x, int y)
printf(&Wrap!\n&);
return add(x, y);
&/code&&/pre&&/div&则汇编代码如下:&br&&div class=&highlight&&&pre&&code class=&language-text&&(gdb) disassemble wrap_add
Dump of assembler code for function wrap_add(int, int):
0x &+0&: push
0x &+1&: push
0x &+2&: sub
$0x14,%esp
0x &+5&: mov
0x20(%esp),%ebx
0x &+9&: mov
0x24(%esp),%esi
0x0804851d &+13&: movl
$0x80485d0,(%esp)
0x &+20&: call
0x8048330 &puts@plt&
0x &+25&: mov
%ebx,0x20(%esp)
0x0804852d &+29&: mov
%esi,0x24(%esp)
0x &+33&: add
$0x14,%esp
0x &+36&: pop
0x &+37&: pop
0x &+38&: jmp
0x80484b0 &add(int, int)& // 跳转到add
&/code&&/pre&&/div&
编译器优化以后,这种wrap不会带来额外开销。 对于这种纯粹的wrap,也就是wrap_func简单的调用原函数时,编译器会优化处理为一个函数(vs 2012)或者复制一份(gcc 4.8)。不论是哪种方式,对效率都没有影响。比如如下代码: #include &stdio.h&
int add(i…
要看楼主想问的是实现层,目标层,还是混合的调用栈。&br&&br&下面假想一个场景,用C来实现一个Python解释器。那么C是实现层,Python是目标层。CPython就是这样的一个实例。&br&&br&假如要打印实现层的调用栈,那么最简单的办法是利用操作系统及其自带的库所提供的功能。&br&比如在Windows上用CaptureStackBackTrace() &a href=&///?target=http%3A///questions/5693192/win32-backtrace-from-c-code& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/quest&/span&&span class=&invisible&&ions/5693192/win32-backtrace-from-c-code&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&在Unix系上用backtrace() &a href=&///?target=http%3A//linux.die.net/man/3/backtrace& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&linux.die.net/man/3/bac&/span&&span class=&invisible&&ktrace&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&之类的。也可以用比较苦逼的办法,那就是自己手工分析stack pointer与frame pointer然后根据找到的栈帧信息去查找原本的函数是什么。HotSpot JVM里就有自己实现这样的功能。&br&&br&假如要打印目标层的调用栈,那在一个简易解释器里非常好办。&br&简易解释器通常不把目标语言的栈帧放在系统栈上,而是自己单独分配和管理一个“解释器栈”。&br&用上面假想的场景,解释器可能会有C语言声明的结构体来描述栈帧信息,而且会有显式或隐式的链式结构非常容易遍历。例如CPython的PyFrameObject &a href=&///?target=http%3A//svn.python.org/projects/python/tags/r26b3/Include/frameobject.h& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&svn.python.org/projects&/span&&span class=&invisible&&/python/tags/r26b3/Include/frameobject.h&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&这种要如何打印调用栈就不用多说了吧,就遍历一普通的链表而已。&br&&br&要打印混合的调用栈就比较麻烦一些。&br&怎样会出现混合的调用栈呢?假如目标层的栈帧也是在系统栈上分配的话,系统栈就会同时包含实现层和目标层两种栈帧,这俩栈就“混合”成一个栈了。HotSpot VM的Java线程的调用栈就是这样。通常要爬这样的调用栈就得自己分析stack pointer与frame pointer然后一层层挖到栈底了。
要看楼主想问的是实现层,目标层,还是混合的调用栈。 下面假想一个场景,用C来实现一个Python解释器。那么C是实现层,Python是目标层。CPython就是这样的一个实例。 假如要打印实现层的调用栈,那么最简单的办法是利用操作系统及其自带的库所提供的功能。 …
讨论之前先说一下,结合上面的补充说明来看,这个问法其实有一点点瑕疵,这也导致了大家在回答时的一些误会。题主这么问,是因为 C++ 确实提供了函数模板的参数类型推导(通过&b&调用方提供&/b&的信息,自动推断并填充到模板参数,从而避免用户手动指明模板参数)。&br&&br&这样看,这个问题的正确提法应该是这样的:&br&&br&&b&“C++ template 为什么不能像典型的参数类型推导那样,通过判断调用方提供的返回值类型,将其自动填充到模板参数,从而避免用户手动指明模板参数?”&/b&&br&&br&-------------------------------------&br&&br&看了一下现有的回答,发现大家在 “类型推导” (Type Deduction) 上,其实没有在说同一件事,所以我觉得有必要先澄清一下这个概念。&br&&br&&a data-hash=&1e2cccc3ce33& href=&///people/1e2cccc3ce33& class=&member_mention& data-editable=&true& data-title=&@Milo Yip& data-tip=&p$b$1e2cccc3ce33& data-hovercard=&p$b$1e2cccc3ce33&&@Milo Yip&/a&
同学在 &a data-hash=&bdca6c289bad& href=&///people/bdca6c289bad& class=&member_mention& data-editable=&true& data-title=&@黄柏炎& data-tip=&p$b$bdca6c289bad& data-hovercard=&p$b$bdca6c289bad&&@黄柏炎&/a&
同学答案的评论中提到:“并不是从调用方推导。C++14可以靠return的类型推导。”
&br&那么 &b&&C++14 返回值类型推导&&/b& 和题主问到的 &b&&函数模板参数类型推导&&/b& 是一码事吗?&br&&br&答案是否定的。我这里详细说明一下。&br&&br&题主的例子中,所谓 &推导& 指的是编译器在&b&某些情况&/b&下,可以根据调用方提供的信息来补全用户未提供的模板参数,是模板实例化 (template instantiation) 的一个步骤,发生的时机是在函数模版的&b&调用时&/b&(invoke time of function template)。也就是说,当需要的时候,每次模版函数的调用,均会 (根据调用方提供的信息) 触发一次潜在的模板参数类型推导。&br&&br&而 &a data-hash=&d073f194bcabc1cec5ef69d0b534de99& href=&///people/d073f194bcabc1cec5ef69d0b534de99& class=&member_mention& data-editable=&true& data-title=&@空明流转& data-tip=&p$b$d073f194bcabc1cec5ef69d0b534de99& data-hovercard=&p$b$d073f194bcabc1cec5ef69d0b534de99&&@空明流转&/a& , &a data-hash=&ecc0ec035f& href=&///people/ecc0ec035f& class=&member_mention& data-editable=&true& data-title=&@vczh& data-tip=&p$b$ecc0ec035f& data-hovercard=&p$b$ecc0ec035f&&@vczh&/a&&a data-hash=&1e2cccc3ce33& href=&///people/1e2cccc3ce33& class=&member_mention& data-editable=&true& data-title=&@Milo Yip& data-tip=&p$b$1e2cccc3ce33& data-hovercard=&p$b$1e2cccc3ce33&&@Milo Yip&/a&
等同学在答案或评论中提到的 &C++14 返回值类型推导&,则分为普通函数和模板函数两种情况:
&br&&br&1.
当为普通函数时,返回值类型推导是函数体的一部分,发生在函数定义 (function definition) 时。举个栗子,形如 auto foo(int typedArg) { return typedA } 的函数,在定义时已可完全确认返回值类型为 int 了。&br&2.
当为模板函数时,返回值类型推导&b&仍为&/b&函数体的一部分,但需根据其&是否依赖模板参数类型&来决定发生于定义时还是实例化时。当返回值类型依赖模板参数类型时,情形正如 &a data-hash=&ecc0ec035f& href=&///people/ecc0ec035f& class=&member_mention& data-editable=&true& data-title=&@vczh& data-tip=&p$b$ecc0ec035f& data-hovercard=&p$b$ecc0ec035f&&@vczh&/a&
同学举的例子;当返回值类型不依赖模板参数类型时,则退化为 1. 中的普通函数调用情况。 &br&&br&请注意,对于 2. 中提到的 &a data-hash=&ecc0ec035f& href=&///people/ecc0ec035f& class=&member_mention& data-editable=&true& data-title=&@vczh& data-tip=&p$b$ecc0ec035f& data-hovercard=&p$b$ecc0ec035f&&@vczh&/a&
同学举的例子,调用方仍需提供模板参数类型,无论是借助编译期推导还是手工填充。 &br&&br&总得来说,&C++14 返回值类型推导& 是一个正向过程,只是语法上的一种简化 (syntax simplification),而语义上与原来的函数完全一致。而题主问到的 &函数模板参数类型推导& 是一个反向过程,在语义上,返回值的类型&接受推导&和&不接受推导&会导致截然不同的函数特化和调用。&br&&br&-------------------------------------&br&&br&好了,辨别清楚了概念,现在我们来正面回答这个题目:&br&&br&为了尽可能与 C 保持语法和语义上的兼容性,在 C++ 中,对于函数的调用方而言,返回值总是可以忽略的。&br&&br&也就是说,对于给定的函数&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&kt&&int&/span& &span class=&nf&&foo&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&调用方可以这么写:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&n&&foo&/span&&span class=&p&&();&/span&
&span class=&c1&&// 忽略返回值&/span&
&/code&&/pre&&/div&&br&对于模版函数而言,如果依赖返回值做模板的类型推导,就会出现由于调用信息不全导致的二义性。&br&&br&还是刚才这个例子,我们改为对应的函数模版,&br&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&k&&template&/span& &span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span&
&span class=&n&&T&/span& &span class=&n&&foo&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&n&&T&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&假如我们允许借助返回值来推导(如下所示)&br&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&kt&&int&/span& &span class=&n&&a&/span& &span class=&o&&=&/span& &span class=&n&&foo&/span&&span class=&p&&();&/span&
&span class=&c1&&// 特化为 foo&int&()&/span&
&span class=&kt&&double&/span& &span class=&n&&b&/span& &span class=&o&&=&/span& &span class=&n&&foo&/span&&span class=&p&&();&/span&
&span class=&c1&&// 特化为 foo&double&()&/span&
&/code&&/pre&&/div&&br&那么当调用方像之前的例子那样调的时候,编译器就没办法处理了:&br&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&n&&foo&/span&&span class=&p&&();&/span& &span class=&c1&&// 报错,因为缺乏足够信息做模板实例化&/span&
&/code&&/pre&&/div&&br&正如 &a data-hash=&bdca6c289bad& href=&///people/bdca6c289bad& class=&member_mention& data-editable=&true& data-title=&@黄柏炎& data-tip=&p$b$bdca6c289bad& data-hovercard=&p$b$bdca6c289bad&&@黄柏炎&/a& 同学所提到的,函数重载时,情况虽略有不同,导致了语义上的处理稍有不同,但最后也产生了类似的效果。 &br&&br&那么总结一下,一句话结论——&b&“为了与C保持兼容,返回值并非是调用函数时的必要条件,因此函数模版类型推导和函数重载都不能且不应依赖返回值。”&/b&&br&&br&-------------------------------------&br&&br&如果你只想了解这个问题本身,那么到刚才的一句话结论就可以结束了。然而,对模板而言,函数返回值与函数签名之间的关系实际上要更复杂一些。咱们刚刚也提到,函数模版类型推导和函数重载,看起来在语法上具有某种&b&形式上的一致性&/b&,两者在语义上是有所不同的。如果您感兴趣,可以接着往下读,我们刨根问底一下,看看返回值究竟在函数签名中扮演了什么角色,顺便弄清楚两者究竟有何不同。&br&&br&-------------------------------------&br&&br&先解释一下函数类型 (Function Type) 和函数签名 (Function Signature) 吧。&br&&br&在 C++ 中,函数类型 (Function Type) 与函数签名 (Function Signature) 是两个完全不同的概念。在我的理解中,前者主要是给程序员用的,通常用来定义函数指针 (形如 void(*)() ) 和函数对象 (形如 std::function&void()&);后者主要是给编译器用的,通常用于重载决议 (Overloading Resolution),模版特化 (Template Specialization) 及相关的类型推导 (Type Deduction),链接时生成独一无二的全局标识 (Name Mangling)。&br&&br&标准规定 (见 1.3.11 对函数签名的说明和 14.5.5.1 对模版函数特化时签名的补充说明):&br&&br&&ol&&li&对于普通函数(非模版函数),函数的签名包括未修饰的函数名 (function name) ,参数类型列表 (parameter type list)和所在类或命名空间名 (class and namespace name)&/li&&li&对于类成员函数,函数的签名除了 1 中提到的以外,还包括 cv 修饰符 (const qualifier and volatile qualifier) 和引用修饰符 (ref qualifier) &/li&&li&对于函数模板,函数的签名除了 1 和 2 中提到的以外,还包括返回值类型和模板参数列表&/li&&li&对于函数模板的特化 (function template specilization),函数的签名除了 1, 2 和 3 中提到的以外,还包括为该特化所匹配的所有模板参数(无论是显式地指定还是通过模板推导隐式地得出)&/li&&/ol&&br&-------------------------------------&br&&br&下面,我们先来挨个看看如何用标准来解释上面的几种行为,再来看看标准为什么对函数的签名做这样的规定。&br&&br&&b&Q1: 普通的函数重载时发生了什么?&/b&&br&A1. 函数的重载决议机制,依赖了函数签名的独特性。标准的 1 和 2 中,并没有提到返回值类型,因此我们可以认为,仅有返回值不同的函数重载是无效的,因为根据标准,它们签名是完全一致的。&br&&br&例如下面两个函数:&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&kt&&void&/span& &span class=&nf&&bar&/span&&span class=&p&&()&/span& &span class=&p&&{}&/span&
&span class=&kt&&int&/span& &span class=&nf&&bar&/span&&span class=&p&&()&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&p&&}&/span&
&/code&&/pre&&/div&&br&在函数定义(不用等到调用)的时候就无法通过编译,因为同一个编译单元 (translation unit) 中出现了两个签名一致的函数。&br&&br&&b&Q2: 函数模板实例化时发生了什么?&/b&&br&A2. 根据 3 和 4 可以知道,通过在签名中包含返回值类型和模板参数列表,一个函数模板及其若干特化得到了某种程度上的强类型保证,当所提到的类型不一致时,编译器有机会报出对应的错误。&br&&br&&b&Q3: 函数模板实例化时,如果触发了类型推导,发生了什么?&/b&&br&A3. 当类型信息提供不完全,需要编译器推导时,从 3 可以知道,由于签名中已经包含了所有必要的信息,编译器有能力借助签名本身得知必要的类型信息并进行补全。&br&&br&&b&Q4: 函数模板实例化时,跟返回值相关的行为是什么?&/b&&br&A4. 返回值是签名的一部分,这个事实导致了下面的定义方式成为可能:&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&k&&template&/span&&span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span& &span class=&kt&&int&/span& &span class=&n&&f&/span&&span class=&p&&()&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&mi&&0&/span&&span class=&p&&;&/span& &span class=&p&&}&/span&
&span class=&k&&template&/span&&span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span& &span class=&kt&&double&/span& &span class=&n&&f&/span&&span class=&p&&()&/span& &span class=&p&&{&/span& &span class=&k&&return&/span& &span class=&mf&&0.0&/span&&span class=&p&&;&/span& &span class=&p&&}&/span&
&/code&&/pre&&/div&&br&请注意,跟 Q1 中 &定义时就无法通过编译& 不同的是,这两个同名同参的函数的定义是可以通过编译的,因为根据 3 可以知道,返回值是签名的一部分,这两个函数的签名是不同的。但实际使用时,根据我们之前的“一句话结论”中提到的,(为了与C保持兼容,返回值并非是调用函数时的充分必要条件),&b&当真正的调用发生时&/b&,编译器有可能缺乏足够的信息去了解返回值的类型,也就不知道该把函数调用决议到哪一个函数定义上去。这个错误理论上来讲可以是一个链接错误,但由于在函数定义的编译阶段已经可以得到了两个不同的函数,那么实际结果是在&b&调用方的编译阶段&/b&就可以报出错误了。&br&&br&&b&Q5: 模板特化和重载决议同时触发时,会发生什么?&/b&&br&A5. 喜欢刨根究底的同学肯定会产生这个疑问,这里我们举两个例子:&br&&br&例子1,这个例子中,我们不仅期望函数模板会自动推导模板参数,而且期望编译器能够选择正确的重载版本去调用&br&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&k&&template&/span&&span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span&
&span class=&kt&&int&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&n&&T&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&mi&&1&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&template&/span&&span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span&
&span class=&kt&&int&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&n&&T&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&mi&&2&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kt&&int&/span& &span class=&n&&main&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&cout&/span& &span class=&o&&&&&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&endl&/span&&span class=&p&&;&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&cout&/span& &span class=&o&&&&&/span& &span class=&n&&f&/span&&span class=&p&&((&/span&&span class=&kt&&int&/span&&span class=&o&&*&/span&&span class=&p&&)&/span&&span class=&mi&&0&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&endl&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&&br&例子2,这个例子中,我们重载了模版函数和非模板函数,和例子1一样,我们不仅期望 (在必要时) 函数模板会自动推导模板参数,而且期望 (在必要时) 能够选择正确的重载版本去调用:&br&&br&&div class=&highlight&&&pre&&code class=&language-cpp&&&span class=&cp&&#include &string& &/span&
&span class=&cp&&#include &iostream& &/span&
&span class=&k&&template&/span&&span class=&o&&&&/span&&span class=&k&&typename&/span& &span class=&n&&T&/span&&span class=&o&&&&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&n&&T&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&s&&&Template&&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&string&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&kt&&int&/span&&span class=&o&&&&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&s&&&Nontemplate&&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&kt&&int&/span& &span class=&n&&main&/span&&span class=&p&&()&/span&
&span class=&p&&{&/span&
&span class=&kt&&int&/span& &span class=&n&&x&/span& &span class=&o&&=&/span& &span class=&mi&&7&/span&&span class=&p&&;&/span&
&span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&cout&/span& &span class=&o&&&&&/span& &span class=&n&&f&/span&&span class=&p&&(&/span&&span class=&n&&x&/span&&span class=&p&&)&/span& &span class=&o&&&&&/span& &span class=&n&&std&/span&&span class=&o&&::&/span&&span class=&n&&endl&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&br&&br&这里我就卖个关子,不给出解释了,大家也先不要急着到编译器里去验证,根据我们前面讲述的知识,可以先试着通过思考,回答下面几个问题:&br&&br&&ol&&li&这两个例子中的函数,在定义能通过编译吗?调用时能通过编译吗?&/li&&li&如果能够运行的话,编译器会做出我们期望的重载决议和类型推导吗?&/li&&/ol&&br&弄明白了这两个例子,Q5的问题自然也就得到解答了。&br&&br&-------------------------------------&br&&br&好了,通过这一系列的追问,我们总算把相关的行为给解释清楚了。想清楚了上面这些细节,我们也就可以很轻松地认识到标准这么规定的理由,说穿了非常简单,就是两点:&br&&br&&ol&&li&&b&始终保证签名的全局唯一性。&/b&&/li&&li&&b&始终保证同一个模板的本体和其所有的特化,在签名上的相关性。&/b&&/li&&/ol&&br&具体地说,&br&&ul&&li&条目1使得函数签名这个机制被用于函数重载的决议成为可能&/li&&li&条目2使得函数签名这个机制被用于模板特化时的类型推导成为可能&/li&&/ul&&br&-------------------------------------&br&&br&嗯,这个问题还是蛮有趣的,不知不觉也讨论了这么多。&br&应该没有落下什么吧。那么先这样吧,有问题的话再补充。&br&&br&[ 注 ] &br&本文同时发在我的 blog (由于对 markdown 的支持,在那里或许可获得更好的阅读体验)&br&&a href=&///?target=http%3A///post/-type-deduction& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&[知乎] C++ template 为什么不能推导返回值类型?&i class=&icon-external&&&/i&&/a&
讨论之前先说一下,结合上面的补充说明来看,这个问法其实有一点点瑕疵,这也导致了大家在回答时的一些误会。题主这么问,是因为 C++ 确实提供了函数模板的参数类型推导(通过调用方提供的信息,自动推断并填充到模板参数,从而避免用户手动指明模板参数)…
首先抖机灵:如果题主想像的优化器是诸如浏览器里的JavaScript引擎的话,它们即便对代码做优化也不会改变函数的名字。所以“foo”不会被优化成“bar”。&br&其次:题主所给出的优化后的代码,与原始版本,语义是不一样的。所以一个符合ECMAScript规范的优化器是不可以把原始版本优化到题主所想的版本的。&br&&br&在这个例子中,原始版本中的 a && b && c 严格等价于 a && b && a (应用“复写传播”(copy propagation)优化可破)。这是因为 c 是一个普通的局部变量,而 foo() 中没有诸如 eval() 之类邪恶的可以偷偷修改局部变量值的东西。&br&另外,因为 a 与 b 都是普通参数,玩不了诸如带副作用的getter的花招;而它们参与的运算只有需要用到 ToBoolean() 的 && 运算符,而 ToBoolean() 不像 ToPrimitive() 有 valueOf() 花招可玩。所以这里可以确定在 && 表达式中,对 a 与 b 的求值都是无副作用的。&br&&br&然后看看两个版本的真值表就明了了。&br&原始版本,a && b && c (等价于 a && b && a):&br&&div class=&highlight&&&pre&&code class=&language-text&& ToBoolean(a) \ ToBoolean(b) | T | F |
-----------------------------+---+---+
T | a | b |
-----------------------------+---+---+
F | a | a |
-----------------------------+---+---+
&/code&&/pre&&/div&而题主所想像的“优化版本”,a && b:&br&&div class=&highlight&&&pre&&code class=&language-text&& ToBoolean(a) \ ToBoolean(b) | T | F |
-----------------------------+---+---+
T | b | b |
-----------------------------+---+---+
F | a | a |
-----------------------------+---+---+
&/code&&/pre&&/div&注意“左上角”那格不同,也就是说当 a 与 b 都为真值的时候,结果不一样。&br&&br&这是因为JavaScript(以及好一些其它语言,例如Python、Ruby等)在条件表达式中,不但带有短路求值的语义,而且表达式的结果并不会被统一到Boolean类型,而是会保持表达式的操作数原本的值(以及类型)。&br&于是
x && y 运算符实际的语义是:&br&&blockquote&当 x 为真值时,以 y 为结果;反之当 x 为假值时,以 x 为结果。&/blockquote&&br&像Groovy就跟这种设计不同,虽然也允许使用各种类型的值当作boolean用,但 && 与 || 运算符的结果还是要统一到boolean的。在这种条件下题主所想像的“优化”才是正确的。&br&&br&=======================================&br&&br&瞄了眼V8 4.4.0版(是有点老了…)的JIT编译结果,发现这个版本的V8无论用Crankshaft还是TurboFan编译出来的代码逻辑都是一样的,都是:&br&&div class=&highlight&&&pre&&code class=&language-js&&&span class=&kd&&function&/span& &span class=&nx&&foo&/span&&span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&,&/span& &span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&kd&&var&/span& &span class=&nx&&t&/span&&span class=&p&&;&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&t&/span& &span class=&o&&=&/span& &span class=&nx&&b&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&nx&&t&/span& &span class=&o&&=&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&t&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&t&/span& &span class=&o&&=&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&nx&&t&/span& &span class=&o&&=&/span& &span class=&nx&&t&/span&&span class=&p&&;&/span& &span class=&c1&&// no-op&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&nx&&t&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&也就是说虽然表面上看结果确实实现了短路求值的语义,但它要做的条件判断/跳转的次数却比真的完全按照短路求值来做要多。这个版本的代码,无论 a 与 b 值为什么,都要做两次条件判断/跳转。&br&(扩展思考:看来V8对 && 生成的代码模式下,如果有1000个连续的 && 运算,即便第一个 && 的左操作数就是一个假值,这也得要做1000次条件判断/分支啊…orz)&br&&br&假想一下,通过tail duplication变换,可以把上面的代码变成这样:&br&&div class=&highlight&&&pre&&code class=&language-js&&&span class=&kd&&function&/span& &span class=&nx&&foo&/span&&span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&,&/span& &span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&b&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&c1&&// can constant-fold&/span&
&span class=&k&&return&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span& &span class=&c1&&// untaken branch&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span& &span class=&c1&&// always taken branch&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&再做条件常量传播(conditional constant propagation),就变成:&br&&div class=&highlight&&&pre&&code class=&language-js&&&span class=&kd&&function&/span& &span class=&nx&&foo&/span&&span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&,&/span& &span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&b&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span& &span class=&k&&else&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&a&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&这样当 a 为假值时,要做的条件判断/分支会比V8 4.4.0 Crankshaft与TurboFan生成的代码要少一次。&br&&br&做到这样也就差不多了。毕竟,根据本回答开头提到的真值表,4个方格里只有1个是 b 的话,也需要两次判断才可以到达这个方格,最长路径上做两次判断是跑不掉的了。
首先抖机灵:如果题主想像的优化器是诸如浏览器里的JavaScript引擎的话,它们即便对代码做优化也不会改变函数的名字。所以“foo”不会被优化成“bar”。 其次:题主所给出的优化后的代码,与原始版本,语义是不一样的。所以一个符合ECMAScript规范的优化器…
我觉得题目本身的定位就有一些问题。。&br&&br&这里暂且分成两个方面考虑:&br&&ul&&li&TypeScript 相比 ES6/7 有什么优势;&/li&&li&tsc 相比 babel 有什么优势。&/li&&/ul&&br&&b&1. TypeScript 相比 ES6/7 有什么优势&/b&&br&&br&ES6/7 并非是一无是处的,比如 ES Module 就是一个很好的设计,其静态特性保证了实体的唯一性,也确实能够为静态分析提供很大的帮助。(比如已经可以基本保证重构时的引用完备性,不会误伤不会遗漏)另外也提供了大量的语法糖为开发者提供便利。&br&&br&&b&只是,对于大型工程而言,这样可能还不够。&/b&&br&&br&TypeScript 的定位是静态类型语言,而非类型检查器。&br&&br&从 Tooling 上来说能提供的优势也远不止类型检查,比如很直接的一点就是 Intellisense over Compilation Error,一段代码,当你写了前两个字母没有提示了,或者写完就有个红波浪了,那它就已经错了,而不是等到编译的时候再告诉你第几行有错。(可能很多人在大学学 C 语言的时候已经有斯德歌尔摩综合&del&症&/del&征了?)&br&&br&而作为开发人员唯一要做的,就只是在接口处标明类型,其它的内部过程交由 ts 推理就好。&br&&br&而如果不进行标注,只靠自顶向底的类型检查的话,那么当出现类型不相符的时候,就会出现和运行时一样的错误栈,又如何知道到底是哪一步到哪一步写错了呢?&br&&br&极端一点的方面,如果是在 FP,组合变换了十几二十次,然后还肿么能明确每一步拿到的都是什么呢?如果有 ts 的话,只要你提供了初始的标注,那么后面每一步的过程也都是明确的了,哪怕返回到中间改了一个什么东西也能立刻知道对后面有没有影响。&br&&br&&b&2. tsc 相比 babel 有什么优势&/b&&br&&br&这点很难说,因为二者的定位并不相同。&br&&br&Babel 是一个 JavaScript 预处理器的基础设施,虽然本身为 es6 而生,但现在 es6 对 babel 而言也只是一个普通的 preset 而已。&br&&br&理论上完全可以基于 babel 实现一个 ts 的编译器,但上面就已经谈到了,ts 的核心并不在那一个编译器。如果我们从 npm 上下一个 typescript 的发布版本,可以看到,在 lib 中,除了 tsc 之外,还有一个很重要的 tsserver。ts 本身也贯彻语言即服务的理念(参考 C# 的 Roslyn),所有编辑器、IDE 都能很方便地利用,避免重复造轮子。&br&&br&总的来说,tsc 的功能没有 babel 多,扩展性也没有 babel 强。但对于 tsc 所覆盖的那一部分(也就是 ts),tsc 做得比 babel 更好更精。而 ts,很多时候也就恰恰是 JavaScript 工程实践中所最需要的那部分。
我觉得题目本身的定位就有一些问题。。 这里暂且分成两个方面考虑: TypeScript 相比 ES6/7 有什么优势;tsc 相比 babel 有什么优势。 1. TypeScript 相比 ES6/7 有什么优势 ES6/7 并非是一无是处的,比如 ES Module 就是一个很好的设计,其静态特性保证了…
做个编译器这种事情现在已经太简单了,去看一下LLVM的文档,试一下它给的几个例子,然后试着自己实现一个BrainFuck的解析器吧。&br&&br&别看这BrainFuck的语法真的很Fuck,但是玩起来挺有趣的。&br&&br&像 &a data-hash=&641e9ec165af& href=&///people/641e9ec165af& class=&member_mention& data-editable=&true& data-title=&@徐辰& data-hovercard=&p$b$641e9ec165af&&@徐辰&/a& 大大就用LLVM写了个带JIT的BrainFuck解析器(&a href=&///?target=https%3A///windoze/brainfuck& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&windoze/brainfuck · GitHub&i class=&icon-external&&&/i&&/a&),而且在test里面居然用BrainFuck写出了OO(&a href=&///?target=https%3A///windoze/brainfuck/blob/master/test/oobrain.b& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&brainfuck/oobrain.b at master · windoze/brainfuck · GitHub&i class=&icon-external&&&/i&&/a&)!&br&&br&另外,学一下Haskell也可以很方便实现一个编译器的,这门语言用来写Parser感觉就像它就是为写Parser而生的一样,有一篇经典的文章&a href=&///?target=http%3A//en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Write Yourself a Scheme in 48 Hours&i class=&icon-external&&&/i&&/a&。&br&&br&不过少年,你真的要在iOS上做开发么?
做个编译器这种事情现在已经太简单了,去看一下LLVM的文档,试一下它给的几个例子,然后试着自己实现一个BrainFuck的解析器吧。 别看这BrainFuck的语法真的很Fuck,但是玩起来挺有趣的。 像
大大就用LLVM写了个带JIT的BrainFuck解析器(
&p&当然能。我上高中第一次做出一个不带指针的pascal模拟器的时候,数据结构只学到链表,其它理论知识什么都不会。你既然有三年,肯把编译原理看懂个10%,好好学习算法导论的话,肯定也能写一个出来,而且比我高中的这个要强得多。&/p&&br&&p&还有,“大学4年写出一个编译器吗”这个说法是不对的,因为我写了一大堆实验用的,不是一个(逃&/p&
当然能。我上高中第一次做出一个不带指针的pascal模拟器的时候,数据结构只学到链表,其它理论知识什么都不会。你既然有三年,肯把编译原理看懂个10%,好好学习算法导论的话,肯定也能写一个出来,而且比我高中的这个要强得多。 还有,“大学4年写出一个编…
编译器自举一般都是编译器开发的一个里程碑事件,你所举的例子Pascal编译器,其第一版编译器是用Fortran写的,而这也是常见的编译器自举过程的几乎必走的一条路,即最开始使用X语言(如Fortran)实现Y语言(如Pascal)的编译器,即解决鸡与蛋的问题,所以你的问题二是不正确的,因为我们先要使用其它语言构建出我们的第一版编译器,之后成熟以后,就可以完全使用已经生成好的编译器来编译出我们的新编译器。&br&&br&而其实疑惑自举大部分就在鸡与蛋问题上,为什么可以自己可以编译自己,上面 &a data-hash=&ecc0ec035f& href=&///people/ecc0ec035f& class=&member_mention& data-hovercard=&p$b$ecc0ec035f&&@vczh&/a&基本上概括了语言的鸡与蛋问题。而鸡与蛋问题还包括了跨体系结构,即在X结构上有一个C编译器,而Y结构没有这样的C编译器,那么这时候要使用的话,使用的就是交叉编译,而C与Free Pascal都是这样的。而支持新的语言特性其实也算,即使用C++98写支持C++11的C++编译器。而等待时机成熟后,就又可以使用C++11来写支持C++14的C++编译器,随后可以使用C++14来写支持C++1z的C++编译器......这就是现代编译器支持新的语言特性的自举方式了,所以你看Clang就开始使用C++11的语法来开发新版本的C++编译器了。&br&&br&最后再来说你的第一个问题,你说的P代码其实就是中间代码输出形式,类似LLVM IR,而这个是编译器后端可以识别的形式,而编译器后端吃进去这样的中间代码,又会开始解析,然后根据体系结构的不同开始输出不同体系结构的汇编指令了,如X86,PPC等,输出这样的汇编指令后,就可以交给汇编器等更低级的工具去进行下一步骤了。
编译器自举一般都是编译器开发的一个里程碑事件,你所举的例子Pascal编译器,其第一版编译器是用Fortran写的,而这也是常见的编译器自举过程的几乎必走的一条路,即最开始使用X语言(如Fortran)实现Y语言(如Pascal)的编译器,即解决鸡与蛋的问题,所以你…
先做点笔记。&br&&br&KinomaJS现在是Marvell在赞助开发。看来曾经也有华人参与开发?&br&&br&KinomaJS的源码在Github:&a href=&///?target=https%3A///Kinoma/kinomajs& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&。&br&开源许可证为 Apache License v2。&br&其中JavaScript引擎的实现名叫XS6,源码在xs6目录:&a href=&///?target=https%3A///Kinoma/kinomajs/tree/master/xs6& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6 at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a& 整个引擎用C语言实现。是个很完整的JavaScript引擎实现。&br&“XS”自身没有版本号,后来的叫法是实现了什么版本的ECMAScript的XS就叫什么版本。所以XS6就是实现了ECMAScript 6的XS。&br&&br&这里有少量开发文档:&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/notes/notes.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/notes.html at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&粗略看下来,这个JavaScript引擎的核心部分的技术水平大概跟Brendan Eich 90年代中期在Netscape写的差不多吧…&br&就是因为实现方式还很简单所以加入各种ECMAScript新功能才会那么快。&br&&br&感觉KinomaJS要说有什么精华的话应该不在这JavaScript引擎里,而在周边库的设计与实现里吧…这我就没看了也没啥兴趣看。&br&&br&解释器状态的顶层结构:struct sxMachine / txMachine。常用变量名the。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6All.h%23L147& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6All.h at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&可以看到它有独立的解释器栈(txMachine::stack)和作用域栈(txMachine::scope)。&br&&br&它的Hosting API对解释器栈的使用方式跟Lua有相似之处:&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6API.c%23L1011& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6API.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&Hosting API的文档:&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/notes/Building%JS%2520API%2520with%2520XS6.md& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/Building a JS API with XS6.md at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&文档里演示的xsCall2()宏实际上就是往解释器栈里压入参数然后调用:&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&cp&&#define xsCall2(_THIS,_ID,_SLOT0,_SLOT1) \&/span&
&span class=&cp&& (xsOverflow(-4), \&/span&
&span class=&cp&& fxPush(_SLOT0), \&/span&
&span class=&cp&& fxPush(_SLOT1), \&/span&
&span class=&cp&& fxInteger(the, --the-&stack, 2), \&/span&
&span class=&cp&& fxPush(_THIS), \&/span&
&span class=&cp&& fxCallID(the, _ID), \&/span&
&span class=&cp&& fxPop())&/span&
&/code&&/pre&&/div&&br&词法分析器(lexer)是个典型的ad-hoc手写实现&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Lexical.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Lexical.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&语法分析器(parser)是个典型的手写递归下降式的,没有在表达式层面用运算符优先级(operator precedence parsing)算法。生成AST。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Syntaxical.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Syntaxical.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&字节码生成就是典型的后序遍历AST生成线性字节码。字节码是基于栈的形式。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Code.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Code.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&XS6将字节码编译器与解释器分离开来,可以允许在资源受限的使用场景中,先在宿主机器上把JavaScript源码编译成字节码序列化起来,然后在实际设备上只运行字节码(实际设备上就不需要有这个字节码编译器了)。&br&这是比较能体现XS6为嵌入式优化的地方。&br&&br&执行引擎是个字节码解释器,可选用switch-threaded或者token-threaded(基于GCC computed goto)。&br&在函数调用方面没啥优化,没有使用inline cache之类的基本优化技巧。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Run.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Run.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&话说这个解释器似乎在处理减法的时候有bug…我没有build来测试,但光读代码目测下面的代码会算出错误的结果:&br&&div class=&highlight&&&pre&&code class=&language-js&&&span class=&kd&&var&/span& &span class=&nx&&x&/span& &span class=&o&&=&/span& &span class=&o&&-&/span&&span class=&mh&&0x7fffffff&/span&
&span class=&kd&&var&/span& &span class=&nx&&y&/span& &span class=&o&&=&/span& &span class=&mh&&0x7fffffff&/span&
&span class=&kd&&var&/span& &span class=&nx&&z&/span& &span class=&o&&=&/span& &span class=&nx&&x&/span& &span class=&o&&-&/span& &span class=&nx&&y&/span& &span class=&c1&&// should be -&/span&
&/code&&/pre&&/div&有人想测试一下不?&br&&br&另外还有一个“高性能”版的解释器实现,在XS6目录之外,而在xslib里:&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs/sources/xslib/xsRun.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xsRun.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs/sources/xslib/xsAccelerator.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xsAccelerator.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&这个所谓“run loop accelerator”并不是XS6的组成部分,而是老的XS(XS5?)的实现。也就是说XS的解释器曾经有比较优化的实现,但新的XS6重写了字节码编译器和解释器之后,还没有把以前的优化移植过来。&br&&br&内存管理,GC堆的实现是典型的分段式(chunked),回收算法是典型的mark-sweep。&br&这里说的每一段叫做一个heap,是一个txSlot数组,直接malloc而来;heap之间形成单向链表,通过每个heap最开头的txSlot的next字段串起来。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Memory.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Memory.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&值的表现方式(value representation)是很肥的discriminated union,struct sxSlot / txSlot是也。&br&txSlot里的kind字段记录了tag,然后value字段记录了对应的值。&br&然而这个txSlot结构非常肥——它不但可以用来表示值,还可以用来表示其它各种东西,包括引用与对象自身。&br&所有值(txSlot)通过next字段串成一个单向链表。这个灵活的结构有多种用途。&br&&ul&&li&flag为XS_VALUE_FLAG的txSlot是值,其可能的包含的值包括function、array、string、boolean、number、regexp、host。&/li&&li&kind为XS_REFERENCE_KIND的txSlot是指向对象的引用。&/li&&li&kind为XS_INSTANCE_KIND的txSlot是对象自身。&/li&&/ul&&br&txMachine::firstHeap字段记录着当前GC堆的起点。它本质上是一个txSlot数组,所有元素都是相同大小的,便于管理,完全避开了heap parsibility的问题。其它像是Array或String内容的chunk之类都必须从某些txSlot可以引用到,所以marker/sweeper只要能找到那些txSlot就可以正确处理挂在下面的chunk了。&br&txMachine::freeHeap字段通过next链将当前free的txSlot串在一起构成freelist。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6All.h& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6All.h at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&对值的操作有不少实现在xs6API里:&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6API.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6API.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&对象布局/对象属性的实现&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Object.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Object.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs/sources/xslib/xsProperty.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xsProperty.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&创建对象是这样实现的:&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Object.c%23L102& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Object.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&n&&txSlot&/span&&span class=&o&&*&/span& &span class=&nf&&fxNewObjectInstance&/span&&span class=&p&&(&/span&&span class=&n&&txMachine&/span&&span class=&o&&*&/span& &span class=&n&&the&/span&&span class=&p&&)&/span&
&span class=&p&&{&/span&
&span class=&n&&txSlot&/span&&span class=&o&&*&/span& &span class=&n&&instance&/span&&span class=&p&&;&/span&
&span class=&n&&instance&/span& &span class=&o&&=&/span& &span class=&n&&fxNewSlot&/span&&span class=&p&&(&/span&&span class=&n&&the&/span&&span class=&p&&);&/span&
&span class=&n&&instance&/span&&span class=&o&&-&&/span&&span class=&n&&kind&/span& &span class=&o&&=&/span& &span class=&n&&XS_INSTANCE_KIND&/span&&span class=&p&&;&/span&
&span class=&n&&instance&/span&&span class=&o&&-&&/span&&span class=&n&&value&/span&&span class=&p&&.&/span&&span class=&n&&instance&/span&&span class=&p&&.&/span&&span class=&n&&garbage&/span& &span class=&o&&=&/span& &span class=&n&&C_NULL&/span&&span class=&p&&;&/span&
&span class=&n&&instance&/span&&span class=&o&&-&&/span&&span class=&n&&value&/span&&span class=&p&&.&/span&&span class=&n&&instance&/span&&span class=&p&&.&/span&&span class=&n&&prototype&/span& &span class=&o&&=&/span& &span class=&n&&the&/span&&span class=&o&&-&&/span&&span class=&n&&stack&/span&&span class=&o&&-&&/span&&span class=&n&&value&/span&&span class=&p&&.&/span&&span class=&n&&reference&/span&&span class=&p&&;&/span&
&span class=&n&&the&/span&&span class=&o&&-&&/span&&span class=&n&&stack&/span&&span class=&o&&-&&/span&&span class=&n&&value&/span&&span class=&p&&.&/span&&span class=&n&&reference&/span& &span class=&o&&=&/span& &span class=&n&&instance&/span&&span class=&p&&;&/span&
&span class=&n&&the&/span&&span class=&o&&-&&/span&&span class=&n&&stack&/span&&span class=&o&&-&&/span&&span class=&n&&kind&/span& &span class=&o&&=&/span& &span class=&n&&XS_REFERENCE_KIND&/span&&span class=&p&&;&/span&
&span class=&k&&return&/span& &span class=&n&&instance&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&也就是从txMachine::freeHeap一个新的txSlot出来,初始化到XS_INSTANCE_KIND以及栈上指定的prototype,然后的栈定的引用指向这个新的txSlot并设置其类别为引用XS_REFERENCE_KIND。&br&fxToInstance()函数可以从一个引用找出其指向的对象的txSlot*:&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Type.c%23L142& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Type.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&换句话说,对这样的JavaScript对象:&br&&div class=&highlight&&&pre&&code class=&language-text&&o = { x: 1, y: 2 }
&/code&&/pre&&/div&在内存里实际上是:&br&&div class=&highlight&&&pre&&code class=&language-text&&
[ reference ]
[ instance ]
next -& [ y: 2 ]
next -& NULL
&/code&&/pre&&/div&&br&&br&数组采用紧凑布局,数组元素放在一段连续的内存里(Chunk):&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs/sources/xslib/xsProperty.c%23L509& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xsProperty.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&数组对象标准库的实现:&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Array.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Array.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&&br&&br&XS6内部的符号驻留(symbol interning),用16位带符号整数类型txID为索引。&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&k&&struct&/span& &span class=&n&&sxSymbol&/span& &span class=&p&&{&/span&
&span class=&n&&txSymbol&/span&&span class=&o&&*&/span& &span class=&n&&next&/span&&span class=&p&&;&/span&
&span class=&n&&txID&/span& &span class=&n&&ID&/span&&span class=&p&&;&/span&
&span class=&n&&txInteger&/span& &span class=&n&&length&/span&&span class=&p&&;&/span&
&span class=&n&&txString&/span& &span class=&n&&string&/span&&span class=&p&&;&/span&
&span class=&n&&txSize&/span& &span class=&n&&sum&/span&&span class=&p&&;&/span&
&span class=&n&&txInteger&/span& &span class=&n&&usage&/span&&span class=&p&&;&/span&
&span class=&p&&};&/span&
&/code&&/pre&&/div&有若干内建的ID是这样处理的,例如mxID(the, _configurable):&br&&div class=&highlight&&&pre&&code class=&language-c&&&span class=&cp&&#define mxIDs the-&stackTop[-1 - mxIDsStackIndex]&/span&
&span class=&cp&&#define mxID(_ID) (((txID*)(mxIDs.value.code))[_ID])&/span&
&/code&&/pre&&/div&&br&剩下的JavaScript核心库的实现也都挺常规的…&br&正则表达式用的是&a class=& wrap external& href=&///?target=http%3A//www.pcre.org/& target=&_blank& rel=&nofollow noreferrer&&PCRE&i class=&icon-external&&&/i&&/a&库。&br&&br&XS6内建一个简单的“profiler”,作为一个小JavaScript引擎这倒是不错。&br&&a href=&///?target=https%3A///Kinoma/kinomajs/blob/master/xs6/sources/xs6Profile.c& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&kinomajs/xs6Profile.c at master · Kinoma/kinomajs · GitHub&i class=&icon-external&&&/i&&/a&
先做点笔记。 KinomaJS现在是Marvell在赞助开发。看来曾经也有华人参与开发? KinomaJS的源码在Github:。 开源许可证为 Apache License v2。 其中JavaScript引擎的实现名叫XS6,源码在xs6目录:
经典的文献有很多,但不一定都用得上;囿于个人能力,更难列出”必读“之类的单子。。&br&&br&找文献的话,可能有各种方法:&br&龙、虎、鲸等书每章最后的Reference;&br&PLDI/POPL/...每年的Best paper;&br&CompIiler相关高级课程的Reading Lists;&br&某个作者/团体评出的Most influence paper。。&br&&br&下面是个人体会:&br&1. 通读综述性/工具书性的文章/书籍,如《The garbage collection handbook》《The SSA Book》《Principle of Program Analysis》之类的。&br&2. 熟悉乃至参与相关的工具项目:经典的和当前的,工业界的和学术界的。。&br&&br&在这些过程中,自然会接触到各种文献,建立类似”知识图谱“的东西;&br&而当你能跟进state-of-art的工作,经典的差不多都知道,该看的大概也读不少了。。&br&&br&换言之,可以从下面两个角度来看(只是一种参考):&br&1. 怎样成为相关领域合格的Phd乃至做开创性的工作?&br&2. 怎样才能去相关领域的顶尖公司乃至自己做东西发大财?&br&幸运的是,CS某些领域(比如体系结构、编译)的人在两个层次都能混得比较爽。。&br&&br&补充一下相关工具/项目: &br&&b&JavaScript&/b&&br&引擎:V8、xxMonkey&br&分析:JSAI、TAJS&br&验证:&img src=&///equation?tex=%5Clambda_%7BJS%7D& alt=&\lambda_{JS}& eeimg=&1&&、JSCert 等等&br&&br&另外Self,Smalltalk有几篇经典的论文(Dynamic typing、Visual Machine相关)。&br&&br&&b&Infrastructure&/b&&br&编译:LLVM,Chris的硕士论文可以看看&br&分析:SUIF,Soot(针对Java的)&br&二进制分析:BAP、BitBlaze&br&&br&Microsoft CLR和DLR也可参考&br&&br&&b&Java、C、Haskel

我要回帖

更多关于 gcc编译器使用教程 的文章

 

随机推荐