C语言对机器模型的抽象层次,恰到好处。
C语言使得开发者,刚好从最底层的费力低效的汇编编程中解脱出来,而又没有进一步往上层做抽象。
这样一来既大大提高了开发效率,又不失对程序行为的精确控制能力,还使程序获得了良好的可移植性。可谓一举三得。
同时,基本C库的理念,也是只做确定正确的事。他所提供的,大体都是最小粒度的几乎不会有变数的基础例程。
不过,本文是要谈一谈C源码与ELF之间的一些联系。
C语言中的各种概念,最终都要转换成汇编层次的概念,进而变成实际可运行的机器码。
C语言中的运算符,编译时转换成运算指令,变量类型决定运算操作的数字宽度,引用结构体成员转换成相应的内存操作的偏移。
C代码经过编译,变量与函数,变成了汇编代码中的符号。
汇编代码再经汇编器变成目标文件,即elf文件(当然,可执行文件、动态库、内核模块,也是ELF文件)。
在elf这个层次,一个符号就相当于是给某个地址起了个名字。
不过,编译器编译C代码,生成的汇编代码中,为C中定义的符号,指定了type(并不是C语言中的类型,而是data or code)和size。
这就保存了更多的信息。这样一来,通过nm工具就可以查看符号的大小了。
但是,通过链接脚本中简单赋值创建的符号(例如,__bss_start = .;),并没有大小信息。
由于每一个C文件的编译是孤立的进行的,因此编译器遇到C代码中引用一个外部的变量或函数时,编译器就认为这是在引用一个外部符号。
编译器只是标识一下,这个外部符号的地址需要在链接时确定其实际的地址。
至于C代码中描述自己引用的是什么东东,编译器并不能判断是不是确实如此。因为编译器也不知道那个符号到底是什么样以及在哪里。
所以,对于外部的符号(不管是变量、函数,还是诸如__bss_start等系统符号),把他当成什么声明,怎么访问,完全是C代码说了算。
至于编译器,只能根据C代码声明的引用方式,生成对此符号地址的各种操作指令。
另外需要说明的是,当nm工具显示一个符号的类型为T时,只能说明符号所在section为code/text section,并不表示此符号一定位于名为.text的这个section。
同样,当nm显示一个符号的类型为D时,只能说明符号所在section为data section,并不表示此符号一定位于名为.data的这个section。