2019年7月19日星期五(交叉编译工具)

一、工程管理文件makefile

1. 什么是makefile

makefile称之为工程管理文件,用于管理整个工程所有.c文件编译规则。

2. makefile是一个工程中是一定要写的吗?

如果在项目源码中,文件不多的时候,一般makefile不用写,因为编译命令比较简单。

如果在项目源码,源文件(.c) 头文件(.h)这些文件比较多,一般会携带一个makefile。

makefile目的: 为了提高编译效率。

. 项目工程应该由哪些文件组成?

1. 简单版

源程序文件    main.c   -> 包含main函数在内的.c文件。

功能程序文件  fun1.c   -> 第1个功能文件    -> 功能性最好以功能来命名,例如: play_music.c

功能程序文件  fun2.c   -> 第2个功能文件

...

功能程序文件  funn.c   -> 第n个功能文件

头文件:      head.h   -> 结构体声明,函数声明,宏定义,系统头文件..

库文件:     

工程管理文件: makefile  -> 里面有一套可以编译整个项目的规则。

2. 复杂版

src/      -> 所有的.c文件

include/  -> 所有的.h文件

lib/      -> 所有的库文件

bin/      -> 编译之后的可执行文件

makefile  -> 里面有一套可以编译整个项目的规则。

如果没有makefile,上述例子中,用户应该每次都要输入: "gcc main.c fun1.c fun2.c -o main -I ."

学习书写makefile之后,就可以让makefile帮我们做这件事情了。

. makefile书写规则?

1. makefile文件的核心:"依赖" "目标"

"依赖"  -> 在编译文件时,才需要写依赖,如果不是编译,则不需要写依赖。一般依赖指的就是.c文件。

"目标"  -> 你最终想得到的文件,一般指的是可执行程序

 在上述例子中: 

  main.c fun1.c fun2.c  -> 依赖

  main    -> 目标

2. makefile的书写规则?

有依赖情况下:

========================================================

1)确定好依赖与目标叫什么名字。

2)按照以下格式来写makefile:

目标:依赖(如果有多个依赖,则每一个依赖之间使用空格分开

<Tab键>编译规则 

注意:

1)<Tab键>不等于四个空格,编译规则之所以能识别出来,就是因为这个Tab键

2)在编译规则中如何调用依赖与目标

   $^   -> 代表所有的依赖文件  -> 等价于  main.c fun1.c fun2.c

   $@   -> 代表目标文件       -> 等价于 main

无依赖情况下:

1)确定目标叫什么名字。

2)按照以下规则来写makefile:

 

目标:

<Tab键>执行命令

3. 举实例。

1makefilehelloworld版本1

target:                      //目标一定要写,但是依赖不一定。

       echo helloworld      //规则,执行make命令,就会执行这套规则了。

执行make,结果:

echo helloworld   -> 默认将规则命令输出到屏幕中,如果不想看到,则只需要在规则前面加@

helloworld

2makefilehelloworld版本2

target:                      //目标一定要写,但是依赖不一定。

       @echo helloworld      //规则,执行make命令,就会执行这套规则了。

执行make,结果:

helloworld

   练习: 修改code1的代码,写一个makefile,使得执行make时候,可以生成main这个可执行程序。

main:main.c fun1.c fun2.c

       gcc $^ -o $@

执行make,结果:

gcc main.c fun1.c fun2.c -o main   -> 在当前目录下生成一个main文件。

再次执行make,结果:

