分类: C/C++
2012-02-20 19:30:10
链接器不理解C语言,但却能理解机器语言及内存布局。链接器的输入是一组目标模块和库文件,输出是一个载入模块。对每个目标模块中的每个外部对象,链接器都要检查载入模块看是否有同名的外部对象,如果没有,则将外部对象载入到模块中,如果有则要处理命名冲突。
4.2 声明与定义int a;如果出现在所有函数体外,则称为外部对象a的定义。
extern int a; 并不是对a的定义,而是声明了对一个外部变量a的显式引用,a的存储空间是在程序的其他地方分配的。此声明语句可出现在一个函数内部。
有extern int a; 则必须在其他某个地方包括语句:int a; 这两个语句可出现在同一个源文件中,也可以位于程序的不同源文件中。
如果外部变量的多个定义各指定一个初始值: int a = 7; int a = 9;出现在不通过的源文件中,大多数系统会拒绝接受该程序。但是如果只是多次定义而没有初始化,则有的系统接受,有的不接受。
4.3 命名冲突与static修饰符1 两个同名外部对象实际代表同一个对象!如果两个不同的源文件中都包含了定义int a;则它或者表示程序错误(如果连接器禁止外部变量重复定义的话),或者在两个源文件中共享a的同一个实例。
2 static 可以减少命名冲突!限定变量或函数只能在其所在的源文件中可用!
(1)static int a;将a的作用域限制在一个源文件中,对于其他源文件a 是不可见的!因此如果个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要的对象也都放在同一个源文件中以static修饰符声明。
(2)static也可以修饰函数。如果函数f需要调用函数g,而且只有f需要调用g,则可把它们都放在同一个源文件中,并声明g为static。为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其他函数调用,我们就应该该函数为static。
4.4 形参、实参与返回值1 如果一个函数在被定义或者声明之前被调用,那么它的返回类型被默认为整型。而若函数的实际返回类型并不是整型,则与此函数链接时就会出现错误。
如果函数的定义与调用在不同的文件中,则需要在调用它的文件中声明此函数。
2 如果一个函数没有float、short或者char类型的参数,则函数声明中完全可以省略参数类型的说明,但函数定义中不能省略参数类型的说明!!而形参为float、short型时,因为传入的实参会被自动转换为double类型,char会被转换为int类型,所以必须显示的声明float、short、char类型的函数。
3 #include
//double sqrt(double);
int main()
{
double s;
s = sqrt(2.0);
printf("%g\n",s);
return 0;
}
double sqrt(double a)
{
return a;
}
结果输出2. Sqrt在math.h中有了声明和定义,所以在main之前不必再声明,但此处调用的却是自定义的sqrt函数!
4.5 检查外部类型1 同一个外部变量名在不同的文件中被声明为不同的类型,如:extern int n; long n;则此程序很危险。大多数编译器不能检测出这种错误,运行时可能有很多情况:
(1) int 和long在内部表示一样的话,程序可能正常工作;
(2)变量n的两个实例虽然存储空间大小不同,但int恰巧与long的低端部分共享存储空间,则恰巧能正常工作;
(3)变量n的两个实例共享存储空间,使得对一个赋值,其效果相当于给另一个赋了完全不同的值。则不能正常工作。
2 保证一个特定名称的所有外部定义在每个目标模块中都有相同的类型,而且是严格意义上的相同。
Char filename[] = “/etc/passwd”; extern char * filename;
尽管在一个语句中引用filename的值将得到指向该数组起始元素的指针,但在此处在定义中,filename的类型是“字符数组”,而不是“字符指针”,而在声明中filename被定义为一个指针,这样声明是错误的!应该为:
Char filename[] = “/etc/passwd”; extern char filename[];或者:
Char *filename= “/etc/passwd”; extern char * filename;
4.6 头文件char filename[] = “/etc/passwd”; 若一个程序有多个模块组成每个模块都要知道一个特定文件名,我们希望在一处改动这个特定文件名,则所有模块中的文件名都同时更新,则可以创建一个头文件如file.h,并包含此变量的声明:extern char filename[];然后再每个要用到filename的源文件总包含file.h : #include “file.h”,最后选择一个源文件给出filename的初始值,如file.c: #include “file.h” char filename[] = “/etc/passwd”;
把头文件展开得:extern char filename[]; char filename[] = “/etc/passwd”;
虽然包含了filename 的两个声明,但只要各个声明是一致的,而且最多只有一个是定义的,则这样写是合法的。