C语言再学习 -- GCC编译过程

参看:GCC编译过程分解

一、GCC简介:

gcc的原名叫做GNU C语言 编译器(GNU C Compile),只能编译C语言程序,后来很快就做了扩展,支持了更多的编程语言,比如C+ Object-c ...,改名为GNC 编译器 套件(GNU Compile Collection) 支持很多的硬件和操作系统。

二、编译过程

C语言的编译过程可分为四个阶段:预处理->>编译->>汇编->>链接

下面以hello.c为示例详细介绍各个编译过程:

//示例hello.c
#include <stdio.h>
int main (void)
{
	printf ("hello world!\n");
	return 0;
}

1、预处理

预编译过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:
1)将所有的#define删除,并且展开所有的宏定义;
2)处理所有条件编译指令,如#if,#ifdef等;
3)处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。
4)删除所有的注释//和 /**/;
5)添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
6)保留所有的#pragma编译器指令,因为编译器须要使用它们;

gcc -E hello.c -o hello.i   得到一个.i为后缀的预处理之后的文件,该文件叫做预处理文件,以下为预处理后的输出文件hello.i的内容:

# 1 "hello.c"
# 1 "<built-in>"
# 1 "<命令行>"
# 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 (void)
{
 printf ("hello world!\n");
 return 0;
}

2、编译

编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。

gcc -S hello.i -o hello.s   得到一个.s为后缀的汇编文件,以下为编译后的输出文件hello.s的内容:

	.file	"hello.c"
	.section	.rodata
.LC0:
	.string	"hello world!"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushl	%ebp
	.cfi_def_cfa_offset 8
	.cfi_offset 5, -8
	movl	%esp, %ebp
	.cfi_def_cfa_register 5
	andl	$-16, %esp
	subl	$16, %esp
	movl	$.LC0, (%esp)
	call	puts
	movl	$0, %eax
	leave
	.cfi_restore 5
	.cfi_def_cfa 4, 4
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
	.section	.note.GNU-stack,"",@progbits

3、汇编

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

gcc –c hello.s –o hello.o,得到一个.o为后缀的目标文件,由于hello.o的内容为机器码,不能以文本形式方便的呈现。可用命令hexdump hello.o 打开。

4、链接

目标代码不能直接执行,要想将目标代码变成可执行程序,还需要进行链接操作。才会生成真正可以执行的可执行程序。链接操作最重要的步骤就是将函数库中相应的代码组合到目标文件中。

gcc hello.o -o hello实现链接的处理,默认生成可执行文件 a.out,可以通过 -o来指定输出文件名。


使用ld指令
ld -static crt1.o crti.o crtbeginT.o hello.o -start -group -lgcc -lgcc_eh -lc -end-group crtend.o crtn.o 

(目标文件,未指定具体目录)
连接的过程包括按序叠加、相似段合并、符号地址的确定、符号解析与重定位、指令修正、全局构造与解析等等

tarena@ubuntu:~/project/c_test$ gcc -v
使用内建 specs。
COLLECT_GCC=gcc.real
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.6/lto-wrapper
目标:i686-linux-gnu
配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
线程模型:posix
gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 

tarena@ubuntu:/$ find /usr -name crt*
/usr/lib/i386-linux-gnu/crti.o
/usr/lib/i386-linux-gnu/crt1.o
/usr/lib/i386-linux-gnu/crtn.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtbeginT.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtfastmath.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtend.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtprec80.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtprec32.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtbeginS.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtbegin.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtendS.o
/usr/lib/gcc/i686-linux-gnu/4.6/crtprec64.o

tarena@ubuntu:~/project/c_test$ ld -static -verbose /usr/lib/i386-linux-gnu/crt1.o /usr/lib/i386-linux-gnu/crti.o /usr/lib/gcc/i686-linux-gnu/4.6/crtbeginT.o -L/usr/lib/gcc/i686-linux-gnu/4.6 -L/usr/lib/i386-linux-gnu hello.o -start -group -lgcc -lgcc_eh -lc -end-group /usr/lib/gcc/i686-linux-gnu/4.6/crtend.o /usr/lib/i386-linux-gnu/crtn.o 
GNU ld (GNU Binutils for Ubuntu) 2.22
  Supported emulations:
   elf_i386
   i386linux
   elf32_x86_64
   elf_x86_64
   elf_l1om
   elf_k1om
ld: bad -rpath option 


gcc版本不对,支持的是 i386linux,如果想深入研究,安装gcc 4.1.2

参看:ld script初探