make: `main' is up to date.   -> 因为makefile检测到所有的依赖文件都没有更新,所以不会重新编译。

                           -> 只要其中一个依赖文件的修改日期与之前的不一样,都会重新编译。

四、makefile多个目标情况。

例子:

main:         -> 当执行make,默认只会执行第一个目标。

       xxxx;

main1:        -> 当需要执行某个特定的目标时,在make时候,需要指定该目标的名字:  make main1

       yyyy;

main2:

       zzzz;

修改makefile为:

main:main.c fun1.c fun2.c

       gcc $^ -o $@

      

clean:

       rm main

 

. makefile的变量种类。

1. 自定义变量   -> makefile不需要声明类型,只需要定义名字就可以。变量默认是字符串类型的。

规则:

1)变量名与C语言规则一致。

2)给变量赋值时,等号两边都可以有空格,也可以没有。但是shell编程中变量赋值时,等边两边不能有空格。

3)引用makefile中变量时,需要在变量前面添加$。  $A  $(A)

4)因为变量都是默认是字符串类型,所以""可以省略。

例子:

A = Hello  等价于  A = "hello"

B=world

C=$A $(B)

  练习2: 修改makefile,将所有的依赖放置到一个变量。

 

C_SOURCE=main.c fun1.c fun2.c

 

main:$(C_SOURCE)

       gcc $^ -o $@

      

clean:

       rm main

2. 系统预设定变量

有些变量是系统中已经写好的,并且已经赋了初值的,这些变量的值就可以直接使用。

CC:  -> 编译器名字,默认系统赋值是cc   CC=cc   cc等价于gcc

RM:  -> 删除命令,默认系统赋值是rm -f   RM=rm -f

  练习3:把系统预设定变量加入到makefile中。尝试交叉编译与本地编译。

CC=arm-linux-gcc

 

C_SOURCE=main.c fun1.c fun2.c

 

main:$(C_SOURCE)

       $(CC) $^ -o $@

clean:

       rm main

3. 自动化变量   -> 变量的值不是固定的,而是会变化的。

   $^  -> 代表当前的所有依赖

   $@  -> 代表目标

例子:

main:

    $@  -> 代表main

clean:

    $@  -> 代表clean

例子:

CC=arm-linux-gcc

C_SOURCE=main.c fun1.c fun2.c

TARGET=main

$(TARGET):$(C_SOURCE)

       $(CC) $^ -o $@

      

clean:

       rm $(TARGET)

. makefile伪指令。

假设makefile有一套规则:

------------------------------

clean:

       $(RM) main

------------------------------

当我们执行make clean时,就会执行这套规则,如果当前目录下有一个文件名字叫clean

那么再执行make clean就会提示:make: `clean' is up to date.

解决方案:将clean这个目标添加为伪指令。

添加为伪指令的含义是什么?

就是告诉makefile,这个目标不是一个生成的文件。

makefile中添加代码:

.PHONY:clean

-----------------------------------------

CC=arm-linux-gcc

C_SOURCE=main.c fun1.c fun2.c

TARGET=main

$(TARGET):$(C_SOURCE)

       $(CC) $^ -o $@

.PHONY:clean

clean:

       $(RM) $(TARGET)

----------------------------------------

 

. makefile函数  ->  wildcard  

1. makefile中调用函数方式: $(函数名参数1,参数2,参数3.....)

   C语言中调用函数方式:    函数名(参数1,参数2,参数3...)

2. wildcard函数作用:在指定的路径下找到相匹配的文件

