和菜鸟一起学c之gcc编译过程及其常用编译选项

       上篇文章,知道了,C代码编译后存放在内存中的位置,那么C代码的整个编译过程又是怎样的呢?一条命令gcc hello.c就可以编译成可执行程序a.out,然后./a.out之后就可以执行hello.c这个程序的代码了。下面的文章分析的不错,就整理了下。

hello.c:

#include<stdio.h>
int main()
{
        printf(“Hello World\n”);
        return 0;
}


实际上gcc hello.c可以分解为4个步骤,分别是预处理(Preprocess),编译(Compilation),汇编(Assembly)和链接(Linking)。

 

一、预处理

预处理过程主要读取c源程序,对伪指令和特殊符号进行处理。包括宏,条件编译,包含的头文件,以及一些特殊符号。基本上是一个replace的过程。

gcc –E hello.c –o hello.i


以下为预处理后的输出文件hello.i的内容

# 1"hello.c"
# 1"<built-in>"
# 1"<command-line>"
# 1"hello.c"
# 1 "/usr/include/stdio.h"1 3 4
# 28"/usr/include/stdio.h" 3 4
/***** 省略了部分内容,包括stdio.h中的一些声明及定义  *****/
# 2"hello.c" 2
int main()
{
 printf("Hello World\n");
 return 0;
}

预处理过程主要处理规则如下:

1、将所有的#define删除,并且展开所有的宏定义;

2、处理所有条件编译指令,如#if,#ifdef等;

3、处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。

4、删除所有的注释//和 /**/;

5、添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;

6、保留所有的#pragma编译器指令,因为编译器须要使用它们;

 

二、编译

编译过程通过词法和语法分析,确认所有指令符合语法规则(否则报编译错),之后翻译成对应的中间码,在linux中被称为RTL(Register Transfer Language),通常是平台无关的,这个过程也被称为编译前端。编译后端对RTL树进行裁减,优化,得到在目标机上可执行的汇编代码。gcc采用as作为其汇编器,所以汇编码是AT&T格式的,而不是Intel格式,所以在用gcc编译嵌入式汇编时,也要采用AT&T格式。

gcc –S hello.i –o hello.s


以下为编译后的输出文件hello.s的内容


    .file  "hello.c"
        .section    .rodata
.LC0:
        .string      "HelloWorld"
        .text
.globl main
        .type         main, @function
main:
        pushl         %ebp
        movl          %esp, %ebp
        andl $-16, %esp
        subl  $16, %esp
        movl          $.LC0, (%esp)
        call   puts
        movl          $0, %eax
        leave
        ret
        .size main, .-main
        .ident        "GCC: (GNU)4.4.0 20090506 (Red Hat 4.4.0-4)"
        .section   .note.GNU-stack,"",@progbits

 

三、汇编

汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。

       

 gcc –c hello.c –o hello.o

由于hello.o的内容为机器码,不能以文本形式方便的呈现。

 

四、链接

链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。

ld –static crt1.o crti.o crtbeginT.ohello.o –start-group –lgcc –lgcc_eh –lc-end-group crtend.o crtn.o

(省略了文件的路径名)。

 

当然链接的时候还会用到静态链接库,和动态连接库。静态库和动态库都是.o目标文件的集合。

静态库是在链接过程中将相关代码提取出来加入可执行文件的库(即在链接的时候将函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中),ar只是将一些别的文件集合到一个文件中。可以打包,当然也可以解包。

ar -v -q  test.a test.o

上面指令可以生成静态链接库test.a

 

动态库在链接时只创建一些符号表,而在运行的时候才将有关库的代码装入内存,映射到运行时相应进程的虚地址空间。如果出错,如找不到对应的.so文件,会在执行的时候报动态连接错(可用LD_LIBRARY_PATH指定路径)。用file test.so可以看到test.so是shared object的ELF文件。

gcc -sharedtest.so test.o

上面指令可以生成动态连接库test.so

 

好了,整个编译过程就如上所示了,那么对于gcc还有一些编译的选项的。具体如下:

 

GCC编译选项

1. -c    

           编译产生对象文件(*.obj)而不链接成可执行文件,当编译几个独立的模块,而待以后由链接程序把它们链接在一起时,就可以使用这个选项,如:

           

         gcc -c hello.c ===> hello.o
         gcc hello.o

 

