GCC、Makefile编程学习

相关学习资料html

http://gcc.gnu.org/
https://gcc.gnu.org/onlinedocs/
http://zh.wikipedia.org/zh/GCC
http://blog.csdn.net/casularm/article/details/316149
http://www.bccn.net/Article/kfyy/cyy/jc/200409/9.html
http://linux.chinaunix.net/techdoc/develop/2008/12/16/1053006.shtml
http://www.91linux.com/html/article/program/cpp/20071203/8745.html
http://www.cnblogs.com/hnrainll/archive/2012/07/05/2578277.html
http://www.cnblogs.com/ggjucheng/archive/2011/12/14/2287738.html
http://www.chinaunix.net/old_jh/23/408225.html
http://blogimg.chinaunix.net/blog/upfile/070907021605.pdf

 

目录linux

1. GCC简介
2. GCC的编译
3. GCC的命令行参数
4. GCC编译中遇到的常见错误
5. Makefile
6. Makefile Example

 

1. GCC简介程序员

GCC(GNU Compiler Collection GNU 编译器家族)。GCC目前能够支持:shell

1. C 
2. Ada  
3. C++  
4. Java  
5. Objective C 
6. Pascal 语言
7. COBOL
8. 函数式编程
9. 逻辑编程的Mercury等等

GCC目前几乎支持全部的硬件(处理器)架构编程

 

 

2. GCC的编译ubuntu

一个程序编译过程是分为四个阶段进行的,即:架构

1. 预处理(也称预编译,Preprocessing)
2. 编译(Linking)
3. 汇编 (Assembly)
4. 链接(Linking)

使用gcc指令能够一步完成,也能够单步独立进行,方便程序员获取中间代码代码,进行调试函数式编程

0x0: 编写源代码函数

0x1: 预处理(也Preprocessing)工具

预处理是C语言程序从源代码变成可执行程序的第一步,主要是C语言编译器对各类预处理命令进行处理,包括:

1. 头文件的包含: #include
2. 宏定义的扩展: 
    1) #define: 定义一个预处理宏
    #define MACRO_NAME(args) tokens(opt)
    以后出现的MACRO_NAME将被替代为所定义的标记(tokens). 宏可带参数, 然后面的标记也是可选的. 
    2) #undef: 取消宏的定义
#define除了能够独立使用以便灵活设置一些参数外,还经常和条件编译语法结合使用,以便灵活地控制代码块的编译与否,也能够用来避免同一个头文件的屡次包含
3. 条件编译的选择: 
    1) #ifdef: 判断某个宏是否被定义, 若已定义, 执行随后的语句
    2) #ifndef: 与#ifdef相反, 判断某个宏是否未被定义 
    3) #if: 编译预处理中的条件命令, 至关于C语法中的if语句
    4) #elif: 若#if, #ifdef, #ifndef或前面的#elif条件不知足, 则执行#elif以后的语句, 至关于C语法中的else-if
    5) #else: 与#if, #ifdef, #ifndef对应, 若这些条件不知足, 则执行#else以后的语句, 至关于C语法中的else
    6) #endif: #if, #ifdef, #ifndef这些条件命令的结束标志.
    7) #line: 标志该语句所在的行号
4. 编译器指示指令(给编译器看的指令)
    1) #pragma: 说明编译器信息
    #pragma用编译器用来添加新的预处理功能或者显示一些编译信息. #pragma的格式是各编译器特定的, gcc的以下:
    #pragma GCC name token(s)
    2) #warning: 显示编译警告信息
    #warning, #error分别用于在编译时显示警告和错误信息
    #error tokens
    3) #error: 显示编译错误信息
    #warning tokens

gcc预处理指令:

gcc -E test.c -o test.i(输出预处理内容到文件)
gcc -E test.c(输出预处理内容到命令行)

0x2: 编译(Linking)

C语言编译器会进行以下步骤进行编译过程:

1. 词法分析
2. 语法分析: 经过-std指定遵循哪一个标准
3. 代码优化和存储分配: 根据优化(-O)要求进行
4. 接着会把源代码翻译成中间语言,即汇编语言(由于C/C++是编译性语言)

gcc编译指令:

