用示例详解嵌入式linux上C语言最易出现的10个连接错误13482100207 [转载]

2019-07-13 06:27发布

用示例详解嵌入式linux上C语言最易出现的10个连接错误13482100207 [转载]

连接错误示例

        连接过程中常见的错误是符号未找到(undefined reference)和符号重定义(redefinition)。由于在编译器在处理各个符号的时候,已经没有了各个C语言源文件的概念,只有目标文件。因此对于这种错误,连接器在报错的时候,只会给出错误的符号的名称,而不会像编译器报错一样给出错误程序的行号。

        符号未定义的错误经常发生在符号已经声明,但是并没有具体的定义的情况下。在C语言中,符号可以是一个函数,也可以是一个全局变量。在程序的编译过程中,只要符号被声明,编译就可以通过,但是在连接的过程中符号必须具有具体的实现才可以成功连接。

        例如:某一个源程序的文件的某一个地方调用了一个函数,如果这个函数具有声明,这时编译就可以通过。在连接的过程中,连接器将在各个代码段中寻找函数,如果函数没有在程序的任何一个位置中定义,那么就不会有函数符号,这时连接器将发生符号未定义的连接错误。请阅读如下程序:
                extern void function(void);
                int main(void) 
                {
                    /* ...... */
                    function ();
                    /* ...... */
                    return 0;
                }
        在以上的例子中函数function可以和其调用者main在同一个源文件中,也可以在其他的源文件被调用中,但是它必须被定义。

        符号重定义错误与符号未定义错误类似,如果连接器在连接的时候,发现一个符号在不同的地方有多于一个定义,这时就会产生符号重定义错误。对于同一个符号,如果在多个源文件中出现多次,将会产生符号重定义错误。

       知识点:在连接过程中,符号未定义和符号重定义是两种最基本的错误。
下面以一个包含3个文件的程序为例,说明在连接过程中的错误。这个程序的三个文件为hello.h、hello.c和main.c。

         main.c文件如下所示:
                #include "hello.h"                 int main(void) 
                {
                    hello();
                    string[0] = ''''a''
                    return 0;
                }

         hello.h文件如下所示:
                #ifndef HELLO_H
                #define HELLO_H                 void hello(void);
                extern char string [];
                #endif

         hello.c文件如下所示:
                #include
                #include "hello.h"                 char string [1024] = "";
                void hello(void)
                {
                    printf("=== hello ===/n");
                }
        以上是一个可以正常运行的程序,编译器将对main.c和hello.c两个源文件分别进行编译。在main函数中,调用了函数hello,并访问了数据string,由于包含了hello.h头文件,hello和string具有声明,因此main.c可以被成功编译。hello.c中定义了一个读写数据段上的变量string和函数hello。在连接的过程中,目标文件main.o对string和hello符号进行访问,hello.o提供了这两个符号,因此可以连接成功。

1.由于无数据定义导致符号未定义错误
         将hello.c中对data的定义去掉,这时编译器依然可以成功编译main.c和hello.c。但是,由于string[0] =''''a''''编译后产生的代码将对string访问,在连接的过程中找不到string这个数据,因此会产生符号未定义连接错误(找不到数据)。

2.由于无函数实现导致符号未定义错误
         将hello.c中对hello函数的定义去掉,这时编译器还是可以成功编译main.c,而且有这个符号,因此也会产生目标文件hello.o。但是,在连接器处理函数调用的时候,需要跳转到hello符号,由于实际上并没生符号而报告未定义连接错误(找不到函数)。

         知识点:在程序中使用只有声明而未定义的函数或数据,可以成功编译,但是连接时将发生符号未定义错误。

3.由于数据仅能在文件内部使用,导致符号未定义错误

         将hello.c的数组string []更改为静态变量:
         static char string [1024] = "";
         此时编译依然是可以通过的,这时候由于string已经是一个静态数据,因此它不会出现在hello.c的目标文件的符号中。也就是说,增加static修饰后,目标文件和以前将略有不同。

         连接器在处理的时候,虽然有string这个数据,但是它只有数据区而没有符号,因此依然会出现未定义符号错误。

4.函数具有声明可编译通过,但有连接错误
         取消hello.c中对函数的声明,在main.c增加对该函数的声明:
         void hello(void);

         在hello.c中,将hello函数的定义改为静态的:
                  static void hello(void)
                  {
                     printf("=== hello ===/n");
                  }
         这时,编译还是能通过,由于hello成为静态函数,只能在文件内部使用,不会产生外部的符号。因此,虽然在main中对该函数进行了声明,连接也会产生未定义符号错误。

         函数在各个源文件中的声明不会对连接产生影响,因此,在本例中,只要hello函数不是静态的,连接就可以通过。

         知识点:使用static的函数和变量不能给其他文件调用,否则会发生连接的符号未定义错误。

5.定义同名变量导致符号重定义错误
         在hello.h文件中,取消对string的外部声明。在main函数中增加一个定义在读写数据段的字符数组string[],

         char string[1024] = "main string";

         编译通过后,在连接时将会产生符号重定义的连接错误。main.c和hello.c中各自有一个读写数据段的字符数组string[],虽然它们看似没有直接的关系,但连接器无法处理这种情况,依然会产生连接错误。

         在这种情况下,即使没有对string的引用(即没有string[0] = ''''a''''),连接器依然无法处理两个同名的读写数据段全局变量,这时还是会报告符号重定义的连接错误。

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中调用的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声明。 http://blog.mcuol.com/User/Broadview/Article/13482_1.htm