文章目录
- 什么是符号?
- 什么是符号表?
- 全局符号和本地符号
- 1. 全局符号:
- symtab符号表
- 2. 本地符号:
- 符号在汇编阶段
- 符号在链接阶段
- 1.由模块 m 定义并能被其他模块引用的全局符号。
- 2.由其他模块定义并被模块 m 引用的全局符号。
- 3.只被模块 m 定义和引用的局部符号。
- static 本地变量
- QA:
- 编译器和汇编器的区别?
- 编译器包含汇编器吗?
什么是符号?
想象一下你在学校里,老师在黑板上写了一些字母和数字,比如"A"、“B”、“1”、“2”。这些字母和数字就是符号。在计算机编程或数学中,符号就是用来代表某个特定概念或值的标记。
举个例子,假设你写了一个计算机程序,里面有一个变量叫做"score",用来表示游戏中的分数。在这里,"score"就是一个符号,它代表了一个特定的值,即分数。
符号可以是字母、数字或其他字符的组合,它们被赋予特定的含义,让程序员或计算机能够理解并操作这些符号代表的信息。所以,通俗地说,符号就是一种用来代表东西的标记,让计算机或人能够更方便地理解和处理信息。
什么是符号表?
想象一下你在读一本书,遇到一个生词,你可能会查字典来找到这个词的解释和意义。符号表就有点像是一本程序的字典,但是不是用来解释生词的,而是用来解释程序中用到的各种符号、变量、函数等等。
在计算机程序中,我们使用各种符号(比如变量名、函数名)来表示不同的东西,就像字典中的单词一样。符号表就记录了这些符号对应的信息,比如变量的数据类型、函数的参数类型、以及它们在内存中的位置等等。
当计算机执行程序时,会根据符号表中的信息来理解和处理这些符号,就像你在阅读一本书时根据字典来理解生词一样。符号表有助于编程语言的解释器或编译器理解程序的结构和含义,使得计算机能够正确地执行你写的代码。
全局符号和本地符号
在编程中,有两种主要类型的符号:全局符号和本地符号。全局符号是在整个程序中都可见的,而本地符号仅在特定的作用域内可见。在链接器的上下文中,我们通常将这些符号称为外部符号和本地符号。
1. 全局符号:
- 全局变量和函数是一种全局符号。它们可以在不同的文件中定义,并在整个程序中共享。
- 链接器对全局符号感兴趣,因为它们需要在不同的目标文件之间解析引用关系,确保这些全局符号的定义能够正确地链接在一起。
symtab符号表
symtab 是符号表(Symbol Table)的缩写,用于存储程序中变量、函数和其他符号信息的数据结构。
关于 .symtab
中的符号表,它主要包含与全局符号相关的信息,因为链接器主要负责解决全局符号之间的引用关系。
本地符号,特别是本地非静态程序变量,通常不包含在符号表中,因为它们在运行时通过栈进行管理,链接器不需要关心这些细节。
这种分离全局和本地符号的处理方式有助于提高链接器的效率,并确保在链接阶段解决全局符号的引用关系时不会受到本地符号的干扰。
认识到本地链接器符号和本地程序变量不同是很重要的。
.symtab 中的符号表不包含对应于本地非静态程序变量的任何符号。这些符号在运行时在栈中被管理,链接器对此类符号不感兴趣。
.symtab 节中包含 ELF 符号表。这张符号表包含一个条目的数组。图 7-4 展示了每个条目的格式。
name是字符串表中的字节偏移,指向符号的以null结尾的字符串名字。
value 是符号的地址。对于可重定位的模块来说,value 是距定义目标的节的起始位置的偏移。对于可执行目标文件来说,该值是一个绝对运行时地址。
size 是目标的大小(以字节为单位)。
type 通常要么是数据,要么是函数。符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。所以这些目标的类型也有所不同。
binding 字段表示符号是本地的还是全局的。
2. 本地符号:
- 本地变量和本地函数是一种本地符号。它们通常只在定义它们的特定代码块(如函数)内部可见。
- 链接器对本地符号不感兴趣,因为它们不需要在不同的目标文件之间解析引用关系。本地符号的生命周期通常限定在其定义的作用域内,例如在函数的栈帧中。
符号在汇编阶段
编译器在处理源代码时,会将源代码转换为汇编语言代码,然后将汇编语言代码输出到.s文件中。在输出的.s文件中,编译器还会将源代码中所有的函数、变量和常量等符号标记,并生成一个符号表,包含了这些符号的信息,如位置、大小等。
这些符号将在后续的汇编和链接过程中起到重要的作用,比如在汇编过程中,汇编器会根据符号表中所包含的信息来解析和生成目标二进制代码。
汇编器的作用主要有以下几个方面:
-
将汇编语言代码转换为机器语言代码:汇编器通过读取汇编代码源文件并将其转换为机器语言代码,创建目标文件,这个文件包含二进制代码和其他与代码相关的元信息。
-
符号解析:汇编器通过符号表解析代码中出现的符号,如函数和变量,以便能够正确地连接和重定位代码。
-
代码调试:汇编器生成的目标文件中包含了关于代码的调试信息,可以被调试器用来调试代码。
-
优化代码:汇编器可以通过优化代码来提高代码的执行效率,例如,通过使用更快的指令集或者重排指令的执行顺序。
符号在链接阶段
每个可重定位目标模块,我们姑且命名一个目标模块为m。
那么,模块m都有一个符号表,它包含模块m 定义和引用的符号的信息。
在链接器的上下文中,有三种不同的符号:
1.由模块 m 定义并能被其他模块引用的全局符号。
全局链接器符号对应于非静态的 C函数和全局变量。
2.由其他模块定义并被模块 m 引用的全局符号。
这些符号称为外部符号,对应于在其他模块中定义的非静态 C 函数和全局变量。
3.只被模块 m 定义和引用的局部符号。
它们对应于带 static 属性的 C函数和全局变量。这些符号在模块 m 中任何位置都可见,但是不能被其他模块引用。
static 本地变量
有趣的是,定义为带有 C static 属性的本地过程变量是不在栈中管理的。
相反,编译器在.data或bss 中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。
比如,假设在同一模块中的两个函数各自定义了一个静态局部变量 x:
下面是一个示例代码,展示了在同一模块中的两个函数各自定义了一个静态局部变量 x,并且这些变量在.data或bss段中分配空间,而不是在栈中管理:
#include <stdio.h>
void function1() {
static int x = 1;
printf("Function 1: x = %d\n", x);
x++;
}
void function2() {
static int x = 10;
printf("Function 2: x = %d\n", x);
x--;
}
int main() {
function1();
function1();
function2();
function2();
function1();
return 0;
}
输出结果:
Function 1: x = 1
Function 1: x = 2
Function 2: x = 10
Function 2: x = 9
Function 1: x = 3
可以看到,每个函数的静态局部变量都保留了它们的状态,并且它们在每个函数调用之间都保留了它们的值。这是因为它们在.data或bss段中分配了空间,而不是在栈中管理。
注意:
利用 static 属性隐藏变量和函数名字: C程序员使用 static属性隐藏模块内部的变量和函数声明,就像你在Java 和C++中使用
public和 private 声明一样。在C中,源文件扮演模块的角色。任何带有static属性声明的全局变量或者函数都是模块私有的。类似地,任何不带 static
属性声明的全局变量和函数都是公共的,可以被其他模块访问。尽可能用 static 属性来保护你的变量和函数是很好的编程习惯。
QA:
编译器和汇编器的区别?
编译器和汇编器都是程序开发中使用的工具,但它们的功能不同,具体区别如下:
-
编译器和汇编器的语言级别不同。编译器通常用来将高级语言(如C、C++、Java等)转换为汇编语言或者机器语言,而汇编器是把汇编语言转换为机器语言。
-
编译器和汇编器的输入和输出不同。编译器的输入是高级语言源代码,输出是汇编语言或者机器语言代码;而汇编器的输入是汇编语言代码,输出是机器语言代码。
-
编译器和汇编器的编译过程不同。编译器会对源代码进行词法分析、语法分析、优化和生成目标代码等多个阶段的处理,而汇编器只是简单地将汇编语言代码翻译成机器语言代码。
-
编译器和汇编器的处理粒度不同。编译器处理的粒度更大,它会对整个程序进行处理,包括程序中的所有函数、变量和常量等;而汇编器处理的粒度更小,它只是针对单个汇编模块进行处理。
编译器包含汇编器吗?
在一些编译器中,汇编器的功能是由编译器自身来实现的,这些编译器被称为“集成编译器”或“自包含编译器”(self-contained compiler)。这种编译器可以直接将高级语言源代码转化成目标机器的机器语言代码,而不需要借助其他汇编器的支持。
但也有一些编译器是不包含汇编器的。在这种情况下,编译器只负责将高级语言源代码转换成汇编语言代码,然后再使用独立的汇编器将汇编语言代码转换成机器语言代码。这种分离的方式可以让用户有更多的选择,可以灵活地选择不同的汇编器来适应不同的需求。
是否包含汇编器取决于不同的编译器实现,而不是编译器的必要组成部分。