通常所说的GCC是GUN Compiler Collection的简称,除了编译程序之外,它还含其他相关工具,所以它能把易于人类使用的高级语言编写的源代码构建成计算机能够直接执行的二进制代码。GCC是Linux平台下最常用的编译程序,它是Linux平台编译器的事实标准。同时,在Linux平台下的嵌入式开发领域,GCC也是用得最普遍的一种编译器。GCC之所以被广泛采用,是因为它能支持各种不同的目标体系结构。例如,它既支持基于宿主的开发(简单讲就是要为某平台编译程序,就在该平台上编译),也支持交叉编译(即在A平台上编译的程序是供平台B使用的)。目前,GCC支持的体系结构有四十余种,常见的有X86系列、Arm、PowerPC等。同时,GCC还能运行在不同的操作系统上,如Linux、Solaris、Windows等。除了上面讲的之外,GCC除了支持C语言外,还支持多种其他语言,例如C++、Ada、Java、Objective-C、FORTRAN、Pascal等。
一、常用指令
- 查看帮助,指令:gcc –help
- 查看gcc使用版本:gcc –version
- 编译单个c源程序文件(假设c源程序文件名是:demo.c)成可执行文件:gcc –o demo demo.c
- 生成预处理阶段完成的中间文档。预处理就是对宏定义指令如include、define等进行处理,使用-E开关选项,指令样列:gcc –o demo.i –E demo.c(这儿的demo.i就是预处理完成是形成的中间文件)
- 生成汇编代码:gcc –o demo.s –S demo.c (-S开选项,后面的文件可以是c源程序、通过预处理的.i文件)
- 生成Intel指令格式的汇编代码:gcc –o demo.s –S demo.c –masm=intel。Gcc产生的汇编默认格式为at&t
- 生成机器码:gcc –o demo.o –c demo.s(-c选项,后面文件可以是c源程序、通过预处理的.i文件,生成的汇编程序文件.s)
- 编译时指定宏名 gcc –o demo demo.c –D 宏名
- 编译时用c99标准 .如:gcc –o demo demo.c –std=c99
- 编译多个c程序,这儿的c程序:gcc –o demo demo1.c demo.c
- 编译时指定,源文程字符集。建议源文件以utf-8的格式保存。这些在处理中文宽字符串时或其他语言的宽字符串时相对容易点。如把源程序当作GB18030格式。增加编译开头选项如下:-finput-charset=GB18030
1)假设demo1.c源码为:
#include <stdio.h>
void printName(char *name);
void printName(char *name){
printf("您的姓名是:%s\n",name);
}
2)假设demo.c源码为
#include<stdio.h>
#include"demo1.h"
void printArray();
int main(){
printf("hello world\n");
printf("你好\n");
printArray();
char * name;
printf("请录入您的姓名:");
scanf("%s",name);
printName(name);
return 0;
}
void printArray(){
char strArray[]={"中国人民站起来了\n"};
printf("%s",strArray);
}
3)demo1.h源码为
void printName(char *name);
4)注意事项:源码文件只有一个文件含main函数,不能有多个main函数在源码文件中,各个源文件的引用是引用其对应的.h文件。当然也可以直接include"源码文件名.c",如果是这样做,该文件没有必要出现编译列表文件中去。如上面的文件demo.c假设其include"demo1.h"改成include"demo1.c",则gcc编译指令为:gcc –o demo demo.c,写成gcc-o demo demo.c demo1.c则会报错。
二、gcc编译阶段,及产生的文件.gcc分为四个阶段,可以用指令生成任何阶段的文件。
阶段 |
任务 |
指令样例 |
预处理阶段 |
在预处理阶段,输入的是C语言的源文件,通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、 #include和#define命令。该阶段会生成一个中间文件*.i |
gcc –o demo.i –E demo.c |
编译 |
在编译阶段,输入的是中间文件*.i,编译后生成汇编语言文件*.s |
gcc –o demo.s –S demo.i |
汇编 |
在汇编阶段,将输入的汇编文件*.s转换成机器语言*.o |
gcc –o demo.o –c demo.s |
连接 |
在连接阶段将输入的机器代码文件*.o(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件 |
gcc –o demo demo.o |
三、gcc进阶功能
1、启用编译器警告,显示警告信息,一般是在gcc指令中加选项 –Wall.如test.c源代码如下:
#include<stdio.h>
main(){
int a;
printf("hello world");
}
这个代码写得有点不符合c标准,如main 函数没有指定int返回类型,变量a没有被使用。可以通过指令控制其是否输出警告信息:
指令:gcc –o test test.c –Wall
输出警告信息如下:
test.c:2:1: warning: return type defaults to 'int'
test.c: In function 'main':
test.c:3:6: warning: unused variable 'a'
test.c:5:1: warning: control reaches end of non-void function
2、把编译器警告当作错误,开关选项是 –Wall–Werror(注意这两个开关要一起用,不用-Wall会看不到提示信息)
样例:
gcc –o test test.c –Wall –Werror
输出如下:
cc1.exe: warnings being treated as errors
test.c:2:1: error: return type defaults to 'int'
test.c: In function 'main':
test.c:3:6: error: unused variable 'a'
test.c:5:1: error: control reaches end of non-void function
3、检查代码是否符合标准C规范,开关选项是-pedantic.
如test.c源代码为:
#include<stdio.h>
main(){
long long int a;
printf("hello world");
}
使用编译指令如下:
gcc –o test test.c –pedantic
输出结果如下:
test.c: In function 'main':
test.c:3:7: warning: ISO C90 does not support 'long long'
4、编译源代码不在同一个目录下的多个源码文件。实际工程中,为了便于组织代码,不会把所有的源代码都放在同一个目录。
如一大点的7小点,我们可以把demo1.c与demo1.h文件放到.\demo1目录,那么该如何组织代码编译呢?
1)修改demo.c文件,把include"demo1.h"改成include"demo1/demo1.h"
2)编译创建指令改成如下 gcc –o demo demo.c .\demo\demo1.c
5、strict-alising 规则
该规则主要是针对不同类型的指针转换,c的指针是强大,如果一个指向int的对象通过类型强制转换指向double,如代码:int*ptr=&abc; double * dptr=(double *)ptr。那么这种转换可能会失败,因为int对象一般在内存中是按4个字节对齐而double对象一般是按8字节对齐。因为存在失败gcc编译器会提出相关警告,在优化选项用-O2及以上。关闭这个选项用-fno-strict-aliasing则可了。
strict-aliasing原解:
Allows the compiler to assume the strictest aliasing rulesapplicable to the language being compiled. For C (and C++), thisactivates optimizations based on the type of expressions. In particular,an object of one type is assumed never to reside at the same address as anobject of a different type, unless the types are almost the same. Forexample, an "unsigned int" can alias an "int", but not a"void*" or a "double". A character type may alias anyother type.
意思是:这个规则不允许不同类型的变量指向同一个地址,除非这变量的类型几乎相等。如unsinged int *可以别名int *。但是unsigned不能别名void *,不能别名 double *。但是char * 可以别名任何类型,因为他是1字节对齐呀。什么类型转换给这个char *几乎要改变提指针地址。
如下这段代码:
1#include<stdio.h>
2
3 voidtest(unsigned int * unt);
4 voidtest1(double *dbl);
5 intmain(void){
6 inta;
7 a=33;
8test((unsigned int*)&a);
9test1((double *)&a);
10printf("ptr's value is %d\n",a);
11return 0;
12 }
13 voidtest(unsigned int * unt){
14printf("test unsigned value %u\n",*unt);
15
16 }
17 voidtest1(double *dbl){
18printf("test double value %f\n",*dbl);
19 }
进行编译时有以下提示:
tsq#gcc stricalias2.c -o stricalias2 -Wall -O2
stricalias2.c:In function 'main':
stricalias2.c:9:warning: dereferencing type-punned pointer will break strict-aliasing rules
意思是不同类型指向同一个地址将打破严格的strict-aliasing这个规则。主要是第9行 int *转double *这个转换,但是int * 转unsigned int *则不进行命名限制