编译语言符号表
㈠ 什么是符号表 符号表有哪些重要作用
符号表是一种用于语言翻译器(例如编译器和解释器)中的数据结构。在符号表中,程序源代码中的每个标识符都和它的声明或使用信息绑定在一起,比如其数据类型、作用域以及内存地址。
符号表的作用:符号表在编译程序工作的过程中需要不断收集、记录和使用源程序中一些语法符号的类型和特征等相关信息。这些信息一般以表格形式存储于系统中。
如常数表、变量名表、数组名表、过程名表、标号表等等,统称为符号表。对于符号表组织、构造和管理方法的好坏会直接影响编译系统的运行效率。
(1)编译语言符号表扩展阅读
编译程序按名字的不同种属分别使用许多符号表,如常熟表。变量名表过程名表函数入口名表。符号表的关键字域(段)就是符号名称等长关键字域(段)符号表。
不等长关键字段符号表,采用关键字词的索引结构。关键字在符号表的查找中相当重要hash函数构造方法就是以取关键字的值不同区分,如直接寻址伐数字分析法折叠法。
㈡ c语言基本符号
c语言基本符号
C++既可用于面向过程的结构化程序设计,又可用于面向对象的程序设计,是一种功能强大的混合型的程序设计语言。下面是我收集的关于c语言基本符号,希望大家认真阅读!
运算符的种类C语言的运算符可分为以下几类:
1.算术运算符
用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(–)共七种。
2.关系运算符
用于比较运算。包括大于(>)、小于(<)、等于(==)、>=)、小于等于(<=)和不等于(!=)六种。
3.逻辑运算符
用于逻辑运算。包括与(&&)、或(||)、非(!)三种。
4.位操作运算符
参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。
5.赋值运算符
用于赋值运算,分为简单赋值(=)、复合算术赋值(+=,-=,*=,/=,%=)和复合位运算赋值(&=,|=,^=,>>=,<<=)三类共十一种。
6.条件运算符
这是一个三目运算符,用于条件求值(?:)。
7.逗号运算符
用于把若干表达式组合成一个表达式(,)。
8.指针运算符
用于取内容(*)和取地址(&)二种运算。
9.求字节数运算符
用于计算数据类型所占的字节数(sizeof)。
10.特殊运算符
有括号(),下标[],成员(→,.)等几种。
1.C的数据类型
基本类型,构造类型,指针类型,空类型
2.基本类型的分类及特点
类型说明符 字节 数值范围
字符型char 1 C字符集
基本整型int 2 -32768~32767
短整型short int 2 -32768~32767
长整型 long int 4-214783648~214783647
无符号型 unsigned 20~65535
无符号长整型 unsigned long 4 0~4294967295
单精度实型 float 43/4E-38~3/4E+38
双精度实型 double81/7E-308~1/7E+308
3.常量后缀
L或l 长整型
U或u 无符号数
F或f 浮点数
4.常量类型
整数,长整数,无符号数,浮点数,字符,字符串,符号常数,转义字符。
5.数据类型转换
·自动转换
在不同类型数据的混合运算中,由系统自动实现转换,由少字节类型向多字节类型转换。不同类型的量相互赋值时也由系统自动进行转换,把赋值号右边的类型转换为左边的类型。
·强制转换
由强制转换运算符完成转换。
6.运算符优先级和结合性
一般而言,单目运算符优先级较高,赋值运算符优先级低。算术运算符优先级较高,关系和逻辑运算符优先级较低。多数运算符具有左结合性,单目运算符、三目运算符、赋值。
7.表达式
表达式是由运算符连接常量、变量、函数所组成的式子。每个表达式都有一个值和类型。表达式求值按运算符的优先级和结合性所规定的顺序进行。
表示输出类型的格式字符 格式字符意义
d 以十进制形式输出带符号整数(正数不输出符号)
o 以八进制形式输出无符号整数(不输出前缀O)
x 以十六进制形式输出无符号整数(不输出前缀OX)
u 以十进制形式输出无符号整数
f 以小数形式输出单、双精度实数
e 以指数形式输出单、双精度实数
g 以%f%e中较短的输出宽度输出单、双精度实数
c 输出单个字符
s 输出字符串
标志字符为-、+、#、空格四种,其意义下表所示:
标志格式字符 标 志 意 义
- 结果左对齐,右边填空格
+ 输出符号(正号或负号)空格输出值为正时冠以空格,为负时冠以负号
# 对c,s,d,u类无影响;对o类,在输出时加前
缀o 对x类,在输出时加前缀0x;对e,g,f 类当结果有小数时才给出小数点
格式字符串
格式字符串的一般形式为: %[*][输入数据宽度][长度]类型,其中有方括号[]的项为任选项。各项的意义如下:
1.类型
表示输入数据的类型,其格式符和意义下表所示。
格式 字符意义
d 输入十进制整数
o 输入八进制整数
x 输入十六进制整数
u 输入无符号十进制整数
f或e 输入实型数(用小数形式或指数形式)
c 输入单个字符
s 输入字符串
转义字符
转义字符是一种特殊的字符常量。转义字符以反斜线”/”开头,后跟一个或几个字符。转义字符具有特定的含义,不同于字符原有的意义,故称“转义”字符。例如,在前面各例题printf函数的格式串中用到的“/n”就是一个转义字符,其意义是“回车换行”。转义字符主要用来表示那些用一般字符不便于表示的控制代码。
常用的转义字符及其含义
转义字符 转义字符的意义
/n 回车换行
/t 横向跳到下一制表位置
/v 竖向跳格
/b 退格
/r 回车
/f 走纸换页
// 反斜线符”/”
/’ 单引号符
/a 鸣铃
/ddd 1~3位八进制数所代表的字符
/xhh 1~2位十六进制数所代表的字符
广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表2.2中的`/ddd和/xhh正是为此而提出的。ddd和hh分别为八进制和十六进制的ASCII代码。如/101表示字?quot;A”,/102表示字母”B”,/134表示反斜线,/XOA表示换行等。转义字符的使用
在C语言中,对变量的存储类型说明有以下四种:
auto 自动变量
register 寄存器变量
extern 外部变量
static 静态变量
自动变量和寄存器变量属于动态存储方式,外部变量和静态变量属于静态存储方式。在介绍了变量的存储类型之后,可以知道对一个变量的说明不仅应说明其数据类型,还应说明其存储类型。因此变量说明的完整形式应为:存储类型说明符数据类型说明符 变量名,变量名…;例如:
static int a,b; 说明a,b为静态类型变量
auto char c1,c2; 说明c1,c2为自动字符变量
static int a[5]={1,2,3,4,5}; 说明a为静整型数组
extern int x,y; 说明x,y为外部整型变量
与指针有关的各种说明和意义见下表。
int *p; p为指向整型量的指针变量
int *p[n]; p为指针数组,由n个指向整型量的指针元素组成。
int (*p)[n]; p为指向整型二维数组的指针变量,二维数组的列数为n
int *p() p为返回指针值的函数,该指针指向整型量
int (*p)() p为指向函数的指针,该函数返回整型量
int **p p为一个指向另一指针的指针变量,该指针指向一个整型量。
指针变量的赋值
p可以有以下两种方式:
(1)指针变量初始化的方法 int a;
int *p=&a;
(2)赋值语句的方法 int a;
int *p;
p=&a;
(1)取地址运算符&
(2)取内容运算符*
;㈢ c语言编程用的符号有哪些
如果真正掌握了C 语言,你就能很轻易的回答上来。这个问题就请读者试着回答一下吧。本章不会像关键字一样一个一个深入讨论,只是将容易出错的地方讨论一下。
表(2.1)标准C 语言的基本符号
C 语言的基本符号就有20 多个,每个符号可能同时具有多重含义,而且这些符号之间相互组合又使得C 语言中的符号变得更加复杂起来。
你也许听说过“国际C 语言乱码大赛(IOCCC)”,能获奖的人毫无疑问是世界顶级C程序员。这是他们利用C 语言的特点极限挖掘的结果。下面这个例子就是网上广为流传的一个经典作品:
#i nclude <stdio.h>
main(t,_,a)char *a;{return!0<t?t<3?main(-79,-13,a+main(-87,1-_,
main(-86,0,a+1)+a)):1,t<_?main(t+1,_,a):3,main(-94,-27+t,a)&&t==2?_<13?main(2,_+1,"%s %d %d
"):9:16:t<0?t<-72?main(_,t,"@n'+,#'/*{}w+/w#cdnr/+,{}r/*de}+,/*{*+,/w{%+,/w#q#n+,/#{l+,/n{n+,/+#n+,/#;#q#n+,/+k#;*+,/'r :'d*'3,}{w+K w'K:'+}e#';dq#'l q#'+d'K#!/+k#;q#'r}eKK#}w'r}eKK{nl]'/#;#q#n'){)#}w'){){nl]'/+#n';d}rw' i;# ){nl]!/n{n#'; r{#w'r nc{nl]'/#{l,+'K {rw' iK{;[{nl]'/w#q#n'wk nw'
iwk{KK{nl]!/w{%'l##w#' i; :{nl]'/*{q#'ld;r'}{nlwb!/*de}'c ;;{nl'-{}rw]'/+,}##'*}#nc,',#nw]'/+kd'+e}+;#'rdq#w! nr'/ ') }+}{rl#'{n' ')# }'+}##(!!/"):t<-50?_==*a?putchar(31[a]):main(-65,_,a+1):main((*a=='/')+t,_,a+1):0<t?main(2,2,"%s"):*a=='/'||main(0,main(-61,*a,"!ek;dc i@bK'(q)-[w]*%n+r3#l,{}:
uwloca-O;m.vpbks,fxntdCeghiry"),a+1);}
还没发狂?看来你抵抗力够强的。这是IOCCC 1988 年获奖作品,作者是Ian Phillipps。
毫无疑问,Ian Phillipps 是世界上最顶级的C 语言程序员之一。你可以数数这里面用了多少个符号。当然这里我并不会讨论这段代码,也并不是鼓励你也去写这样的代码(关于这段代码的分析,你可以上网查询)。
㈣ 如何查看ndk编译的动态库符号表
$ /path/to/ndk/buid/prebuilt/windows/arm-eabi-4.4.0/bin/arm-eabi-nm libs/armeabi/libsanangeles.so
00003600 T java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
00003638 T Java_com_example_SanAngeles_DemoRenderer_nativeDone
0000367c T Java_com_example_SanAngeles_DemoRenderer_nativeInit
000035b4 T Java_com_example_SanAngeles_DemoRenderer_nativeRender
00003644 T Java_com_example_SanAngeles_DemoRenderer_nativeResize
00007334 a _DYNAMIC
0000740c a _GLOBAL_OFFSET_TABLE_
复制代码
这里可以看到几乎所有的函数名全局变量名都会被导出。其中有Java_com_example_SanAngeles_为前缀的JNI接口函数,有importGLInit这些普通函数,有freeGLObject这些局部(static)函数,还有sStartTick等全局变量名。其实在这个动态发布的时候,只需要导出java_com_开头的jni函数就可以了,里面这些细节函数名完全不需要暴露出来。
如何做到这一点呢?首先,我们需要了解gcc新引进的选项-fvisibility=hidden,这个编译选项可以把所有的符号名(包括函数名和全局变量名)都强制标记成隐藏属性。我们可以在Android.mk中可以通过修改LOCAL_CFLAGS选项加入-fvisibility=hidden来做到这一点,这样编译之后的.so看到的符号表为:
000033d0 t Java_com_example_SanAngeles_DemoGLSurfaceView_nativePause
00003408 t Java_com_example_SanAngeles_DemoRenderer_nativeDone
0000344c t Java_com_example_SanAngeles_DemoRenderer_nativeInit
00003384 t Java_com_example_SanAngeles_DemoRenderer_nativeRender
00003414 t Java_com_example_SanAngeles_DemoRenderer_nativeResize
00007104 a _DYNAMIC
㈤ c++编译中的符号表是什么东西
符号表是库中所有函数,变量的总称,用于连接过程.
㈥ 编译原理对符号表进行操作有哪些
//----------------------------符号表---------------------------------------
//预定义
struct snode;
struct stable;
//符号表结点
struct snode
{
string text; //符号名称
string type; //符号类型
union {int ival;double rval;}value; //值------------
int offset; //偏移量
snode *nextn; //指向下一个节点
stable *header; //指向下属符号表的表头
};
//符号表表头
struct stable
{
stable *previous; //指向先前创建的符号表表头
snode *firstnode; //指向第一个结点
stable *ifnoelements;//如果此表为空,则用它指向下一个表头
};
//当前表头
stable *currtab;
//建立新表,返回表头指针
//参数:当前的节点的表头
stable *mktable(stable *previous)
{
stable *newtable =new stable;
newtable->previous=previous;
newtable->ifnoelements=0;
newtable->firstnode=0;
if(previous->firstnode==0)
{
previous->ifnoelements=newtable;
}
else
{
snode* ininode=previous->firstnode;
while(ininode->nextn!=0)
{
ininode=ininode->nextn;
}
ininode->header=newtable;
}
currtab=newtable;
return newtable;
}
//在node指向的符号表中为text建立一个新表项,返回新建立的结点
//参数:node为当前的节点的表头,text名称,type类型,offset偏移
snode *enter(stable *table,string text,string type,int offset,double value)
{
//创建节点
snode *newnode = new snode;
newnode->text=text;
newnode->type=type;
newnode->offset=offset;
newnode->nextn=0;
newnode->header=0;
if(type=="int")
{
newnode->value.ival=value;
}
else if(type=="real")
{
newnode->value.rval=value;
}
//判断此表是否无元素
if(currtab->firstnode==0)
{
currtab->firstnode=newnode;
currtab->ifnoelements=0;
}
else
{
snode* addnode=currtab->firstnode;
while(addnode->nextn!=0)
{
addnode=addnode->nextn;
}
addnode->nextn=newnode;
}
return newnode;
}
//初始化符号表,返回表头节点
void inittab()
{
stable *initable = new stable;
initable->firstnode=0;
initable->previous=0;
initable->ifnoelements=0;
currtab=initable;
}
//查找指针,表示结果
snode *searchresult;
//查找变量,返回指向该变量的指针
//查找变量,返回指向该变量的指针
snode* search(string name)
{
//检查表是否空
bool isempty=true;
stable* checktab=currtab;
if(checktab->firstnode!=0)
{isempty=false;}
while(checktab->previous!=0)
{
if(checktab->firstnode!=0)
{isempty=false;}
checktab=checktab->previous;
}
if(checktab->firstnode!=0)
{isempty=false;}
if(isempty)
{
return 0;
}
snode* lastnode;
if(currtab->firstnode==0)
{
//移到非空的表头
stable* notnullhead=currtab;
while(notnullhead->firstnode==0)
{
notnullhead=notnullhead->previous;
}
snode* tmpnode=notnullhead->firstnode;
//移到最后的元素
while(tmpnode->nextn!=0)
{
tmpnode=tmpnode->nextn;
}
lastnode=tmpnode;
}
else
{
lastnode=currtab->firstnode;
while(lastnode->nextn!=0)
{
lastnode=lastnode->nextn;
}
}
//移到表头
stable* fronttab=currtab;
while(fronttab->previous!=0)
{
fronttab=fronttab->previous;
}
snode* nownode=0;
while(nownode!=lastnode)
{
while(fronttab->ifnoelements!=0)
{
fronttab=fronttab->ifnoelements;
}
nownode=fronttab->firstnode;
while(nownode->nextn!=0)
{
if(nownode->text==name)
{
searchresult=nownode;
return searchresult;
}
nownode=nownode->nextn;
}
if(nownode->text==name)
{
searchresult=nownode;
return searchresult;
}
fronttab=nownode->header;
}
if(nownode->text==name)
{
searchresult=nownode;
return searchresult;
}
return 0;
}
//消毁符号表
void delNode()
{
//more codes here......
}
㈦ 如何建立符号表
Symbol Tables
为了维持静态作用域的程序里各个名字的轨迹,编译器需要依靠一种称为符号表的数据结构。从最基本的层次上看,符号表就是一个字典:它把名字映射到编译器已知的有关信息。这里最基本的操作是把一个新映射关系(名字对象约束)放入表里,以及(非破坏性的)用一个给定名字去提取映射下的信息,以后我们把
这两个操作分别称为insert和lookup。大部分语言里的静态作用域规则还提出了另外的复杂性,它们要求在程序里的不同部分有不同的引用环境。为了处理作用域规则,我们可能希望简单增加一个remove操作。由于编译器在语义分析阶段要从头到尾扫描代码,这样它就可以在某个作用域开始时插入新约束,在作用域最后撤销它们。但是,存在一些因素使这种直接做法并不实际。
¨ 在许多有着嵌套作用域的语言里,内层声明的效果可以遮蔽外层声明,这就意味着符号表必须有能力为一个给定名字保存任意数目的映射。lookup操作必须返回最内层的映射,到作用域结束时还必须使外层映射重新变成可见的。
¨ 类Algol语言里的记录(结构)具有某种作用域性质,但却又不享有作用域那样的良好嵌套结构。当语义分析器看到一个记录声明时,它就必须记下各个记录域的名字(它们也是递归的,因为记录可以嵌套)。在这种声明结束时,各个域的名字又必须变成不可见的。然而,在此之后,一旦这一记录类型的某个变量出现在程序的正文里(例如在my_rec.field_name),在引用中位于圆点之后的部分,这些域名又必须立即重新变成可见的。在Pascal和另一些有with语句的语言里,记录域的名字还应该在多个语句的上下文里变成可见的。
¨ 某些时候一些名字有可能在它们被声明之前使用,即使在类Algol语言里情况也如此。举例说,Algol 60和Algol 68都允许标号的向前引用。Pascal避免了这种情况,它要求标号必须在作用域开始处声明,但还是允许指针声明的向前引用:
type
company = record
CEO : ^person; (* forward reference *)
...
end;
person = record
employer : ^company;
...
end;
¨ Pascal和其他语言都允许子程序的向前声明,以便支持相互递归:
procere Q (A, B : integer); forward;
procere P (A, B : integer);
begin
...
Q (3, 4);
...
end;
procere Q; (* parameters are not repeated in Pascal *)
begin
...
P (4, 5);
...
end;
在看到这段代码里的向前声明时,语义分析器必须记住Q的参数,以便后面可以在Q的体里使它们重新变成可见的,但在此期间又必须使它们成为不可见的。这种操作类似于记住记录域的名字。
¨ 虽然我们有可能希望在作用域结束时忘记有关的名字,甚至回收这些名字在符号表里占据的空间,但有关它们的信息仍需要保存起来,以便符号纠错系统(symbolic debugger)使用。这种纠错系统是非常有用的工具,用户可以借助它方便地操纵程序,如启动程序,停住它,读出或者修改程序里的数据等等。为了分析来自用户的高级名字(例如,要求打印出my_firm^.revenues[1999] 的值),符号纠错程序必须能访问编译器的符号表。为了使符号表在运行时也可以用,编译器通常会把这个表保存到最后的机器语言程序里的某个隐蔽的部分。
静态作用域的大部分变化都可以通过扩充基本符号表的方式处理,通过增加一对enter_scope和leave_scope操作维持可见性的轨迹。任何东西都不会从符号表里删除,在整个编译阶段所有的结构都保留着,最后还要为纠错系统使用而保存起来。带有可见性处理的符号表可以以多种不同方式实现,下面描述的方式归功于LeBlanc和Cook [CL83]。
在遇到每个作用域时赋给它一个序列号。给定最外层的作用域(其中包含着预定义的标识符)编号0,包含用户定义全局名字的作用域给以编号1。其他作用域按遇到它们的顺序进行编号。所有的编号互不相同,它们并不表示词法嵌套的层次,但也有一点,嵌套于内部的子程序的编号自然会大于它们的外围作用域的编号。
所有的名字都被放入一个大的散列表里,以名字作为关键码,无论其作用域如何。表里的每项都包含一个符号名,其类属(变量、常量、类型、过程、域名字、参数等等),作用域编号,类型(一个指向另一符号表项的指针),以及另一些特定类属所拥有的信息。
除了这一散列表之外,符号表还包含一个作用域堆栈,它按顺序指明组成当前引用环境的所有作用域。在语义分析器扫描程序的过程中,在进入或退出程序时分别压入或者弹出这个堆栈。作用域堆栈的项里包含着作用域编号,指明这一作用域是否为闭的,有些情况下还可以有另外一些信息。
图3.13 LeBlanc-Cook符号表的lookup算法。
当需要到表里查找名字时,我们会顺着某个适当的散列表链向下找,这样就会找到要找的名字所对应的一些项。对于每个匹配项,我们都向下扫描作用域堆栈,看看这个项所在的作用域是否可见。这种堆栈查看的深度不应超过最上面的闭作用域。要把导入项和导出项变为在它们的作用域之外可见的,方法就是在表里建立另外的项,让这些项里包含着指向实际项的指针。对于所有带有作用域编号0的项,我们都不需要去检查作用域堆栈,因为它们是渗透性的。图3.13里是lookup算法的伪代码。
图3.14的右下角是一个Mola-2程序的梗概,图中其余部分展现的是在过程P2里with语句处的引用环境的符号表配置情况。作用域堆栈里包含4个项,分别表示那个with语句,过程P2,模块M和全局作用域。with语句的作用域指明了在这一特定作用域里的(域)名字属于哪个记录变量。最外面的渗透性作用域没有显式表示。
图3.14 一个Mola-2例子程序的LeBlanc-Cook符号表。作用域堆栈表示在过程P2里with语句的引用环境。为清楚起见,许多指向符号表里对应于integer和real的项都用带括号的 (1) 和 (2) 表示,没有画出箭头。
因为这里的散列表以名字作为关键码,特定名字的所有项都出现在同一个散列链里。在这个例子里,散列冲突导致A2、F2和T出现在同一个链里。变量V和I(M的I)有另外的项,使它们跨过闭作用域M的边界后仍为可见的。当我们处于P2里时,对于I的查找操作将找到P2的I,M的I里的两个项都不可见。类型T的项指明了在with语句期间放入作用域堆栈的作用域编号。每个子程序的项里包含了一个头指针,指向子程序参数的链接表,以便做调用分析时使用(图中没有给出这些链的一些链接)。在代码生成过程中,许多符号表项还可能包含另外的域,表示例如对象大小和运行时地址等等信息。
图片信息,看参考资料。。。