gcc -S test.i -o test.s(-S选项,表示在程序编译期间,在生成汇编代码后,中止,-o输出汇编代码文件)

0x3: 汇编(Assembly)

把做为中间结果的汇编代码翻译成了机器代码,即目标代码(能够在目标机器运行的机器码),这一步其实就是咱们在进行汇编编程中的汇编编译过程。这一步产生的所谓机器代码还不能够直接运行,还须要下一步的连接程序进行连接后才能够

对于这一步的工做来讲,咱们可使用:

1. as汇编工具来汇编AT&T格式的汇编语言,由于gcc产生的中间代码就是AT&T格式的
2. 或者直接用gcc的汇编编译器来进行汇编编译

这一步执行后,获得的就已是机器码了

1. 用as把汇编语言汇编成目标代码
//注意,这里的.s文件是上一步编译后的文件
as -o test.o test.s       

2. gcc汇编指令
gcc -c test.s -o test.o

0x4: 连接(Linking)

连接的目的是处理可重定位文件(.so、.dll),重定位是将"符号引用"与"符号定义"进行连接的过程。这句话能够这么理解,咱们在写代码中会使用到不少的函数和变量,这些"函数名"、和"变量名"最终须要被转换为对应的虚拟内存地址,CPU才能够执行,这个过程就是连接过程
连接又分为:

1. 静态连接
程序开发阶段程序员用ld(gcc实际上在后台调用了ld)静态连接器手动连接的过程
例如:
若是连接到可执行文件中的是静态链接库libmyprintf.a,那么. rodata节区在连接后须要被重定位到一个绝对的虚拟内存地址,以便程序运行时可以正确访问该节区中的字符串信息

2. 动态连接
动态连接则是程序运行期间系统调用动态连接器(ld-linux.so)自动连接的过程
例如:
对于puts,由于它是动态链接库libc.so中定义的函数,因此会在程序运行时经过动态符号连接找出puts函数在内存中的地址,以便程序调用该函数 

关于交叉编译

交叉编译通俗地讲就是在一种平台上编译出能运行在体系结构不一样的另外一种平台上,好比在咱们地PC平台(X86 CPU)上编译出能运行在sparc CPU平台上的程序,编译获得的程序在X86 CPU平台上是不能运行的,必须放到sparc CPU平台上才能运行。固然两个平台用的都是linux。这种方法在异平台移植和嵌入式开发时用得很是广泛。
相对与交叉编译,咱们日常作的编译就叫本地编译,也就是在当前平台编译,编译获得的程序也是在本地执行。
用来编译这种程序的编译器就叫交叉编译器,相对来讲,用来作本地编译的就叫本地编译器,通常用的都是gcc,但这种gcc跟本地的gcc编译器是不同的,须要在编译gcc时用特定的configure参数才能获得支持交叉编译的gcc。
为了避免跟本地编译器混淆,交叉编译器的名字通常都有前缀,好比sparc-xxxx-linux-gnu-gcc、sparc-xxxx-linux-gnu-g++等等
交叉编译器使用方法跟本地的gcc差很少,但有一点必需要注意的是:必须用-L和-I参数指定编译器用sparc系统的库和头文件,不能用本地(X86)的库,例如

sparc-xxxx-linux-gnu-gcc test.c -L/path/to/sparcLib -I/path/to/sparcInclude

 

 

3. GCC的命令行参数

