多目标文件的链接

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\n", 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\n", 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\n", count); while (! isEmpty()) {
printf("%d\n", 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\n", count); while (! isEmpty()) {
printf("%d\n", 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文件就不能链接在一起





c的链接详解的更多相关文章

  1. Linux 链接详解----静态链接实例分析

    由Linux链接详解(1)中我们简单的分析了静态库的引用解析和重定位的内容, 下面我们结合实例来看一下静态链接重定位过程. /* * a.c */ ; void add(int c); int mai ...

  2. Linux链接命令及软链接、硬链接详解

    命令ln详解 命令ln,所在路径为: 可以看到,它的路径为:/usr/bin/ln,因此,它的执行权限是所有用户 命令的基本功能是创建链接文件(硬链接),例如:ln /etc/issue /tmp 选 ...

  3. mysql数据库表间内外链接详解

    1. 内连接(自然连接) 2. 外连接 (1)左外连接 (左边的表不加限制)(2)右外连接(右边的表不加限制)(3)全外连接(左右两表都不加限制) 3. 自连接(同一张表内的连接) SQL的标准语法: ...

  4. Linux 链接详解----动态链接库

    静态库的缺点: 库函数被包含在每一个运行的进程中,会造成主存的浪费. 目标文件的size过大 每次更新一个模块都需要重新编译,更新困难,使用不方便. 动态库: 是一个目标文件,包含代码和数据,它可以在 ...

  5. Linux 链接详解(2)

    可执行文件加载执行过程: 上一节我们说到ELF文件格式,静态库的符号解析和重定位的内容.这一节我们来分析一下可执行文件. 由上一节我们知道可执行文件也是ELF文件,当程序被加载器加载到内存时是按照EL ...

  6. Linux 链接详解(1)

    可执行文件的生成过程: hello.c ----预处理--->  hello.i ----编译----> hello.s -----汇编-----> hello.o -----链接- ...

  7. linux 软/硬链接详解

    SYNOPSIS ln [OPTION]... [-T] TARGET LINK_NAME (1st form) ln [OPTION]... TARGET (2nd form) ln [OPTION ...

  8. Linux 软硬链接详解

    软链接 软链接: 类似于windows的快捷方式,—>文本文件,但是包含了真实文件的地址               源文件删除,则软连接也删除               软链接可以放在任何文 ...

  9. linux ln链接详解

    1.序 Linux具有为一个文件起多个名字的功能,称为链接.被链接的文件可以存放在相同的目录下,但是必须有不同的文件名,而不用在硬盘上为同样的数据重复备份.另外,被链接的文件也可以有相同的文件名,但是 ...

随机推荐

  1. hdoj 5199 Gunner map

    Gunner Time Limit: 1 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid=5199 D ...

  2. codevs 1004 四子连棋 BFS、hash判重

    004 四子连棋 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold       题目描述 Description 在一个4*4的棋盘上摆放了14颗棋子,其中有7颗白色棋 ...

  3. numpy中的random函数

    1:rand rand(d0, d1, ..., dn)    Random values in a given shape.    Create an array of the given shap ...

  4. IO流-递归遍历目录下指定后缀名结尾的文件名称

    /* *自定义遍历目录下指定后缀名结尾文件的名称的方法: * * param file:指定目录 name:指定后缀名 */ 1 public static void FileName(File fi ...

  5. MYSQL-5.5.37-win32.msi 这个版本得程序包谁有吗 可以给我一下吗?

    之前下载了这个版本得mysql   但是跟服务器链接不上   后来我就卸载了  但由于卸载不干净  现在又删了注册表   好像把这个程序包得什么文件删除了  现在提示配置文件错误  所以有这个程序包得 ...

  6. Oracle | 给表和字段添加注释

    comment  on  column  表名.字段名   is  '注释内容'; comment on column OPERATOR_INFO.MAIN_OPER_ID is '归属操作员'; c ...

  7. PLSQL Developer 配置Oralce11g连接 转

    PLSQL Developer因为没有对应的64位程序,所以用该程序连接Oracle时只能对应x86的Oracle客户端.本示例为Oracle11g,去官网下载instantclient_11_2的x ...

  8. HDOJ 4876 ZCC loves cards

    枚举组合,在不考虑连续的情况下推断能否够覆盖L...R,对随机数据是一个非常大的减枝. 通过检測的暴力计算一遍 ZCC loves cards Time Limit: 4000/2000 MS (Ja ...

  9. Spring DAO vs Spring ORM vs Spring JDBC

    Pat 的疑惑 最近关注于 Spring 提供的数据访问技术,对于 Spring 相关的这几个项目有何不同我不是太明白: Spring-DAO (http://docs.spring.io/sprin ...

  10. ajax访问遇到Session失效问题

    最近由于一个项目,模块切换为ajax请求数据,当Session失效后,ajax请 求后没有返回值,只有响应的html:<html><script type='text/javascr ...