可以直接通过 gcc hello.c -o hello来生成可执行文件,这只是把中步操作隐藏起来了


注意:gcc -c hello.o -o hello 是错误的

tarena@ubuntu:~/project/c_test$ gcc -c hello.o -o hello
gcc.real: 警告: hello.o:未使用链接器输入文件,因为链接尚未完成


注意:目标文件和可执行文件的不同

# gcc -c hello.c 
生成 hello.o  /*目标文件*/
# file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

# readelf -h hello.o 
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          288 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         13
  Section header string table index: 10
# gcc hello.o -o hello
生成 hello  /*可执行文件*/
# file hello
hello: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x25c438a7052180bb74c1b9d78b498e6777586c92, not stripped

# readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8048320
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4412 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         30
================================================

# arm-linux-gcc -c hello.c  
生成 hello.o  /*二进制目标文件*/
# file hello.o
hello.o: ELF 32-bit LSB relocatable, ARM, version 1 (SYSV), not stripped

# readelf -h hello.o
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          288 (bytes into file)
  Flags:                             0x5000000, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 9
# arm-linux-gcc hello.o -o hello  
生成 hello  /*二进制可执行文件*/
# file hello
hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped

# readelf -h hello
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x8304
  Start of program headers:          52 (bytes into file)
  Start of section headers:          4464 (bytes into file)
  Flags:                             0x5000002, has entry point, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         10
  Size of section headers:           40 (bytes)
  Number of section headers:         29
  Section header string table index: 26

readelf -h 选项,读取ELF文件的文件头信息,注意其中的三项值:Type、Machine、Entry point address

Type  信息就是file中的文件类型
Machine  是在ARM执行还是在inter执行的
Entry point address  表示文件的执行入口点,只有可执行文件该项才有值,而目标文件是可重定向文件,还不可以直接执行,因此该项值为0.


如果在ARM上执行目标文件而不是可执行文件会出现如下错误:
line 1: syntax error: unexpected word (expecting ")")

总之一句话,你要明白你编译出来的是什么


三、文件名后缀

tarena@ubuntu:~/project/c_test$ ls
hello  hello.c  hello.i  hello.o  hello.s

文件名后缀 

文件类型

.c

C源文件

.C .cpp .cc .c++ .cxx               

C++源文件

.h

头文件

.i

预处理后的C源文件                           

.s

汇编程序文件

.o

目标文件

.a

静态链接库

.so

动态链接库


四、gcc 常用命令

参看:15个常用的gcc 命令选项

下载:gcc,g++-GNU工程的C和C++编译器中文手册

GCC编译器非常强大,在各个发行的 linux  系统中都非常流行,本文介绍的是一些常用的 gcc 编译选型。

下面这段代码将回绕整个文章:

编译 hello.c 如下:

#include <stdio.h>

int main (void)
{
	printf ("hello world!\n");
	return 0;
}


GCC 编译选项:

1、指定输出可执行文件的名字

使用最基本的gcc编译格式

gcc hello.c
生成 a.out 
执行完上面这句命令,会在当前目录下输出一个名为 a.out 的可执行文件。

使用 -o 选项可以指定输出的可执行文件名称。

gcc hello.c -o hello
生成 hello

执行完上面这句命令,会在当前目录下输出一个名为 hello 的可执行文件。

2、让所有编译警告都显示出来,选项 -Wall

如下,编辑一段警告的代码

#include <stdio.h>

int main (void)
{
	int i;
	printf ("\n hello world![i]\n", i);
	return 0;
}

root@ubuntu:/home/tarena/project/c_test# gcc -Wall hello.c -o hello
hello.c: 在函数‘main’中:
hello.c:6:2: 警告: 提供给格式字符串的实参太多 [-Wformat-extra-args]
hello.c:6:9: 警告: 此函数中的‘i’在使用前未初始化 [-Wuninitialized]

3、指定 -E 编译选项,使得只输出预编译结果

gcc -E hello.c -o hello.i
生成 helo.i

4、通过编译选项 -S 输出汇编代码

gcc -S hello.c
生成 hello.s

5、指定 -c 输出编译后的代码

gcc -c hello.c
生成 hello.o

6、通过编译选项 -save-temps 输出所有的中间代码

root@ubuntu:/home/tarena/project/c_test# gcc -save-temps hello.c 
root@ubuntu:/home/tarena/project/c_test# ls
a.out  hello.c  hello.i  hello.o  hello.s

7、链接共享库 (动态链接库)指定编译选项 -l