Usage: gcc [options] file...
Options:
    1) -combine: 一次传给gcc多个源文件
    2) -save-temps: 保留中间文件 
    3) -B <directory>: 添加gcc搜索的源文件路径 
    4) -E 源代码file: 只单独进行预处理(预编译)这一步  
    5) -S 预处理后的file: 只单独进行编译这一步
    6) -c 编译后待汇编的文件: 只进行编译和汇编这两步 
    7) -o <file>: 输出文件 
    
    8) -l参数
    -l参数就是用来指定程序要连接的库,-l参数紧接着就是库名,库名和真正的库文件名之间有一个对应关系呢,例如数学库,他的库名是m,他的库文件名是libm.so,把库文件名的头lib和尾.so去掉就是库名了
    值得注意的是,-l参数在搜索指定库名的时候会从gcc默认的"库路径"下去查找
        1. /lib
        2. /usr/lib
        3. /usr/local/lib
    因此,如咱们自已要用到一个第三方提供的库名字叫libtest.so,那么咱们只要把libtest.so拷贝到这些路径中(例如/usr/lib)
    example:
    编译时加上-ltest参数,咱们就能用上libtest.so库了(咱们还须要与libtest.so配套的头文件,对咱们须要使用的函数进行声明,咱们才能在代码中使用)
    9) -L参数
    咱们都知道,放在里的库直接用-l参数就能连接了,但若是库文件没放在这三个目录里,而是放在其余目录里,这时咱们只用-l参数的话,连接仍是会出错,出错信息大概是:"/usr/bin/ld: cannot find -lxxx",也就是连接
程序ld在那3个目录里找不到
-L参数跟着的是库文件所在的目录名。再好比咱们把libtest.so放在/aaa/bbb/ccc目录下,那连接参数就是-L/aaa/bbb/ccc -ltest 10) -include -include用来包含头文件,但通常状况下包含头文件都在源码里用#include xxxxxx实现,因此-include参数不多用 11) -I参数 -I参数是用来指定头文件目录。/usr/include目录通常是不用指定的,gcc知道去那里找(默认路径),可是若是头文件不在/usr/include里咱们就要用-I参数指定了,好比头文件放在/myinclude目录里,那编译命令行就要加上
"-I/myinclude"参数了,若是不加你会获得一个"xxxx.h: No such file or directory"的错误 -I参数能够用相对路径,好比头文件在当前目录,能够用-I.来指定 12) -O参数 这是一个程序优化参数,通常用-O2就是,用来优化程序用的,好比 gcc test.c -O2 -o test 优化获得的程序比没优化的要小,执行速度可能也有所提升。关于编译原理、优化的相关知识又是另外一类庞大的体系了,之后有时间能够研究一下 13) -shared参数 编译动态库时要用到,好比gcc -shared test.c -o libtest.so

 

 

4. GCC编译中遇到的常见错误

0x1: undefined reference to 'xxxxx'错误

这是连接(Linking)错误,不是编译(Linking)错误,已经到了第4步出错的,也就是说若是只有这个错误,说明你的程序源码自己没有问题,是你用编译器编译时参数用得不对

reason:
多是因为没有指定连接程序要用到得库,好比你的程序里用到了一些数学函数,那么你就要在编译参数里指定程序要连接数学库

solution: 在编译命令行里加入程序要用到的库,

0x2: libxxxx.so未创建软连接报错

大部分libxxxx.so只是一个连接,好比

libm.so它连接到/lib/libm.so.x
/lib/libm.so.6连接到/lib/libm-2.3.2.so

若是没有这样的连接,仍是会出错,由于gcc ;loader只会找libxxxx.so,因此若是你要用到xxxx库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,作一个连接就能够了,例如

ln -s libxxxx-x.x.x.so libxxxx.so

不少库开发包提供了生成连接参数的程序,名字通常叫xxxx-config,通常放在/usr/bin目录下,好比gtk1.2的连接参数生成程序是gtk-config,执行gtk-config --libs就能获得如下输出

"-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic -lgmodule -lglib -ldl -lXi -lXext -lX11 -lm"

这就是编译一个gtk1.2程序所需的gtk连接参数

 

 

5. Makefile

0x1: Makefile规则格式

makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则(rules)来指定

1. 哪些文件须要先编译
2. 哪些文件须要后编译
3. 哪些文件须要从新编译
4. 甚至于进行更复杂的功能操做,由于makefile就像一个Shell脚本同样,其中也能够执行操做系统的命令

makefile带来的好处就是"自动化编译",一旦写好,只须要一个make命令,整个工程彻底自动编译,极大的提升了软件开发的效率
make是一个linux下的命令工具,是一个解释makefile中指令的命令工具,通常来讲,大多数的IDE都有这个命令,好比:Delphi的make,Visual C++的nmake,Linux下GNU的make

/*
1. 变量声明
在Makefile中声明变量
var = value;
在须要使用value的地方直接使用$(var)就能够了
*/

