理解的重点就在于“符号表”(symbol table)。之前回答过一个略微相关的问题,
C 语言程序变量作用域的实现机制是什么? - RednaxelaFX 的回答那个问题主要关注的是符号表对局部变量的体现,而题主这个问题关注的是符号表在函数层面的体现。
举个例子:C语言在极限情况下可以“裸奔”——完全不依赖于任何外部的运行时库,编译出来的就是单一、独立的可执行文件。正因为如此,它可以方便的用于写直接跑在裸硬件上的程序,例如操作系统自身。
但一般用C写应用层面的程序,多少还是得依赖一些外部库的。最常见的情况之一就是与C Runtime Library(CRT)动态链接。为了能做到这点,CRT作为动态链接库自然要提供链接用的符号信息,而应用程序也需要提供链接用的符号信息说明自己依赖于哪些符号。
于是就带回到题主的问题了。C写的程序,编译出来的目标文件,在(静态-)链接前自然带有所有链接用的符号信息,而在静态链接后仍然可能带有动态链接用的符号信息。
目标文件里,链接/加载相关的符号信息表大致有三种:
题主问到微软的情况,接下来稍微提一下Windows上是怎么做的。
MSVC编译程序时可以生成可执行文件或动态链接库。这是最终产物,中间步骤会涉及静态库文件(static library,.lib文件)。
例如说,一个程序假如有a.c、b.c和c.c三个源文件,分别编译然后打包成一个“东西”,那么中间步骤就会有a.obj、b.obj和c.obj三个目标文件,可以配置为打包成:
题主要导出静态链接用的符号不用做啥特别的事,但要导出动态链接用的符号的话,在源码里函数声明处要加上
__declspec(dllexport),或者是
用.def文件来指定导出符号。
这里,静态库文件与动态链接库文件可能比较好理解,其包含的符号信息肯定要能满足静态/动态链接器对导入/导出信息的需要。但是配合动态链接库的那个“导入库文件”又是啥?
其实一个“导入库文件”也是一个静态库文件,它包含的桩代码(stub)会通过IAT(Import Address Table)调用位于DLL文件里的实际实现,而它包含的符号信息正好对应于配套的DLL文件。
例如说,一个C写的程序通过MSVC工具链编译与链接,要与MSVC的CRT(例如msvcrt90.dll)动态链接的话,可以在静态链接时与配套的import library(例如msvcrt90.lib)链接起来,这样得到的链接后代码就有足够符号信息和stub代码去在运行时正确调用动态链接库的函数。
以上这种在静态链接时使用import library的做法,在MSDN的文档上叫做implicit linking,又名load-time dynamic linking。
相对应的,还有explicit linking,又名run-time dynamic linking。这种用法不需要使用import library,而是调用方自己显式调用LoadLibrary()加载DLL文件,然后用GetProcAddress()找到对应名字的函数的地址并对其调用,最后用完调用FreeLibrary()去释放DLL文件。
请参考文档:
Linking an Executable to a DLL要是想弄load-time dynamic linking但是又不想依赖import library的话,其实在程序里
__declspec(dllimport)也是可以的。事实上这样生成的代码还略微快一些(少了一次stub跳转)。