LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 2121|回复: 10

学习GCC的时候,在网上搜集的部分入门的资料.

[复制链接]
发表于 2006-12-1 08:53:45 | 显示全部楼层 |阅读模式
学习GCC的时候,在网上搜集的部分入门的资料.希望对您有所帮助!

一、GCC简介

通常所说的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的基本功能,还涉及到一些诸如优化之类的高级功能。另外,我们还考察GCC的一些映像操作工具,如size和objcopy等,这将在后续的文章中加以介绍。


二、程序的编译过程


对于GUN编译器来说,程序的编译要经历预处理、编译、汇编、连接四个阶段,如下图所示:


     


从功能上分,预处理、编译、汇编是三个不同的阶段,但GCC的实际操作上,它可以把这三个步骤合并为一个步骤来执行。下面我们以C语言为例来谈一下不同阶段的输入和输出情况。


在预处理阶段,输入的是C语言的源文件,通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、 #include和#define命令。该阶段会生成一个中间文件*.i,但实际工作中通常不用专门生成这种文件,因为基本上用不到;若非要生成这种文件不可,可以利用下面的示例命令:




gcc -E  test.c -o test.i




在编译阶段,输入的是中间文件*.i,编译后生成汇编语言文件*.s 。这个阶段对应的GCC命令如下所示:




GCC -S test.i -o test.s




在汇编阶段,将输入的汇编文件*.s转换成机器语言*.o。这个阶段对应的GCC命令如下所示:




GCC -c test.s -o test.o




最后,在连接阶段将输入的机器代码文件*.s(与其它的机器代码文件和库文件)汇集成一个可执行的二进制代码文件。这一步骤,可以利用下面的示例命令完成:




GCC test.o -o test




上面介绍了GCC编译过程的四个阶段以及相应的命令。下面我们进一步介绍常用的GCC的模式。


三、GCC常用模式


这里介绍GCC追常用的两种模式:编译模式和编译连接模式。下面以一个例子来说明各种模式的使用方法。为简单起见,假设我们全部的源代码都在一个文件test.c中,要想把这个源文件直接编译成可执行程序,可以使用以下命令:




$ GCC -o test




这里test.c是源文件,生成的可执行代码存放在一个名为test 的文件中(该文件是机器代码并且可执行)。-o 是生成可执行文件的输出选项。如果我们只想让源文件生成目标文件(给文件虽然也是机器代码但不可执行),可以使用标记-c ,详细命令如下所示:




$ GCC -c test.c




默认情况下,生成的目标文件被命名为test.o,但我们也可以为输出文件指定名称,如下所示:




$ GCC -c test.c -o




上面这条命令将编译后的目标文件命名为mytest.o,而不是默认的test.o。


迄今为止,我们谈论的程序仅涉及到一个源文件;现实中,一个程序的源代码通常包含在多个源文件之中,这该怎么办?没关系,即使这样,用GCC处理起来也并不复杂,见下例:




$ GCC -o test  first.c second.c third.c




需要注意的是,要生成可执行程序时,一个程序无论有有一个源文件还是多个源文件,所有被编译和连接的源文件中必须有且仅有一个main函数,因为main函数是该程序的入口点(换句话说,当系统调用该程序时,首先将控制权授予程序的main函数)。但如果仅仅是把源文件编译成目标文件的时候,因为不会进行连接,所以main函数不是必需的。


四、常用选项


许多情况下,头文件和源文件会单独存放在不同的目录中。例如,假设存放源文件的子目录名为./src,而包含文件则放在层次的其他目录下,如./inc。当我们在./src 目录下进行编译工作时,如何告诉GCC到哪里找头文件呢?方法如下所示:




$ gcc test.c �I../inc -o test




上面的命令告诉GCC包含文件存放在./inc 目录下,在当前目录的上一级。如果在编译时需要的包含文件存放在多个目录下,可以使用多个-I 来指定各个目录:




$ gcc test.c �I../inc �I../../inc2 -o test




这里指出了另一个包含子目录inc2,较之前目录它还要在再上两级才能找到。


另外,我们还可以在编译命令行中定义符号常量。为此,我们可以简单的在命令行中使用-D选项即可,如下例所示:




$ gcc -DTEST_CONFIGURATION test.c -o test




上面的命令与在源文件中加入下列命令是等效的:




#define TEST_CONFIGURATION




在编译命令行中定义符号常量的好处是,不必修改源文件就能改变由符号常量控制的行为。


五、警告功能


当GCC在编译过程中检查出错误的话,它就会中止编译;但检测到警告时却能继续编译生成可执行程序,因为警告只是针对程序结构的诊断信息,它不能说明程序一定有错误,而是存在风险,或者可能存在错误。虽然GCC提供了非常丰富的警告,但前提是你已经启用了它们,否则它不会报告这些检测到的警告。


