makefile实验一

参考:https://blog.csdn.net/u010655348/article/details/50815370

首先需要弄明白程序编译执行的大致过程:

  • 预处理阶段
  • 词法和语法分析阶段
  • 编译阶段 :对.c文件进行编译,首先编译成汇编语句,再编译成跟CPU相关的二进制码,最后生成的是目标文件(后缀名为.obj)
  • 链接阶段 :将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件(后缀名为.exe)

.h是头文件,内含函数声明、宏定义、结构体定义等等
.c文件是程序文件,内含函数实现、变量定义等等

其实合在一起写也可以,那么分开写的理由何在呢?原因如下:

  • 如果在头文件中实现一个函数体,如果在多个cpp文件中引用它,而且又同时编译多个cpp文件从而生成多个目标文件,那么,在每个引用此头文件的cpp文件所生成的目标文件中都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在链接时,就会发现多个相同的函数,就会报错,函数重复定义
  • 如果在头文件中定义全局变量,势必会对次全局变量赋初值,那么在多个引用此头文件的cpp文件中同样存在相同变量名的拷贝,关键是此变量被赋了初值,所以编译器就会将次变量放入data域,最终在链接阶段,会在data域中存在多个相同的变量,它无法将这些变量统一成一个变量。但是对于声明一个变量,这个变量在头文件中没有初值,编译器就会将它放入BSS域,链接器会对BSS域的多个同名变量仅分配一个存储空间.
  • 如果在cpp文件中声明宏、结构体、函数等,那么我要在另一个cpp文件中引用相应的宏、结构体、函数,就必须再做一次声明,如果我改了一个cpp文件的一个声明却忘了改其他cpp文件中的,就会出现问题。所以把这些公用的东西统一放在一个文件里,以后想用它的cpp文件只需要一个#include就可以了
  • 如果在头文件中声明结构体、函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何来用你的库呢?这时候就可以利用头文件,从头文件中看函数原型,了解如何调用

比如,我在aaa.h里定义了一个函数的声明,然后我在同一个目录下建立aaa.c,里面定义了这个函数的实现,然后在main函数所在的.c文件中#include该头文件aaa.h,然后我就可以使用这个函数了。main在运行时就会找到这个定义了该函数的aaa.c文件,主要过程如下:

  • 1、main函数为标准C/C++程序入口,编译器会先找到main函数所在文件;
  • 2、假定编译程序编译myproject.c(含有主函数main)时,发现他include了mylib.h(其中声明了函数void tests()),那么此时编译器将按照事先设定的路径(include路径列表及代码文件所在的路径)查找与之同名的实现文件mylib.c,如果找到该文件并在其中发现已经该函数的实现代码,则继续编译;如果在指定目录找不到实现文件,或者在找到的实现文件及其后续的各include文件中未找到实现代码,则返回一个编译错误.其实include的过程完全可以看成是一个”文件拼接”的过程.

上面过程就是一个动态链接。

还有一种静态链接,在这种方式下,我们所要做的,就是写出包含函数,类等等声明的头文件(a.h,b.h,…),以及他们对应的实现文件(a.cpp,b.cpp,…),编译程序会将其编译为静态的库文件(a.lib,b.lib,…)。在随后的代码重用过程中,我们只需要提供相应的头文件(.h)和相应的库文件(.lib),就可以使用过去的代码了。

相对动态方式而言,静态方式的好处是实现代码的隐蔽性,即C 中提倡的"接口对外,实现代码不可见"。有利于库文件的转发.

下面写一个具体的实例。

1、普通编译链接

1、分别创建头文件.h、同名实现文件.c、主函数文件.c

vim add_minus.h

#ifndef __ADD_MINUS_H__
#define __ADD_MINUS_H__

int add(int a,int b);
int minus(int a,int b);

#endif /*__ADD_MINUS_H__*/
vim add_minus.c

#include "add_minus.h"

int add(int a,int b)
{
    return a+b;
}

int minus(int a,int b)
{
    return a-b;
}
vim main.c

#include "stdio.h"
#include "add_minus.h"
 int main(void)
 {
    int rst;
    printf("Hello Cacu!\n");

    rst=add(3,2);
    printf("3 + 2 = %d\n",rst);

    rst=minus(3,2);
    printf("3 - 2 = %d\n",rst);

    return 0;
 }

2、将同名实现文件编译成目标文件,结果得到add_minus.o

gcc -c add_minus.c

3、将主函数文件编译成目标文件,结果得到main.o

gcc -c main.c

4、将各个目标文件联合链接成可执行文件

gcc -o main main.o add_minus.o
./main

2、静态链接

1、同上
2、同上
3、将add_minus.o打包到静态库中,将会生成 libadd_minus.a静态库文件:

ar rc libadd_minus.a add_minus.0

4、将目标文件main.o和静态库文件进行链接并执行:

gcc -o main2 main.o -L./ -ladd_minus  //-L./表明库文件位置在当前文件夹-ladd_minus表示链接libadd_minus.a,使用“-l”参数时,前缀“lib”和后缀“.a”是需要省略的。
./main2

3、动态链接

1、分别创建头文件.h、同名实现文件.c、主函数文件.c

vim multi_div.h

#ifndef __MULTI_DIV_H__
#define __MULTI_DIV_H__

int multi(int a,int b);
int div(int a,int b);

#endif /*__MULTI_DIV_H__*/
vim multi_div.c

#include "multi_div.h"

int multi(int a,int b)
{
    return a×b;
}

int div(int a,int b)
{
    return a/b;
}
vim main.c

#include "stdio.h"
#include "multi_div.h"
 int main(void)
 {
    int rst;
    printf("Hello Cacu!\n");

    rst=multi(3,2);
    printf("3 × 2 = %d\n",rst);

    rst=div(6,2);
    printf("6 / 2 = %d\n",rst);

    return 0;
 }

2、将multi_div.c编译成动态链接库,生成libmulti_div.so文件:

gcc multi_div.c -fPIL -shared -o libmulti_div.so

3、将主函数文件编译成目标文件,结果得到main.o

gcc -c main.c

4、将目标文件main.o和动态链接库文件进行链接并执行:

gcc -o main3 main.o -L./ -lmulti_div
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jqq/Makefile_example/chapter0/   //在LD_LIBRARY_PATH中指定库文件路径,否则会因生成的动态库不在库文件搜索路径中而报错
./main3

4、动态库和静态库混合链接

1、重写main.c文件,其他文件内容不变

vim main.c

#include "stdio.h"
#include "multi_div.h"
 int main(void)
 {
    int rst;
    printf("Hello Cacu!\n");

    rst=multi(3,2);
    printf("3 × 2 = %d\n",rst);

    rst=div(6,2);
    printf("6 / 2 = %d\n",rst);

    return 0;
 }

2、将主函数文件编译成目标文件,结果得到main.o

gcc -c main.c

3、将目标文件main.o同时与静态链接库和动态链接库进行链接并执行:

gcc -o main4 main.o -L./ -ladd_minus -lmulti_div
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/jqq/Makefile_example/chapter0/   //如果此前修改过这里就不用修改了
./main4

猜你喜欢

转载自blog.csdn.net/qq_31425127/article/details/80916689