栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

【嵌入式Linux】嵌入式Linux应用开发基础知识之hello程序、GCC编译过程及选项、makefile

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【嵌入式Linux】嵌入式Linux应用开发基础知识之hello程序、GCC编译过程及选项、makefile

文章目录

前言1、hello2、GCC

2.1、GCC编译过程2.2、GCC常用选项 3、Makefile

3.2、Makefile的引入及规则3.3、Makefile的语法3.4、Makefile函数

前言

韦东山嵌入式Linux应用开发基础知识学习笔记

视频教程地址: https://www.bilibili.com/video/BV1kk4y117Tu?spm_id_from=333.788.b_765f64657363.5

1、hello

hello.c

#include 



int main(int argc, char **argv)
{
	if (argc >= 2)
		printf("Hello, %s!n", argv[1]);
	else
		printf("Hello, world!n");
	return 0;
}

main函数参数说明:
  argc:argument count 参数个数
  argv:argument value 参数值
示例:
  输入hello weidongshan
  此时argc=2 argv[0]="hello" argv[1]="weidongshan"

▲程序运行实例

安装完成NFS服务之后执行bash命令:arm-buildroot-linux-gnueabihf-gcc -o hello hello.c
在开发板上挂载Ubuntu的NFS目录来互传文件:
在开发板上使用此命令来使得网络互通的开发板和ubuntu主机之间建立挂载关系:mount -t nfs -o nolock,vers=3 :/home//nfs_rootfs /mnt
例如:mount -t nfs -o nolock,vers=3 10.234.201.221:/home/mi/nfs_rootfs /mnt

▲在开发板上挂载ubuntu文件夹

▲在开发板上执行交叉编译后产生的hello文件
2、GCC 2.1、GCC编译过程

▲GCC编译过程

▲机器码、汇编码、高级语言关系及转化过程

发展历程: 电工开关->纸带打孔(使用机器码)->英文助记符(使用汇编码)->使用高级语言

下面通过一个程序的编译过程来说明这四个步骤:
hello.c

#include 

#define   MAX  20 
#define   MIN  10 

//#define  _DEBUG
#define   SetBit(x)  (1< 

在文件目录下的控制终端输入$ gcc -o hello hello.c -v可以得到编译过程:

Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04) 
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/7/cc1 -quiet -v -imultiarch x86_64-linux-gnu hello.c -quiet -dumpbase hello.c -mtune=generic -march=x86-64 -auxbase hello -version -fstack-protector-strong -Wformat -Wformat-security -o /tmp/cceyrvE0.s
GNU C11 (Ubuntu 7.5.0-3ubuntu1~18.04) version 7.5.0 (x86_64-linux-gnu)
	compiled by GNU C version 7.5.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/7/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/7/include
 /usr/local/include
 /usr/lib/gcc/x86_64-linux-gnu/7/include-fixed
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.
GNU C11 (Ubuntu 7.5.0-3ubuntu1~18.04) version 7.5.0 (x86_64-linux-gnu)
	compiled by GNU C version 7.5.0, GMP version 6.1.2, MPFR version 4.0.1, MPC version 1.1.0, isl version isl-0.19-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: b62ed4a2880cd4159476ea8293b72fa8
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
 as -v --64 -o /tmp/ccfFL7CE.o /tmp/cceyrvE0.s
GNU汇编版本 2.30 (x86_64-linux-gnu) 使用BFD版本 (GNU Binutils for Ubuntu) 2.30
COMPILER_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/
LIBRARY_PATH=/usr/lib/gcc/x86_64-linux-gnu/7/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib/:/lib/x86_64-linux-gnu/:/lib/../lib/:/usr/lib/x86_64-linux-gnu/:/usr/lib/../lib/:/usr/lib/gcc/x86_64-linux-gnu/7/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'
 /usr/lib/gcc/x86_64-linux-gnu/7/collect2 -plugin /usr/lib/gcc/x86_64-linux-gnu/7/liblto_plugin.so -plugin-opt=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper -plugin-opt=-fresolution=/tmp/ccqVYCCi.res -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s -plugin-opt=-pass-through=-lc -plugin-opt=-pass-through=-lgcc -plugin-opt=-pass-through=-lgcc_s --build-id --eh-frame-hdr -m elf_x86_64 --hash-style=gnu --as-needed -dynamic-linker /lib64/ld-linux-x86-64.so.2 -pie -z now -z relro -o hello /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/Scrt1.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crti.o /usr/lib/gcc/x86_64-linux-gnu/7/crtbeginS.o -L/usr/lib/gcc/x86_64-linux-gnu/7 -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/7/../../../../lib -L/lib/x86_64-linux-gnu -L/lib/../lib -L/usr/lib/x86_64-linux-gnu -L/usr/lib/../lib -L/usr/lib/gcc/x86_64-linux-gnu/7/../../.. /tmp/ccfFL7CE.o -lgcc --push-state --as-needed -lgcc_s --pop-state -lc -lgcc --push-state --as-needed -lgcc_s --pop-state /usr/lib/gcc/x86_64-linux-gnu/7/crtendS.o /usr/lib/gcc/x86_64-linux-gnu/7/../../../x86_64-linux-gnu/crtn.o