在众多的警告选项之中,最常用的就是-Wall选项。该选项能发现程序中一系列的常见错误警告,该选项用法举例如下:




$ gcc -Wall test.c -o test




该选项相当于同时使用了下列所有的选项:


◆unused-function:遇到仅声明过但尚未定义的静态函数时发出警告。

◆unused-label:遇到声明过但不使用的标号的警告。

◆unused-parameter:从未用过的函数参数的警告。

◆unused-variable:在本地声明但从未用过的变量的警告。

◆unused-value:仅计算但从未用过的值得警告。

◆Format:检查对printf和scanf等函数的调用,确认各个参数类型和格式串中的一致。

◆implicit-int:警告没有规定类型的声明。

◆implicit-function-:在函数在未经声明就使用时给予警告。

◆char-subscripts:警告把char类型作为数组下标。这是常见错误,程序员经常忘记在某些机器上char有符号。

◆missing-braces:聚合初始化两边缺少大括号。

◆Parentheses:在某些情况下如果忽略了括号,编译器就发出警告。

◆return-type:如果函数定义了返回类型,而默认类型是int型,编译器就发出警告。同时警告那些不带返回值的 return语句,如果他们所属的函数并非void类型。

◆sequence-point:出现可疑的代码元素时,发出报警。

◆Switch:如果某条switch语句的参数属于枚举类型,但是没有对应的case语句使用枚举元素,编译器就发出警告(在switch语句中使用default分支能够防止这个警告)。超出枚举范围的case语句同样会导致这个警告。

◆strict-aliasing:对变量别名进行最严格的检查。

◆unknown-pragmas:使用了不允许的#pragma。

◆Uninitialized:在初始化之前就使用自动变量。


需要注意的是,各警告选项既然能使之生效,当然也能使之关闭。比如假设我们想要使用-Wall来启用个选项,同时又要关闭unused警告,利益通过下面的命令来达到目的:




$ gcc -Wall -Wno-unused test.c -o test




下面是使用-Wall选项的时候没有生效的一些警告项:


◆cast-align:一旦某个指针类型强制转换时,会导致目标所需的地址对齐边界扩展,编译器就发出警告。例如,某些机器上只能在2或4字节边界上访问整数,如果在这种机型上把char *强制转换成int *类型, 编译器就发出警告。

◆sign-compare:将有符号类型和无符号类型数据进行比较时发出警告。

◆missing-prototypes :如果没有预先声明函数原形就定义了全局函数,编译器就发出警告。即使函数定义自身提供了函数原形也会产生这个警告。这样做的目的是检查没有在头文件中声明的全局函数。

◆Packed:当结构体带有packed属性但实际并没有出现紧缩式给出警告。

◆Padded:如果结构体通过充填进行对齐则给出警告。

◆unreachable-code:如果发现从未执行的代码时给出警告。

◆Inline:如果某函数不能内嵌(inline),无论是声明为inline或者是指定了-finline-functions 选项,编译器都将发出警告。

◆disabled-optimization:当需要太长时间或过多资源而导致不能完成某项优化时给出警告。


上面是使用-Wall选项时没有生效,但又比较常用的一些警告选项。本文中要介绍的最后一个常用警告选项是-Werror。使用该选项后,GCC发现可疑之处时不会简单的发出警告就算完事,而是将警告作为一个错误而中断编译过程。该选项在希望得到高质量代码时非常有用。


六、小结


本文介绍了GCC的基本编译过程和编译模式,并详细阐述了GCC的一些常用选项以及警告功能。这些是在利用GCC进行应用编程时最基本也最常用的一些内容,我们会在后续文章中继续介绍GCC的调试和优化技术。
 楼主| 发表于 2006-12-1 08:56:05 | 显示全部楼层
GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 O
bject C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++
源程序。
  如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可
执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编
译生成一个计算阶乘的程序。
代码:
-----------------------
清单 factorial.c
-----------------------
int factorial (int n)
{
  if (n <= 1)
   return 1;
  else
   return factorial (n - 1) * n;
}
-----------------------
清单 main.c
-----------------------
#include <stdio.h>
#include <unistd.h>
int factorial (int n);
int main (int argc, char **argv)
{
  int n;
  if (argc < 2)
  {
    printf ("Usage: %s n\n", argv [0]);
    return -1;
  }
  else
  {
   n = atoi (argv[1]);
   printf ("Factorial of %d is %d.\n", n, factorial (n));
   }
  return 0;
}
-----------------------
利用如下的命令可编译生成可执行文件,并执行程序:
$ gcc -o factorial main.c factorial.c
$ ./factorial 5
Factorial of 5 is 120.
  GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀
名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源
文件的后缀名为 .C 或 .cpp。但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C
++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程
序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C):
#include <iostream>
void main (void)
{
  cout << "Hello, world!" << endl;
}
则可以如下调用 g++ 命令编译、连接并生成可执行文件:
$ g++ -o hello hello.C
$ ./hello
Hello, world!
**********************gcc/egcs 的主要选项*********
gcc 命令的常用选项
选项 解释
-ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色,
例如 asm 或 typeof 关键词。
-c 只编译并生成目标文件。
-DMACRO 以字符串“1”定义 MACRO 宏。
-DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。
-E 只运行 C 预编译器。
-g 生成调试信息。GNU 调试器可利用该信息。
-IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY 连接时搜索指定的函数库LIBRARY。
-m486 针对 486 进行代码优化。
-o FILE 生成指定的输出文件。用在生成可执行文件时。
-O0 不进行优化处理。
-O 或 -O1 优化生成代码。
-O2 进一步优化。
-O3 比 -O2 更进一步优化,包括 inline 函数。
-shared 生成共享目标文件。通常用在建立共享库时。
-static 禁止使用共享连接。
-UMACRO 取消对 MACRO 宏的定义。
-w 不生成任何警告信息。
-Wall 生成所有警告信息。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-12-1 08:57:22 | 显示全部楼层
动态库*.so在linux下用c和c++编程时经常会碰到,最近在网站找了几篇文章介绍动态库的编译和链接,总算搞懂了这个之前一直不太了解得东东,这里做个笔记,也为其它正为动态库链接库而苦恼的兄弟们提供一点帮助。
1、动态库的编译

下面通过一个例子来介绍如何生成一个动态库。这里有一个头文件:so_test.h,三个.c文件:test_a.c、test_b.c、test_c.c,我们将这几个文件编译成一个动态库:libtest.so。

so_test.h:



#include

#include



void test_a();

void test_b();

void test_c();



test_a.c:



#include "so_test.h"



void test_a()

{

printf("this is in test_a...\n");

}



test_b.c:



#include "so_test.h"



void test_b()

{

printf("this is in test_b...\n");

}



test_a.c:



#include "so_test.h"



void test_c()

{

printf("this is in test_c...\n");

}



将这几个文件编译成一个动态库:libtest.so



$ gcc test_a.c test_b.c test_c.c -fPIC -shared -o libtest.so


2、动态库的链接

在1、中,我们已经成功生成了一个自己的动态链接库libtest.so,下面我们通过一个程序来调用这个库里的函数。程序的源文件为:test.c。

test.c:



#include "so_test.h"



int main()

{

test_a();

test_b();

test_c();



return 0;

}



l 将test.c与动态库libtest.so链接生成执行文件test:



$ gcc test.c -L. -ltest -o test



l 测试是否动态连接,如果列出libtest.so,那么应该是连接正常了



$ ldd test



l 执行test,可以看到它是如何调用动态库中的函数的。
3、编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件

l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。

l -L.:表示要连接的库在当前目录中

l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称

l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。

l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
4、注意

调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-12-1 09:05:05 | 显示全部楼层
转帖]Linux编程入门-gcc、make和gdb简介

欢迎来到Linux的编程世界!
  如果你已是Windows下优秀的程序员,那么到了这里,你将会发现一个崭新的天地,虽然在Linux下编程比起在Windows下编程有种种的不便,但她的许多迷人之处一定会让你流连忘返-----只要你是一个真正"热爱"编程的程序员;如果你以前很少编程,只是刚刚迈进了这个大门,那么,你更是走对了地方,在这里,你会得到比MSDN更多的指引和帮助,你会迅速的成长为真正的程序员----如果你想成为的话。当然,这里最吸引人的,还应该是自由软件的精神。或许,在以后,你会把这里作为自己的家园。
  要成为Linux下优秀的程序员,首先你要接触的便是gcc、make和gdb。如果你对微软的Visual系列开发工具有所了解的话,那么下面的等式会让你对它们有所了解,虽然并不是那么贴切:
    gcc + make + gdb = Visual C ++
  gcc(GNU cc)是一个编译器套件,它不仅能编译C、Objective C和C++程序,还能编译Fortran、Pascal等语言编写的程序。但就编译器而言,它是目前编译最快、效率最高的编译器,当然,这是非官方的说法。
  make是一个项目管理工具。如果你早已习惯了敲F9、Ctrl+F9或者点击某个按钮来编译运行程序,那么,这个工具将会使你明白这一切如何发生的。
  gdb(GNU DeBugger)是一个调试器,这对一个程序员是必不可少的。gdb的功能非常强大,但如果把友好的图形化的界面作为衡量的主要标准之一,那么现阶段在Linux下的确缺少好的调试器。xxgdb和kde下的Debugger正在这方面努力着。
  和绝大多数教材、文章一样,我们从"Hello World!"开始。
  我们先用vi或者其他一些图形化的编辑器生成一个hello.c文件:

  #include
  void main(void)
  {
   printf("Hello World!");
  }

  我们使用gcc进行编译:
  gcc hello.c -o hello
  就在当前目录下生成可执行文件hello,运行这个文件,你自然就会看到Hello World!的字样。这里hello.c是源文件,-o是编译选项,hello是你指定的文件名。要注意的是,不要使用test、help这类"常用"的名字来命名自己的可执行文件,他们往往早已被系统捷足先登了。
  在程序中如果引用了其它的头文件,则需要用 -I路径名 选项告诉编译器在那里才能找到这些头文件。例如,在hello.c文件中有如下的两行:
  #include
  #include
  其中myheadfile1.h在目录/usr/local/include目录下,myheadfile2.h在上一层目录下,则编译命令如下:
  gcc -I/usr/local/include -I.. hello.c -o hello