/*
2. Makefile的隐含规则
在使用make编译.c源文件时,编译.c源文件规则的命令能够不用明确给出。这是由于make自己存在一个默认的规则,可以自动完成对.c文件的编译并生成对应的.o文件。它执行命令"cc -c"来编译.c源文件。在Makefile中咱们只须要
给出须要重建的目标文件名(一个.o文件),make会自动为这个.o文件寻找合适的依赖文件(对应的.c文件。对应是指:文件名除后缀外,其他都相同的两个文件),并且使用正确的命令来重建这个目标文件
*/ TARGET... : PREREQUISITES... COMMAND ... ... /* 3. 清理工做 Makefile规则除了完成源代码编译以外,也能够完成其它任务。例如实现清除当前目录中编译过程当中产生的临时文件 1) 经过".PHONY"特殊目标将"clean"目标声明为伪目标。避免当磁盘上存在一个名为"clean"文件时,目标"clean"所在规则的命令没法执行 2) 在命令行以前使用"-",意思是忽略命令"rm"的执行错误 值得注意的是,目标"clean"没有出如今终极目标"target"依赖关系中(终极目标的直接依赖或者间接依赖),因此咱们执行"make"时,目标"clean"所在的规则将不会被处理。当须要执行此规则,要在make的命令行选项中明确指定这
个目标,即执行"make clean"
*/ .PHONY:clean clean: -rm target $(var) 1. target: 规则的目标 一般是最后须要生成的文件名或者为了实现这个目的而必需的中间过程文件名。能够是: 1) .o文件 2) 最后的可执行程序的文件名 3) 一个make执行的动做的名称,如目标"clean",咱们称这样的目标是"伪目标" 2. prerequisites: 规则的依赖 生成规则目标所须要的文件名列表。一般一个目标依赖于一个或者多个文件。当规则的目标是一个文件,在它的任何一个依赖文件被修改之后,在执行"make"时这个目标文件将会被从新编译或者从新链接 3. command: 规则的命令行 规则所要执行的动做(任意的shell命令或者是可在shell下执行的程序),它限定了make执行这条规则时所须要的动做。一个规则能够有多个命令行,每一条命令占一行。 须要注意的是:每个命令行必须以[Tab]字符开始,[Tab]字符告诉make此行是一个命令行。make按照命令完成相应的动做 命令(command)就是在任何一个目标的依赖文件发生变化后重建目标的动做描述。一个目标能够没有依赖而只有动做(指定的命令)。好比Makefile中的目标"clean",此目标没有依赖,只有命令。它所定义的命令用来删除make过程产生
的中间文件(进行清理工做)

从概念上来讲,一个Makefile文件是由一个个的规则组成,每一个规则包括: target、prerequisites、command
make程序根据规则的依赖关系,决定是否执行规则所定义的命令的过程咱们称之为"执行规则"

0x2: Makefile运行流程

1. 当在shell提示符下输入"make"命令之后。make读取当前目录下的Makefile文件,并将Makefile文件中的第一个目标做为其执行的"终极目标",开始处理第一个规则,默认的状况下,make执行的是Makefile中的第一个规则,此
规则的第一个目标称之为"最终目的"或者"终极目标"。结合Makefile的工做原理就会很好理解为何第一个规则会是"最终规则",由于Makefile的规则是递归依赖的,最终规则递归地依赖于下面的其余规则 2. make在执行这个规则所定义的命令以前,首先处理目标"终极目标"的全部的依赖目标,多是: 1) 伪目标 2) .so文件 对.o文件为目标的规则处理有下列三种状况: 2.1) 目标.o文件不存在,使用其描述规则建立它 2.2) 目标.o文件存在,目标.o文件所依赖的.c源文件、.h文件中的任何一个比目标.o文件"更新"(在上一次make以后被修改),则根据规则从新编译生成它 2.3) 目标.o文件存在,目标.o文件比它的任何一个依赖文件的(c源文件、.h文件)"更新"(它的依赖文件在上一次make以后没有被修改),则什么也不作 3) 可执行文件 对于Makefile的工做流程的理解,咱们必定要牢记递归的概念,即make会将终极目标的依赖目标从右到左一次回退所有执行完,出如今"终极目标"的依赖列表中的目标必定要所有执行完以后,终极目标才能被最终完成 3. 若是在处理终极目标的依赖目标的时候,又出现了新的依赖目标,则继续递归地进行处理(相似函数栈调用原理) 4. . 在Makefile中一个规则的目标若是不是"终极目标"所依赖的(或者"终极目标"的依赖文件所依赖的),那么这个规则将不会被执行,除非明确指定执行这个规则(能够经过make的命令行指定重建目标,那么这个目标所在的规则就会
被执行,例如"make clean") 5. 更新(或者建立)终极目标的过程当中,若是任何一个规则执行出现错误make就当即报错并退出。 整个过程make只是负责执行规则,而对具体规则所描述的依赖关系的正确性、规则所定义的命令的正确性不作任何判断。 就是说,一个规则的依赖关系是否正确、描述重建目标的规则命令行是否正确,make不作任何错误检查。 所以,须要正确的编译一个工程。须要在提供给make程序的Makefile中来保证其依赖关系的正确性、和执行命令的正确性。