COLLECT_GCC_OPTIONS='-o' 'hello' '-v' '-mtune=generic' '-march=x86-64'

从编译过程中可以梳理出下图所示的四个步骤(预处理、编译、汇编、链接):

▲hello.c编译过程

上述过程可以使用gcc -o hello hello.c完成,也可以使用下面四条命令分别完成
gcc -E -o hello.i hello.c <-预处理
gcc -S -o hello.s hello.i <-编译
gcc -c -o hello.o hello.s <-汇编
gcc -c hello hello.o <-链接

tips:语法错误是在编译过程中检测到的。

2.2、GCC常用选项

▲GCC常用选项

关于GCC常用选项-c的使用:
有这么几个文件:
main.c

#include 
#include "sub.h"

int main(int argc, char *argv[])
{
       int i;
       printf("Main fun!n");
       sub_fun();
       return 0;
}

sub.c

void sub_fun(void)
{
       printf("Sub fun!n");
}

sub.h

void sub_fun(void);

在文件目录下的终端里输入以下指令:
$ gcc -o test main.c sub.c
$ ./test
可以得到如下结果:

▲终端结果

那么多个文件GCC编译产生的流程又是怎样的呢?
答案如下:

▲多个文件GCC编译流程

这么做的问题:多个文件在进行GCC编译时可能由于一个文件中包含的错误使得整个过程前功尽弃,于是我们可以采用先编译不链接最后统一链接的方法来提高GCC编译的效率。

先编译不链接最后统一链接的方法:
gcc -c -o main.o main.c <-编译
gcc -c -o sub.o sub.c <-编译
gcc -o test main.o sub.o <-链接
./test <-执行

▲GCC常用选项

关于GCC常用选项-I的使用:

如果把main.c中的#include "sub.h"更改为#include 会发生什么?

▲Linux终端界面

很显然编译器没有找到sub.h目录所以中止了编译
输入gcc -o test main.c sub.c -v可以在终端查看不同符号修饰头文件名时编译器会搜寻的目录

▲Linux终端界面

使用-I选项可以拓宽<>修饰符所指定的寻找范围,比如输入$ gcc -o test main.c sub.c -I ./ -v,就会发现<>修饰符所指定的寻找范围中多了当前目录。

▲Linux终端界面

▲GCC常用选项

关于GCC常用选项-L和-l的使用:
在链接库时需要先制作库:库分为动态库和静态库两种。

动态库、静态库两者的区别:
  静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库
  动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在

1、制作、使用静态库:
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
ar crs libsub.a sub.o (可以使用多个.o 生成静态库) <-制作静态库
gcc -o test main.o libsub.a (如果.a 不在当前目录下,需要指定它的绝对或相对路径) <-使用静态库
2、制作、使用动态库:
gcc -c -o main.o main.c
gcc -c -o sub.o sub.c
gcc -shared -o libsub.so sub.o (可以使用多个.o 生成动态库) <-制作动态库
gcc -o test2 main.o libsub.so 或 gcc -o test2 main.o -L ./ -lsub <-使用动态库(其中-l后输入链接哪一个库文件即库文件的文件名sub而非动态库名称libsub.so)
为什么要指定库文件目录和库文件?:当使用文件名寻找动态库时系统会从LIBRARY_PATH中寻找故需要指定库文件目录和库文件来拓展LIBRARY_PATH范围
在运行链接了动态库的文件时不能使用./ 文件名,而要使用export LD LIBRARY_PATH=$LD_LIBRARY_PATH:./指定动态库的位置再运行,这一操作同链接时指定位置是同样道理。

▲Linux终端界面

一些很有用的选项:
gcc -E main.c // 查看预处理结果,比如头文件是哪个
gcc -E -dM main.c > 1.txt // 把所有的宏展开,存在 1.txt 里
gcc -Wp,-MD,abc.dep -c -o main.o main.c // 生成依赖文件 abc.dep,后面 Makefile 会用
echo 'main(){}'| gcc -E -v - // 它会列出头文件目录、库目录(LIBRARY_PATH)

3、Makefile 3.2、Makefile的引入及规则

使用keil, mdk,avr等工具开发程序时点击鼠标就可以编译了。
1、它的内部机制是什么?
2、它怎么组织管理程序?
3、怎么决定编译哪一个文件?

有这么几个文件:
a.c

#include 

int main()
{
	func_b();
	return 0;
}

b.c

#include 

void func_b()
{
	printf("This is Bn");
}

① 为什么需要 Makefile:高效地编译程序,修改源文件或头文件,只需要重新编译牵涉到的文件,就可以重新生成 APP。
② Makefile 的规则