例子: SRC = $(wildcard *.c)  -> 在当前目录下寻找所有的.c结尾的文件,并把结果保存在SRC变量,每个结果之间使用空格分开。

      SRC = $(wildcard /*.c)  -> 在根目录下寻找所有的.c结尾的文件。

注意:

*  -> 代表任意长度的任意字符。

最终得到简单版通用的makefile为:

CC=arm-linux-gcc

C_SOURCE=$(wildcard *.c)

TARGET=main

INCLUDE_PATH=-I .

$(TARGET):$(C_SOURCE)

       $(CC) $^ -o $@ $(INCLUDE_PATH)

.PHONY:clean

clean:

       $(RM) $(TARGET)

  练习4: 将简单版通用的makefile修改为复杂版的!

CC=arm-linux-gcc

C_SOUCRE=$(wildcard ./src/*.c)

TARGET=./bin/main

INCLUDE_PATH=-I ./include

$(TARGET):$(C_SOUCRE)

       $(CC) $^ -o $@ $(INCLUDE_PATH)

.PHONY:clean

clean:

       $(RM) $(TARGET)

.嵌入式linux的库文件。

1. 什么是库文件?

库文件在linux下以二进制形式存在,往往我们编译程序时,需要链接这些库。

2. 库文件的格式?

1)静态库        --->  libxxx.a

2)动态库/共享库 --->  libxxx.so

  例子: libxxx.so.9.1.0

  lib: 库的前缀

  xxx: 库的名字

  .a/.so: 库的后缀

  .9: 库的版本号

  .1.0:库的修正号

3. 静态库与动态库的区别?

静态库特点:libxxxx.a  -> 去图书馆(libxxxx.a)中把图书(函数接口)拿走

1)程序在编译时,如果是链接静态库,那么就等于把库的内容拿走,就会导致可执行程序的大小非常大。

2)由于是静态库编译,所以在编译程序之后,可执行文件不需要静态库的存在都可以运行。

动态库特点:libxxx.so  -> 去图书馆(libxxx.so)看看书(函数接口)而已,看完了,就把书放回图书馆,并没有拿走。

1)程序在编译时,如果是链接动态库,仅仅链接而已,没有拿走库的东西,相对于静态库来讲,会比较小。

2)由于是动态库编译,在执行可执行文件之后,动态库必须存在,可执行文件才能正常运行。

4. 如何制作库文件?

只有包含功能性函数.c文件才能制作成库文件,含有main函数在内的.c文件不能制作为库文件。

1)静态库的制作。  -> 没有架构可言。

1.将工程所有不包含main函数在内的.c文件找到。

2.将这些.c文件全部编译为.o文件。

  gcc fun1.c -o fun1.o -c

  gcc fun2.c -o fun2.o -c

3.将这些.o文件全部塞进一个.a文件

  ar rcs libmy.a fun1.o fun2.o

4.编译程序

  gcc main.c -o main -L . -lmy

5.执行

  ./main  就可以出来结果!

注意:

1)-L .  -> 只是告诉系统去当前目录下寻找库文件,但是没有告诉链接哪个。

2)-lmy  -> -l(小写字母L)没有空格的,要紧跟库的名字(my),注意不是库文件的名字(libmy.a)

3)制作库与编程程序所使用到的工具链必须一致。

  练习5:熟悉交叉编译静态库的制作。

arm-linux-gcc fun1.c -o fun1.o -c

arm-linux-gcc fun2.c -o fun2.o -c

ar rcs libmy.a fun1.o fun2.o

arm-linux-gcc main.c -o main -L . -lmy

结论: 库的架构必须与编译程序时的工具链架构一致,否则就会出错:

库:x86  编译: arm-linux-gcc

 

/usr/local/arm/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/../../../../arm-none-linux-gnueabi/bin/ld: skipping incompatible ./libmy.a when searching for -lmy

/usr/local/arm/bin/../lib/gcc/arm-none-linux-gnueabi/4.5.1/../../../../arm-none-linux-gnueabi/bin/ld: cannot find -lmy

库: ARM  编译: gcc

/usr/bin/ld: skipping incompatible ./libmy.a when searching for -lmy

/usr/bin/ld: cannot find -lmy

collect2: ld returned 1 exit status

  练习6:把库添加到复杂版的lib目录,修改makefile。

CC=arm-linux-gcc

C_SOUCRE=$(wildcard ./src/*.c)

TARGET=./bin/main

INCLUDE_PATH=-I ./include

LIBRARY_PATH=-L ./lib -lmy

$(TARGET):$(C_SOUCRE)

       $(CC) $^ -o $@ $(INCLUDE_PATH) $(LIBRARY_PATH)

.PHONY:clean

clean:

       $(RM) $(TARGET)

2)动态库的制作   -> 有架构可言

1. 将工程所有不包含main函数在内的.c文件找到。

2. 将这些.c文件全部编译为.o文件。

   gcc fun1.c -o fun1.o -c -fPIC

   gcc fun2.c -o fun2.o -c -fPIC

3. 将这些.o文件编译为libxxx.so文件

   gcc -shared -fPIC -o libmy2.so fun1.o fun2.o

4. 编译程序

   gcc main.c -o main -L . -lmy2

5. 执行

   ./main

终端提示错误:

./main: error while loading shared libraries: libmy2.so: cannot open shared object file: No such file or directory

//执行main程序时需要加载共享库:libmy2.so时出现了错误: 因为文件不存在所以,不能打开这个文件。

解决方案:

1. 把制作的好的库文件拷贝到/lib中   -> 不推荐

2. 把该库的路径添加到环境变量  LD_LIBRARY_PATH

   假设库在/home/gec。

   export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/gec   -> 系统就会家目录下找这个"libmy2.so"这个东东!

3. 重新执行代码

   ./main

  练习7:熟悉交叉编译动态库的制作。

  练习8:把库添加到复杂版的lib目录,修改makefile。

 

posted @ 2019-07-19 19:12  柚子皮max  阅读(276)  评论(0编辑  收藏  举报