gcc hello.c -o hello -lCPPfile
gcc 命令在执行链接 hello.c 代码时,会链接上 -lCPPfile 动态链接库来生成 hello 可执行文件。

8、指定编译选项 -fPIC 创建独立的(无关联的)地址信息代码

当创建动态链接库时,独立位置信息(position independent)代码也需要生成。这可以帮助动态链接库或者跟多的加载地址信息来替代其他相对的地址信息。所以-fPIC这个选项作用很大,能快速准确定位错误地址。
下面是一个例子,

$ gcc -c -Wall -Werror -fPIC Cfile.c  
$ gcc -shared -o libCfile.so Cfile.o  

9、查看gcc版本信息选项 -v

root@ubuntu:/home/tarena/project/c_test# gcc -v
使用内建 specs。
COLLECT_GCC=gcc.real
COLLECT_LTO_WRAPPER=/usr/lib/gcc/i686-linux-gnu/4.6/lto-wrapper
目标:i686-linux-gnu
配置为:../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.6.3-1ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.6/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.6 --enable-shared --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.6 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=i686-linux-gnu --host=i686-linux-gnu --target=i686-linux-gnu
线程模型:posix
gcc 版本 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5) 

10、指定编译选项 -ansi,支持 ISO C89 程序

通过-ansi 选项开启支持 ISO C89 风格,看如下代码:

root@ubuntu:/home/tarena/project/c_test# gcc -ansi hello.c 
hello.c: 在函数‘main’中:
hello.c:5:2: 错误: expected expression before ‘/’ token

11、指定编译选项 -funsigned-char 选项将 char 类型解释为 unsigned char 类型

#include <stdio.h>

int main (void)
{
	char c = -10;
	printf ("c is %d\n", c);
	return 0;
}

root@ubuntu:/home/tarena/project/c_test# gcc hello.c 
root@ubuntu:/home/tarena/project/c_test# ./a.out 
c is -10
root@ubuntu:/home/tarena/project/c_test# gcc -funsigned-char hello.c 
root@ubuntu:/home/tarena/project/c_test# ./a.out 
c is 246
root@ubuntu:/home/tarena/project/c_test# 

12、指定 -D 选型开启编译时的宏

#include <stdio.h>  
int main()  
{  
    int num=0;  
    int arr[SIZE]={};   //使用gcc -D可以宏定义这个数字  
    for(num = 0;num <= SIZE - 1;num++)  
    {  
        arr[num]=num;  
        printf("%d ",arr[num]);  
    }  
    printf("\n");  
    return 0;  
}  
gcc -DSIZE=4 define.c  
输出结果:  
0 1 2 3  

13、将编译警告转换成错误的选项 -Werror

编译警告很多时候会被我们忽视,在特殊场合我们还是需要重视编译警告的,如果能把编译警告变成直接输出错误,那我们的重视程度会提高很多并去解决。

#include <stdio.h>

int main (void)
{
	int i;
	printf ("\n hello world![i]\n", i);
	return 0;
}

root@ubuntu:/home/tarena/project/c_test# gcc -Wall -Werror hello.c
hello.c: 在函数‘main’中:
hello.c:6:2: 错误: 提供给格式字符串的实参太多 [-Werror=format-extra-args]
cc1: all warnings being treated as errors
上述代码未初始化变量 c ,警告变成了错误提示。

14、通过文件制定编译选项,指定@编译选项

比较神奇的功能,可以使用@编译选项然后跟着文件名,例如:

root@ubuntu:/home/tarena/project/c_test# cat opt_file 
-o hello
root@ubuntu:/home/tarena/project/c_test# gcc hello.c @opt_file
生成 hello

15、指定采用什么版本的规范进行编译,选项 -std

加上 -std=c89/-std=c99

root@ubuntu:/home/tarena/project/c_test# gcc -std=c99 hello.c
生成 a.out

16、优化程序选项 -O

优化是编译器的一部分,它可以检查和组合编译器生成的代码,指出未达到最优的部分,并重新生成它们,从而使用户编写的程序更加完美且节省空间。在gcc编译器选项中,使用-O选项对代码进行优化。优化级别分3级,由高到低分别为:-O3、-O2、-O1
优化程序选项说明:
-O1(-O): 对编译出的代码进行优化
-O2: 进行比-O高一级的优化
-O3: 产生更高级别的优化

说明:
-O1(或-O)、-O2、-O3分别代表优化级别,数字越高,代表gcc的优化级别越高,高的优化级别代表着程序将运行的更快。优化级别越高则程序量越大。直接优化程序本身,性能的提高的变化更加明显。