2. -o    

         允许用户指定输出文件名,如

           gcc hello.c -o hello.o
           or
           gcc hello.c -o hello
 

3. -g   

         指明编译程序在编译的输出中应产生调试信息.这个调试信息使源代码和变量名引用在调试程序中或者当程序异常退出后在分析core文件时可被使用.

 

4. -D  

       允许从编译程序命令行定义宏符号

    一共有两种情况:一种是用-DMACRO,相当于在程序中使用#define MACRO,另一种是用-DMACRO=A,相当于程序中的#define MACRO A.如对下面这代码:

           #ifdef DEBUG
                printf("debugmessage\n");
           #endif

     编译时可加上-DDEBUG参数,执行程序则打印出编译信息

 

5. -I  

         可指定查找include文件的其他位置.例如,如果有些include文件位于比较特殊的地方,比如/usr/local/include,就可以增加此选项如下:

           gcc -c -I/usr/local/include -I/opt/include hello.c

        此时目录搜索会按给出的次序进行.

 

6. -E  

         这个选项是相对标准的,它允许修改命令行以使编译程序把预先处理的C文件发到标准输出,而不实际编译代码.在查看C预处理伪指令和C宏时,这是很有用的.可能的编译输出可重新定向到一个文件,然后用编辑程序来分析:

           gcc -c -E hello.c >cpp.out   
   

     此命令使include文件和程序被预先处理并重定向到文件cpp.out.以后可以用编辑程或者分页命令分析这个文件,并确定最终的C语言代码看起来如何.

 

7. -O  

       优化选项,这个选项不是标准的

           -O和 -O1指定1级优化

           -O2 指定2级优化

           -O3 指定3级优化

           -O0指定不优化

           gcc -c O3 -O0 hello.c 

       当出现多个优化时,以最后一个为准!!

 

8. -Wall 

       以最高级别使用GNU编译程序,专门用于显示警告用!!

           gcc -Wall hello.c

 

9. -L

       指定连接库的搜索目录,-l(小写L)指定连接库的名字

           gcc main.o -L/usr/lib -lqt -o hello

10.-share   

       此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库

 

11.-static  

        此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库

 

12.-fPIC

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

 

 

 

 

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
GCC 是 C/C++ 编译器,不支持直接编译 Java 代码。Java 代码需要先被编译成字节码,然后由 Java 虚拟机(JVM)解释执行或者编译成本地代码执行。 Java 编译器通常使用 javac 命令进行编译。下面是一个简单的编译示例: ``` javac HelloWorld.java ``` 这将会编译名为 HelloWorld.java 的 Java 源代码文件,并生成一个名为 HelloWorld.class 的字节码文件。 如果你想使用 GCC 编译 Java 代码,你需要先将 Java 代码编译成字节码,然后再使用 GCC 编译字节码文件所生成的头文件和源文件。 下面是一个基本的步骤: 1. 使用 javac 命令编译 Java 代码文件: ``` javac HelloWorld.java ``` 2. 使用 javah 命令生成头文件: ``` javah HelloWorld ``` 这将生成一个名为 HelloWorld.h 的头文件。 3. 将头文件和字节码文件一起使用 JNI 编写 C 代码。在 C 代码中,你需要包含 Java 的头文件和 JNI 库,这样就可以调用 Java 方法了。 4. 使用 GCC 编译 C 代码: ``` gcc -c -I/usr/lib/jvm/java-8-openjdk-amd64/include -I/usr/lib/jvm/java-8-openjdk-amd64/include/linux HelloWorld.c ``` 其中,-I 选项指定了 Java 和 JNI 的头文件所在的目录。 5. 使用 GCC 链接 C 代码和 Java 库: ``` gcc -shared -o libHelloWorld.so HelloWorld.o -lc -ljvm ``` 这将生成一个名为 libHelloWorld.so 的共享库,可以在 Java 中使用 System.loadLibrary() 方法加载。 需要注意的是,由于 GCC 不是专为编译 Java 代码而设计的,因此使用 GCC 编译 Java 代码可能会比较麻烦,并且可能会出现一些问题。建议还是使用专门的 Java 编译器 javac 来编译 Java 代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值