目标(target)… : 依赖(prerequiries)…
命令(command)
如果“依赖文件”比“目标文件”更加新,那么执行“命令”来重新生成“目标文件”。
命令被执行的 2 个条件:依赖文件比目标文件新,或是 目标文件还没生成。

如果想实现:
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -o test a.o b.o
高效编译,就需要在Makefile里这么写:

test:a.o b.o
	gcc -o test a.o b.o
	
a.o : a.c
	gcc -c -o a.o a.c

b.o : b.c
	gcc -c -o b.o b.c

按照规则中所描述的流程运行使用make命令会出现如下效果:

▲Linux终端界面
3.3、Makefile的语法

①、通配符

%.o:表示所用的.o文件
%.c:表示所有的.c文件
$@:表示目标
$<:表示第1个依赖文件
$^:表示所有依赖文件

使用通配符可以简化规则。比如我们对上节程序进行修改代码如下:

test:a.o b.o
	gcc -o test a.o b.o
	
a.o : a.c
	gcc -c -o a.o a.c

b.o : b.c
	gcc -c -o b.o b.c
test: a.o b.o 
	gcc -o test $^ 
%.o : %.c
	gcc -c -o $@ $<

增加一个c.c:
c.c

#include 

void func_c()
{
	printf("This is Cn");
}

修改Makefile:

test: a.o b.o c.o
	gcc -o test $^ 
%.o : %.c
	gcc -c -o $@ $<

运行有以下结果:

▲Linux终端界面

②、假想目标: .PHONY
如果想清楚可执行文件可以在Makefile结尾加上如下代码:

clean:
        rm *.o test

此时可以使用make clean指令来删除已经生成的可执行文件。

▲可执行文件的编译和删除

需要指出的是:make后面可以带上目标名,也可以不带,如果不带目标名的话它就想生成第一个规则里面的第一个目标。

但是仅仅这么做存在一个问题:
如果目录下存在一个名为clean的文件则会出现如下现象:

▲Linux终端界面

为什么?

我们之前说,一个规则能过执行的条件:

*1)目标文件不存在
*2)依赖文件比目标新

现在我们的目录里面有名为“clean”的文件,目标文件是有的,并且没有
依赖文件,没有办法判断依赖文件的时间。这种写法会导致:有同名的"clean"文件时,就没有办法执行make clean操作。解决办法:我们需要把目标定义为假象目标,用关键字PHONY
在Makefile中插入以下代码:

.PHONY: clean

重新执行make clean操作就会执行相应的删除操做。


③变量
Makefile中有两种变量类型:
(1)简单变量/即时变量
A := xxx # A的值即刻确定,在定义时即确定
(2)延时变量
B = xxx # B的值使用到时才确定

想使用变量的时候使用“$”来引用,如果不想看到命令是,可以在命令的前面加上"@"符号,就不会显示命令本身。当我们执行make命令的时候,make这个指令本身,会把整个Makefile读进去,进行全部分析,然后解析里面的变量。常用的变量的定义如下:

:= # 即时变量 
= # 延时变量 
?= # 延时变量, 如果是第1次定义才起效, 如果在前面该变量已定义则忽略这句 
+= # 附加, 它是即时变量还是延时变量取决于前面的定义(附加后变量在输出类似于字符串拼接)

如果在一个Makefile文件中有以下代码

A := $(C)
B = $(C)
C = abc

D = 100ask
D ?= weidongshan

all:
        @echo A = $(A)
        @echo B = $(B)
        @echo D = $(D)
C += 123

按照上述语法规则,执行文件可以得到以下结果:

▲Linux终端界面
3.4、Makefile函数

几个常用的Makefile函数:

$(foreach var,list,text)#对list中的每个var执行text公式
$(filter pattern...,text) # 在text中取出符合patten格式的值 
$(filter-out pattern...,text) # 在text中取出不符合patten格式的值
$(wildcard pattern) # pattern定义了文件名的格式, wildcard取出其中存在的文件。
$(patsubst pattern,replacement,$(var)) #从var变量里面取出每一个值
                                        #如果这个符合pattern格式
                                        #把它替换成replacement格式

实例:

A = a b c
B = $(foreach f, $(A), $(f).o) #给A中的每一个变量添加.o后缀

C = a b c d/

D = $(filter %/, $(C)) #返回C中满足%/的变量
E = $(filter-out %/, $(C)) #返回除C中满足%/的变量的所有变量

files = $(wildcard *.c) #返回目录下满足*.c条件的所有文件名

files2 = a.c b.c c.c d.c e.c  abc
files3 = $(wildcard $(files2)) #返回目录下满足和file2变量中含有相同字符串文件的文件名

dep_files = $(patsubst %.c,%.d,$(files2)) #从files2变量中取出符合%.c格式的值
										  #将其替换成%.d格式

all:
        @echo B = $(B)
        @echo D = $(D)
        @echo E = $(E)
        @echo files = $(files)
        @echo files3 = $(files3)
        @echo dep_files = $(dep_files)

执行Makefile得到如下结果:

▲Linux终端界面
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/729426.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号