程序中如果使用了不在默认库中的调用,例如,要在hello.c中调用pthread_create( )函数创建多线程,输入如下代码:
  ……
  #include
  ……
  pthread_create(t,attr,0,0);
  ……
  使用上面的命令编译将出错:undefined reference to 'pthread_create'。这是因为没有告诉编译器在哪里能找到包含了名为pthread_create的调用。pthread_create在/usr/lib目录下的库文件libpthread.a(静态库)和libpthread.so(动态库)中。用 -L路径名 指定库的位置,用 -lFILE 指定库名为libFILE的静态或动态库。包含了pthread_create的hello.c的编译命令为:
  gcc -L/usr/lib -lpthread hello.c -o hello
  当程序为一个项目,包含很多文件时,可以先用 -c 选项编译生成目标文件,然后再用-o选项连接目标文件生成可执行文件。例如,项目的源文件为server.c 和 client.c,想生成名为 myapp 的可执行文件,则编译命令为:
  gcc -c server.c
  gcc -c client.c
  gcc server.o client.o -o myapp
  以上编译命令和下面的编译命令是等价的:
  gcc client.c server.c -o myapp
  当项目包含多个文件时,每一次敲入繁琐的编译命令大概会吓跑所有慕名到Linux下来开发的程序员们。这时,就该make来大显身手了。
  make会在当前目录下寻找名为Makefile或makefile的文件,然后依次执行该文件。以前面的myapp为例:
  myapp: server.o client.o
  gcc server.o client.o -o myapp
  server.o: server.c
  gcc -c server.c
  client.o: client.c
  gcc -c client.c
  其中,为键盘Tab键,不能用几个空格代替,即使它们看起来一样。第1、3、5行定义了依赖关系,第2、4、6行定义了相应的编译命令。make扫描1、3、5行,目标不存在则调用相应的命令行编译,若已经存在,则比较文件日期,若冒号后的文件新于冒号前的,同样重新编译。这样,如果用户仅仅修改了client.c一个文件,则用make重新编译时server.o就不会被重新编译,这样大大节省了时间。
  千里之行,始于足下。这虽然只是一个简单的开始,但是,通向一个神秘殿堂的大门已经打开了,如何才能成为一个合格的程序员,一切都要靠努力,努力养成看文档、看How-to、看高手程序的好习惯,努力自己去动手实践,一切都会好起来的
回复 支持 反对

使用道具 举报

发表于 2006-12-1 09:15:47 | 显示全部楼层
当初开始学习编译连接的知识时看到楼主的文章多好
赞一个!
回复 支持 反对

使用道具 举报

发表于 2006-12-1 15:53:41 | 显示全部楼层
受教鸟!楼主真事太好鸟!
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-12-1 16:09:37 | 显示全部楼层
我也是前几天才开始学Linux的,所以这些东西都是我搜集后转载过来的, 只是本着大家一起学习的目的发的!
谢谢大家的肯定!
回复 支持 反对

使用道具 举报

发表于 2006-12-3 14:03:41 | 显示全部楼层
正开始学习,谢谢楼主
回复 支持 反对

使用道具 举报

发表于 2008-12-17 22:06:02 | 显示全部楼层
学习了,楼主真牛。。。
回复 支持 反对

使用道具 举报

发表于 2008-12-18 16:17:05 | 显示全部楼层
好东西,顶一下!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表