缺点,编译、链接的速度就相应地要慢一些。

17、连接程序选项

库:是一组预先编译好的函数集合。
说明:
标准库文件一般存储在/lib和/usr/lib目录中。所有的库名都以lib开头。例如:libc.so(标准C语言函数库)、libm.so(数学运算函数库)以.a结尾的是静态库;以.so结尾的库是动态库。使用ar工具将目标文件收集起来,放到一个归档文件中。
连接程序选项说明:
-L dir:将dir所指出的目录加到“函数库搜索列表”中,dir 为库文件所在的路径
-llib: 链接lib库,lib 为库名
-I name: 连接时,加载名字为name的函数库。该库位于系统预设的目录或者由-L选项确定的目录下。实际的库名是libname(后缀为.a或.so)


链接过程通常的形式如下:

gcc file.o -o file -lxxx -L dirname 

-L:指定了链接时用到的库文件所在的目录。
-lxxx:指示链接的库函数名为libxxx.a

例子:编译产生可执行文件hello,搜索数学库以解决问题。

# gcc hello.o -o hello  /usr/lib/libm.a
或者
# gcc -o hello hello.c -lm 

比如,使用pow幂函数,当指数为变量时,编译出现undefined reference to `pow‘的错误

因为math.h不是C运行库函数,就像linux下线程函数库pthread.h也不是,都需要在编译时连接该库。如果你有

IDE(如eclipse)+ GCC 开发C程序,可以在项目属性中编译命令中添加-lm,作用是一样的。

#include <stdio.h>  
#include <math.h>  
  
int main (void)  
{  
    int n = 2, m = 2;  
    int i = pow (2, m);  
    printf ("i = %d\n", i);  
    return 0;  
}  
编译:gcc test.c -lm  
输出结果:  
i = 4  

18、指定头文件的路径dir,选项 -I dir
先在指定的路径中搜索要包含的头文件,若找不到,则在标准路径(/usr/include,/usr/lib及当前工作目录)上搜索

#include "add.h"
int main (void)
{
	printf ("hello world!\n");
	return 0;
}

root@ubuntu:/home/tarena/project/c_test# gcc hello.c 
hello.c:1:17: 致命错误: add.h:没有那个文件或目录
编译中断。
root@ubuntu:/home/tarena/project/c_test# gcc hello.c -I ../    /*上一个目录查找*/
root@ubuntu:/home/tarena/project/c_test# ./a.out 
hello world!
root@ubuntu:/home/tarena/project/c_test# 

19、调试选型 -g

可产生供gdb调试用的可执行文件,大小明显比只用-o选项编译汇编连接后的文件大。

root@ubuntu:/home/tarena/project/c_test# gcc hello.c 
root@ubuntu:/home/tarena/project/c_test# ls -la a.out 
-rwxr-xr-x 1 root root 7159 Nov 26 23:32 a.out
root@ubuntu:/home/tarena/project/c_test# gcc -g hello.c 
root@ubuntu:/home/tarena/project/c_test# ls -la a.out 
-rwxr-xr-x 1 root root 8051 Nov 26 23:32 a.out

gdb的简单使用:

(gdb)l  列表(list)
(gdb)r  执行(run)
(gdb)n  下一个(next)
(gdb)q  退出(quit)
(gdb)p  输出(print)
(gdb)c  继续(continue)
(gdb)b 4 设置断点(break)
(gdb)d   删除断点(delete)

root@ubuntu:/home/tarena/project/c_test# gdb a.out
GNU gdb (Ubuntu/Linaro 7.4-2012.02-0ubuntu2) 7.4-2012.02
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /home/tarena/project/c_test/a.out...done.
(gdb) l
1	#include <stdio.h>
2	int main (void)
3	{
4		printf ("hello world!\n");
5		return 0;
6	}
(gdb) r
Starting program: /home/tarena/project/c_test/a.out 
hello world!
[Inferior 1 (process 6906) exited normally]
(gdb) q
root@ubuntu:/home/tarena/project/c_test# 

扩展:可使用 man gcc/cc 查询 gcc/cc的更多相关信息和选项

五、后续应用

参看:C语言再学习 -- 关键字volatile

参看:UNIX再学习 -- 静态库与共享库

参看:S5PV210开发 -- 交叉编译器

这里面的问题,line 1: syntax error: unexpected word (expecting ")")   值得看一下。



相关推荐
©️2020 CSDN 皮肤主题: 猿与汪的秘密 设计师:白松林 返回首页
实付 19.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值