13.3.3 连接错误示例(2)
6.实现同名函数导致符号重定义错误
在main.c文件中,增加对hello函数的定义。
void hello(void) { printf("+++ main hello +++\n"); } |
编译通过后,连接时会产生符号重定义的错误。实际上,由于在hello.c和main.c的目标文件的代码段中分别定义hello函数。连接器认为出现重定义。与上例的程序类似,即使没有对函数hello的调用,编译器也不允许在代码段中出现2个hello函数的符号,所以还是会产生符号重定义连接错误。
知识点:在多个文件中定义全局的同名函数和变量,连接时将发生符号重定义错误。
7.静态函数与其他文件中的函数重名,可以正常使用
将main.c文件更改成如下:
#include "hello.h" int main(void) { hello(); string[0] = 'a'; return 0; } static void hello(void) /* 静态的函数,内部使用 */ { printf("+++ main hello +++\n"); } |
程序主要的变化是增加了静态的hello函数,在这种情况下,是可以成功地进行编译连接和运行的,运行结果如下所示:
从运行结果中可以看到,main中调用的hello函数是main.c文件中定义的static 的hello函数。
值得注意的是,在这种情况下,编译器在进行编译的时候,main函数写在静态的函数hello前面,因此可以通过编译。这是由于main.c文件中包含了hello.h文件,其中具有对hello()函数的声明。但是,当编译器编译到main.c之中的hello函数的时候,由于static头文件中声明函数原型不同,可能出现一个编译报警(warning)。
8.静态变量与其他文件中的变量重名,可以正常使用
在main.c中,增加静态(static)的读写数据段的字符数组string[]的定义。
char string[1024] = "main string";
|
在这种情况下,编译连接可以成功。当连接工作完成后,可执行程序的读写数据段将出现两个string[1024]数组,均占用的空间。一个是hello.c中定义的全局的数组,一个是main.c定义的文件内部使用的数组。在数据访问的过程中,语句string[0] = 'a'访问的将是main.c中定义的数组string[]。
知识点:如果全局变量和函数已定义,而在某个文件中另外定义静态的同名变量和函数,可以在文件内部使用同名的静态变量和函数。在使用的过程中,将优先使用文件内的变量和函数。
9.在头文件中定义已经初始化数据,可能产生问题
在程序中,将string[]的定义放入hello.h的头文件中:
#ifndef HELLO_H #define HELLO_H void hello(void); char string[1024] = ""; #endif |
同时,取消在hello.c中对string[]数组的定义。此时,由于hello.c和main.c同时包含了hello.h头文件,因此string在内存中有两份,连接的时候将产生符号重定义错误。
如果将头文件中string的定义改为静态的,这时不会产生连接错误,但是会在hello.c和main.c的目标文件中各产生一个string[1024]。最终可执行程序的读写数据段中也会有两个string。
如果在头文件中使用如下方式定义:
const char string_const[1024] = {"constant data"}; |
由于具有const属性,string_const是一个在只读数据段上的常量。这样有多个文件包含该头文件的时候,在连接过程中也会出现符号重定义的错误。连接器在这个问题上,对读写数据区和只读数据区的处理方式是类似的。
知识点:具有初始值的变量将被连接到读写数据区。在头文件中不应该定义有初始值的全局变量。同样,也不应该定义只读数据段的常量。否则,在头文件被多个文件包含的时候,将发生连接错误。
10.在头文件中定义未初始化数据段,可以正常使用
在程序中,hello.h的头文件中定义string[],但是没有初值:
#ifndef HELLO_H #define HELLO_H void hello(void); char string[1024]; #endif |
同时,取消在hello.c中对string[]数组的定义。在这种情况下,编译连接都是可以通过的,程序也可以正常运行。
知识点:无初始化的变量将被连接到未初始化数据段,在头文件中可以定义。当头文件被多个文件包含时,该未初始化段在运行时也将只有一份。
事实上,由于没有初值,string[]将不再是读写数据段上的变量,而是未初始化数据段上的变量。未初始化段上的变量并不会占用目标文件或者可执行文件中的空间,它们只是一些标识。由于不需要分配空间,编译器允许这种做法。未初始化数据段的变量在运行的时候才会产生,而且只会有一个。
同理,可以将string修改为static的未初始化变量:
#ifndef HELLO_H #define HELLO_H void hello(void); static char string[1024]; #endif |
在这种情况下,在编译的时候将会在两个目标文件中各自记录一个未初始化数据段,在运行时程序将在内存上开辟两个独立的1024字节的数据区。
比较以上的两个示例(9和10),总结出以下的结论:
首先,不应该在头文件中使用全局的读写数据变量,这样当两个文件同时引用这个头文件的时候,将会产生符号重定义连接错误。
其次,在头文件中也不应该使用静态的变量,无论它有没有初值(即在读写数据段或者未初始化数据段),这样虽然不会引起连接错误,但是在各个源文件中各自产生变量,不但占用更多的空间,而且在逻辑上是不对的,也违背头文件的使用原则。
最后,在头文件中使用全局的没有初始化的变量是可以的,它在程序运行的过程中,在内存中只会有一份,可以被包含该头文件的程序访问。
从C语言程序设计的角度,不应该在头文件中定义变量或者函数。对于函数,在头文件中只是声明,需要在源文件中定义;对于变量,无论何种性质(只读数据段、可读写数据段、未初始化数据段),最好的方式是在C语言的源文件中定义,在头文件中使用extern声明。