zoukankan      html  css  js  c++  java
  • c的链接详解

    多目标文件的链接

    stack.c

    #include <stdio.h>
    
    #define STACKSIZE 1000
    
    typedef struct stack {
    	int data[STACKSIZE];
    	int top;
    } stack;
    
    stack s;
    int count = 0;
    
    void pushStack(int d)
    {
    	s.data[s.top ++] = d;
    	count ++;
    }
    
    int popStack()
    {
    	return s.data[-- s.top];
    }
    
    int isEmpty()
    {
    	return s.top == 0;
    }

    link.c

    #include <stdio.h>
    
    int a, b;
    
    int main()
    {
    	a = b = 1;
    
    	pushStack(a);
    	pushStack(b);
    	pushStack(a);
    
    	while (! isEmpty()) {
    		printf("%d
    ", popStack());
    	}
    	
    	return 0;
    }
    

    编译方式:

    gcc -Wall stack.c link.c -o main

    提示出错信息如下:



    但是代码是可以执行的

    定义和声明


    static和extern修饰函数

    上述编译出现错误的原因是:编译器在处理函数调用代码时没有找到函数原型,只好根据函数调用代码做隐式声明,把这三个函数声明为:

    int pushStack(int);
    int popStack(void);
    int isEmpty(void);

    编译器往往不知道去哪里找函数定义,像上面的例子,我让编译器编译main.c,而这几个函数定义却在stack.c里,编译器无法知道,因此可以用extern声明。修改link.c如下:

    #include <stdio.h>
    
    int a, b;
    
    extern void pushStack(int d);
    extern int popStack(void);
    extern int isEmpty(void);
    
    int main()
    {
    	a = b = 1;
    
    	pushStack(a);
    	pushStack(b);
    	pushStack(a);
    
    	while (! isEmpty()) {
    		printf("%d
    ", popStack());
    	}
    	
    	return 0;
    }
    

    这样编译器就不会报警了。这里extern关键字表示这个标识符具有External Linkage.pushStack这个标识符具有External Linkage指的是:如果link.c和stack.c链接在一起,如果pushStack在link.c和stack.c中都声明(在stack.c中的声明同时也是定义),那么这些声明指的是同一个函数,链接后是同一个GLOBAL符号,代表同一个地址。函数声明中的extern可以省略不写,不屑extern的函数声明也表示这个函数具有External Linkage。

    如果用static关键字修饰一个函数声明,则表示该标识符具有Internal Linkage,例如有以下两个程序文件:

    /* foo.c */
    
    static void foo(void) {}

    /*main.c*/
    
    void foo(void);
    
    int main(void) { foo(); return 0;}

    编译链接在一起会出错,原因是:

    虽然在foo.c中定义了函数foo,但是这个函数是static属性,只具有internal Linkage。如果把foo.c编译成目标文件,函数名foo在其中是一个LOCAL的符号,不参与链接过程,所以在链接时,main.c中用到一个External Linkage的foo函数,链接器却找不到它的定义在哪,无法确定它的地址,也就无法做符号解析,只好报错。

    凡是被多次声明的变量或函数,必须有且只有一个声明是定义,如果有多个定义,或者一个定义都没有,链接器就无法完成链接


    static和extern修饰变量

    如果我想在link.c中访问stack.c中定义的int变量count,则可以用extern声明

    #include <stdio.h>
    
    int a, b;
    
    extern void pushStack(int d);
    extern int popStack(void);
    extern int isEmpty(void);
    extern int count;
    
    int main()
    {
    	a = b = 1;
    
    	pushStack(a);
    	pushStack(b);
    	pushStack(a);
    
    	printf("%d
    ", count);
    
    	while (! isEmpty()) {
    		printf("%d
    ", popStack());
    	}
    	
    	return 0;
    }
    

    变量count具有external linkage,它的存储空间是在stack.c中分配的,所以link.c中的变量声明extern int count;不是变量定义,因为它不分配存储空间。

    如果不想在stack.c外让外界访问到count,则可以用static关键字将count声明为Internal Linkage

    区别

    变量生命和函数声明有一点不同,函数声明的extern可写可不写,而变量声明如果不写extern,意思就完全变了。如果上面的例子不写extern就表示在main函数中定义一个全局变量count。

    用static关键字声明具有Internal Linkage的函数和关键字是处于保护内部状态的目的,也是一种封装(Encapsulation)的思想。一个模块中,有些函数是提供给外界使用的,也称为导出(Export)给外界使用,这些函数用extern声明为External Linkage的。


    头文件

    为了防止每次函数extern声明,例如又有一个foo.c也使用pushStack等函数,又需要在foo.c中写多个extern声明,为了避免这种重复麻烦的操作,可以自己定义一个stack.h头文件:

    #ifndef STACK_H
    #define STACK_H
    
    #define STACKSIZE 1000
    
    typedef struct stack {
    	int data[STACKSIZE];
    	int top;
    } stack;
    
    extern void pushStack(int d);
    extern int popStack(void);
    extern int isEmpty(void);
    
    #endif
    

    这样,在link.c里就只需要包含这个头文件就可以了,而不需要写三个函数声明了:

    #include <stdio.h>
    #include "stack.h"
    
    int a, b;
    
    extern int count;
    
    int main()
    {
    	a = b = 1;
    
    	pushStack(a);
    	pushStack(b);
    	pushStack(a);
    
    	printf("%d
    ", count);
    
    	while (! isEmpty()) {
    		printf("%d
    ", popStack());
    	}
    	
    	return 0;
    }
    

    为什么#include <stdio.h>用角括号,而#include "stack.h"用引号?原因
    • 对于用角括号包含的头文件,gcc首先查找-I选项指定的目录,然后查找系统的头文件目录(通常是/usr/include)
    • 对于用“”包含的头文件,gcc首先查找包含头文件的.c文件所在的目录,然后查找-I选项指定的目录,然后查找系统的头文件目录

    用#ifndef #define #endif是为了防止头文件的重复包含,头文件重复包含的问题如下:
    1. 使预处理的速度变慢了,要处理很多本来不需要处理的头文件
    2. 如果a.h包含了b.h,然后b.h又包含了a.h的情况,预处理就陷入死循环了
    3. 头文件按有些代码不允许重复出现

    头文件中的变量和函数声明一定不能是定义。如果头文件中出现变量或函数定义,这个头文件又被多个.c文件包含,那么这些.c文件就不能链接在一起




  • 相关阅读:
    48. Rotate Image
    47. Permutations II
    46. Permutations
    45. Jump Game II
    44. Wildcard Matching
    43. Multiply Strings
    42. Trapping Rain Water
    41. First Missing Positive
    40. Combination Sum II
    39. Combination Sum
  • 原文地址:https://www.cnblogs.com/suncoolcat/p/3281290.html
Copyright © 2011-2022 走看看