0x3: Example

edit : main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o       /*注释:若是后面这些.o文件比edit可执行文件新,那么才会去执行下面这句命令*/
    cc -o edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

main.o : main.c defs.h
    cc -c main.c
kbd.o : kbd.c defs.h command.h
    cc -c kbd.c
command.o : command.c defs.h command.h
    cc -c command.c
display.o : display.c defs.h buffer.h
    cc -c display.c
insert.o : insert.c defs.h buffer.h
    cc -c insert.c
search.o : search.c defs.h buffer.h
    cc -c search.c
files.o : files.c defs.h buffer.h command.h
    cc -c files.c
utils.o : utils.c defs.h
    cc -c utils.c
clean :
    rm edit main.o kbd.o command.o display.o \
        insert.o search.o files.o utils.o

Relevant Link:

http://wiki.ubuntu.org.cn/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%86%99Makefile:MakeFile%E4%BB%8B%E7%BB%8D#.E4.B8.80.E4.B8.AA.E7.A4.BA.E4.BE.8B

 

6. Makefile Example

hellomake.c hellofunc.c hellomake.h
#include <hellomake.h>

int main() {
  // call a function in another file
  myPrintHelloMake();

  return(0);
}
#include <stdio.h>
#include <hellomake.h>

void myPrintHelloMake(void) {

  printf("Hello makefiles!\n");

  return;
}
/*
example include file
*/

void myPrintHelloMake(void);

0x1: Makefile 1

hellomake: hellomake.c hellofunc.c
    gcc -o hellomake hellomake.c hellofunc.c -I.

0x2: Makefile 2

CC=gcc
CFLAGS=-I.

hellomake: hellomake.o hellofunc.o
    $(CC) -o hellomake hellomake.o hellofunc.o -I.

0x3: Makefile 3

CC=gcc
CFLAGS=-I.
DEPS = hellomake.h

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: hellomake.o hellofunc.o 
    gcc -o hellomake hellomake.o hellofunc.o -I.

0x4: Makefile 4

CC=gcc
CFLAGS=-I.
DEPS = hellomake.h
OBJ = hellomake.o hellofunc.o 

%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS)

0x5: Makefile 5

IDIR =../include
CC=gcc
CFLAGS=-I$(IDIR)

ODIR=obj
LDIR =../lib

LIBS=-lm

_DEPS = hellomake.h
DEPS = $(patsubst %,$(IDIR)/%,$(_DEPS))

_OBJ = hellomake.o hellofunc.o 
OBJ = $(patsubst %,$(ODIR)/%,$(_OBJ))


$(ODIR)/%.o: %.c $(DEPS)
    $(CC) -c -o $@ $< $(CFLAGS)

hellomake: $(OBJ)
    gcc -o $@ $^ $(CFLAGS) $(LIBS)

.PHONY: clean

clean:
    rm -f $(ODIR)/*.o *~ core $(INCDIR)/*~

Relevant Link:

http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/

 

Copyright (c) 2014 LittleHann All rights reserved

相关文章
相关标签/搜索