GCC简介
GCC(GNU Compiler Collection)是由GNU项目开发的程序语言编译器。原名为GNU C Compiler(GNU C编译器),因为最初只能处理C语言。GCC现在已经能支持多种编程语言,包括C、C++、Objective-C、Fortran、Ada、Go等。
GCC是自由软件基金会(Free Software Foundation)的关键项目,并且是GNU操作系统的核心组成部分。GCC应用非常广泛,大多数Unix系统以及其变种都采用GCC,包括Linux和BSD。另外,GCC也是跨平台编译器,对于在Windows环境开发Unix或Linux应用程序也非常重要。
GCC主要有四个部分组成:
- 预处理器(Preprocessor):处理源代码中的预处理指令,如#define,#include等
- 编译器(Compiler):将预处理后的源代码转换为汇编语言。
- 汇编器(Assembler):将汇编语言转换为目标文件(二进制代码)。
- 链接器(Linker):将一个或多个目标文件链接为一个可执行文件。
GCC不仅是一个编译器,同时也是一个编译器的集合,它还提供了许多高级功能,如内联汇编、各级别的优化、警告控制、错误排查等。
GCC(GNU Compiler Collection)作为一款非常流行的编译器,与其他C语言编译器相比,具有一些显著的优点和一些缺点。
优点:
- 支持多种语言:GCC不仅支持C语言,还支持C++、Java、Fortran、Ada、Go、Objective-C等多种编程语言。
- 跨平台:GCC是跨平台的,可以在众多操作系统中运行,包括Unix、Linux、Mac OS、Windows等。
- 生成高效的代码:GCC提供多种优化选项,可以生成高效的目标代码。
- 强大的预处理功能:GCC的预处理器支持包括宏定义、条件编译、头文件包含等功能。
- 开源且免费:GCC是GNU项目的一部分,遵循GPL协议,可以自由使用、修改和分发。
缺点:
- 编译速度:GCC的编译速度相比某些专有的编译器可能会稍慢。
- 错误提示不够友好:相比某些专有编译器,GCC的错误和警告信息可能不够明确或者友好,新手可能会觉得难以理解。
- 对C++的支持:虽然GCC也支持C++,但是在一些特性的支持上,可能不如一些专门针对C++的编译器。
最后,值得注意的是,GCC的优缺点也在持续变化中,因为GCC作为一个活跃的开源项目,一直在进行改进和更新。
gcc与g++的区别
gcc(GNU Compiler Collection)和 g++ 都是 GNU 编译器的命令行前端,它们的主要区别在于处理 C 和 C++ 代码的方式上。
- 默认编译语言:gcc 可以被用来编译 C、C++ 和其他语言的代码,但是如果没有明确指定编译语言(例如通过 -x c++),它会假设源文件是 C 语言,而不是 C++。相反,g++ 将默认把源文件当作 C++ 代码来处理。
- 链接标准库:gcc 和 g++ 在链接阶段的行为也有所不同。默认情况下,gcc 不会链接 C++ 标准库,而 g++ 会。这意味着如果你的代码中使用了 C++ 标准库,例如 <iostream>,那么你需要使用 g++ 或者告诉 gcc 链接 C++ 标准库(通过 -lstdc++ 选项)。
因此,虽然 gcc 和 g++ 都可以用来编译 C 和 C++ 代码,但通常我们使用 gcc 来编译 C 代码,使用 g++ 来编译 C++ 代码,这样可以避免不必要的问题。
请注意,gcc 和 g++ 实际上都是同一个程序。当你调用 g++ 时,它只是以 “支持 C++” 的方式运行 gcc 程序。这就是为什么 gcc 和 g++ 之间的行为差异可以通过命令行选项来调整的原因。
GCC的组成
GCC编译工具链(toolchain),是指以GCC编译器为核心的一整套工具。它主要包含以下三部分内容:
- gcc-core:即GCC编译器,用于完成预处理和编译过程,把C代码转换成汇编代码。
- Binutils :除GCC编译器外的一系列小工具包括了链接器ld,汇编器as、目标文件格式查看器readelf等。
- glibc:包含了主要的 C语言标准函数库,C语言中常常使用的打印函数printf、malloc函数就在glibc 库中。
在很多场合下会直接用GCC编译器来指代整套GCC编译工具链。
Binutils
Binutils是GNU二进制工具集,通常跟GCC编译器一起打包安装到系统,它的官方说明网站地址为: https://www.gnu.org/software/binutils/ 。
在进行程序开发的时候通常不会直接调用这些工具,而是在使用GCC编译指令的时候由GCC编译器间接调用。下面是其中一些常用的工具:
- as:汇编器,把汇编语言代码转换为机器码(目标文件)。
- ld:链接器,把编译生成的多个目标文件组织成最终的可执行程序文件。
- readelf:可用于查看目标文件或可执行程序文件的信息。
- nm : 可用于查看目标文件中出现的符号。
- objcopy: 可用于目标文件格式转换,如.bin 转换成 .elf 、.elf 转换成 .bin等。
- objdump:可用于查看目标文件的信息,最主要的作用是反汇编。
- size:可用于查看目标文件不同部分的尺寸和总尺寸,例如代码段大小、数据段大小、使用的静态内存、总大小等。
系统默认的Binutils工具集位于/usr/bin目录下,可使用如下命令查看系统中存在的Binutils工具集:
glibc库
- glibc库是GNU组织为GNU系统以及Linux系统编写的C语言标准函数库,在Linux系统下的极大多数C语言函数都依赖此函数库运行。
- 在Ubuntu系统下,so.6是glibc的库文件,可直接执行该库文件查看版本,在主机上执行如下命令:/lib/x86_64-linux-gnu/libc.so.6
GCC的基本命令
GCC的基本用法是编译源代码文件. 以下是一些基本的GCC命令:
- gcc filename.c:用来编译C文件生成out可执行文件
- gcc -o output filename.c:编译C文件并生成名称为output的可执行文件
- gcc -c filename.c:只预处理和编译,但不链接,生成obj文件
- gcc -E filename.c:只进行预处理,生成预处理后的C源文件
GCC编译流程
文件类型
对于任何给定的输入文件,文件类型决定进行何种编译。GCC常用的文件类型如表1所示:
后缀 | 描述 |
.c | C 源文件 |
.C/.cc/.cxx/.cpp | C++ 源文件 |
.h | C/C++ 头文件 |
.i/.ii | 经过预处理的 C/C++ 文件 |
.s/.S | 汇编语言源文件 |
.o/.obj | 目标文件 |
.a/.lib | 静态库 |
.so/.dll | 动态库 |
.out | 可执行文件,但可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。
如果没有给出可执行文件的名字,GCC将生成一个名为a.out的文件。 |
使用GCC将源代码文件生成可执行文件,需要经过预处理、编译、汇编和链接。
- 预处理:将源程序(如 .c 文件)预处理,生成 .i 文件。
- 编译:将预处理后的 .i 文件编译成为汇编语言,生成 .s 文件。
- 汇编:将汇编语言文件经过汇编,生成目标文件 .o 文件。
- 链接:将各个模块的 .o 文件链接起来生成一个可执行程序文件。
其中 .i 文件、.s文件、.o文件是中间文件或临时文件,如果使用GCC一次性完成C语言程序的编译,则这些文件会被删除。
Hello World程序编译
hello.c代码
#include <stdio.h> int main() { printf("hello world\n"); return 0; }
下面就来细讲整个编译的过程。
上图是一个hello的c程序由gcc编译器从源码文件hello.c中读取内容并将其翻译成为一个可执行的对象文件hello的过程。这个过程包含了几个阶段:
预处理过程
预处理(cpp)根据以字符#开头的命令,gcc -E hello.c -o hello.i修改原始的C程序。比如hello.c中的第一行的#include <stdio.h> 命令告诉预处理器读取系统头文件stdio.h的内容,插入到程序文本中。结果就得到里另一个C程序,通常是以*.i作为文件扩展名。
可通过执行如下指令获得:
用记事本打开,可查看到如下的内容:
# 0 "hello.c" # 0 "<built-in>" # 0 "<command-line>" # 1 "/usr/include/stdc-predef.h" 1 3 4 # 0 "<command-line>" 2 # 1 "hello.c" # 1 "/usr/include/stdio.h" 1 3 4 # 27 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 1 3 4 # 33 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 3 4 # 1 "/usr/include/features.h" 1 3 4 # 392 "/usr/include/features.h" 3 4 # 1 "/usr/include/features-time64.h" 1 3 4 # 20 "/usr/include/features-time64.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 # 21 "/usr/include/features-time64.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 1 3 4 # 19 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 # 20 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 2 3 4 # 22 "/usr/include/features-time64.h" 2 3 4 # 393 "/usr/include/features.h" 2 3 4 # 486 "/usr/include/features.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4 # 559 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 # 560 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4 # 561 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4 # 487 "/usr/include/features.h" 2 3 4 # 510 "/usr/include/features.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4 # 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4 # 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4 # 511 "/usr/include/features.h" 2 3 4 # 34 "/usr/include/x86_64-linux-gnu/bits/libc-header-start.h" 2 3 4 # 28 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h" 1 3 4 # 209 "/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h" 3 4 # 209 "/usr/lib/gcc/x86_64-linux-gnu/11/include/stddef.h" 3 4 typedef long unsigned int size_t; # 34 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.h" 1 3 4 # 40 "/usr/lib/gcc/x86_64-linux-gnu/11/include/stdarg.h" 3 4 typedef __builtin_va_list __gnuc_va_list; # 37 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types.h" 1 3 4 # 27 "/usr/include/x86_64-linux-gnu/bits/types.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 # 28 "/usr/include/x86_64-linux-gnu/bits/types.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 1 3 4 # 19 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4 # 20 "/usr/include/x86_64-linux-gnu/bits/timesize.h" 2 3 4 # 29 "/usr/include/x86_64-linux-gnu/bits/types.h" 2 3 4 typedef unsigned char __u_char; typedef unsigned short int __u_short; typedef unsigned int __u_int; typedef unsigned long int __u_long; typedef signed char __int8_t; typedef unsigned char __uint8_t; typedef signed short int __int16_t; typedef unsigned short int __uint16_t; typedef signed int __int32_t; typedef unsigned int __uint32_t; typedef signed long int __int64_t; typedef unsigned long int __uint64_t; typedef __int8_t __int_least8_t; typedef __uint8_t __uint_least8_t; typedef __int16_t __int_least16_t; typedef __uint16_t __uint_least16_t; typedef __int32_t __int_least32_t; typedef __uint32_t __uint_least32_t; typedef __int64_t __int_least64_t; typedef __uint64_t __uint_least64_t; typedef long int __quad_t; typedef unsigned long int __u_quad_t; typedef long int __intmax_t; typedef unsigned long int __uintmax_t; # 141 "/usr/include/x86_64-linux-gnu/bits/types.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/typesizes.h" 1 3 4 # 142 "/usr/include/x86_64-linux-gnu/bits/types.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/time64.h" 1 3 4 # 143 "/usr/include/x86_64-linux-gnu/bits/types.h" 2 3 4 typedef unsigned long int __dev_t; typedef unsigned int __uid_t; typedef unsigned int __gid_t; typedef unsigned long int __ino_t; typedef unsigned long int __ino64_t; typedef unsigned int __mode_t; typedef unsigned long int __nlink_t; typedef long int __off_t; typedef long int __off64_t; typedef int __pid_t; typedef struct { int __val[2]; } __fsid_t; typedef long int __clock_t; typedef unsigned long int __rlim_t; typedef unsigned long int __rlim64_t; typedef unsigned int __id_t; typedef long int __time_t; typedef unsigned int __useconds_t; typedef long int __suseconds_t; typedef long int __suseconds64_t; typedef int __daddr_t; typedef int __key_t; typedef int __clockid_t; typedef void * __timer_t; typedef long int __blksize_t; typedef long int __blkcnt_t; typedef long int __blkcnt64_t; typedef unsigned long int __fsblkcnt_t; typedef unsigned long int __fsblkcnt64_t; typedef unsigned long int __fsfilcnt_t; typedef unsigned long int __fsfilcnt64_t; typedef long int __fsword_t; typedef long int __ssize_t; typedef long int __syscall_slong_t; typedef unsigned long int __syscall_ulong_t; typedef __off64_t __loff_t; typedef char *__caddr_t; typedef long int __intptr_t; typedef unsigned int __socklen_t; typedef int __sig_atomic_t; # 39 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h" 1 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h" 1 3 4 # 13 "/usr/include/x86_64-linux-gnu/bits/types/__mbstate_t.h" 3 4 typedef struct { int __count; union { unsigned int __wch; char __wchb[4]; } __value; } __mbstate_t; # 6 "/usr/include/x86_64-linux-gnu/bits/types/__fpos_t.h" 2 3 4 typedef struct _G_fpos_t { __off_t __pos; __mbstate_t __state; } __fpos_t; # 40 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h" 1 3 4 # 10 "/usr/include/x86_64-linux-gnu/bits/types/__fpos64_t.h" 3 4 typedef struct _G_fpos64_t { __off64_t __pos; __mbstate_t __state; } __fpos64_t; # 41 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/__FILE.h" 1 3 4 struct _IO_FILE; typedef struct _IO_FILE __FILE; # 42 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/FILE.h" 1 3 4 struct _IO_FILE; typedef struct _IO_FILE FILE; # 43 "/usr/include/stdio.h" 2 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h" 1 3 4 # 35 "/usr/include/x86_64-linux-gnu/bits/types/struct_FILE.h" 3 4 struct _IO_FILE; struct _IO_marker; struct _IO_codecvt; struct _IO_wide_data; typedef void _IO_lock_t; struct _IO_FILE { int _flags; char *_IO_read_ptr; char *_IO_read_end; char *_IO_read_base; char *_IO_write_base; char *_IO_write_ptr; char *_IO_write_end; char *_IO_buf_base; char *_IO_buf_end; char *_IO_save_base; char *_IO_backup_base; char *_IO_save_end; struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; int _flags2; __off_t _old_offset; unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; __off64_t _offset; struct _IO_codecvt *_codecvt; struct _IO_wide_data *_wide_data; struct _IO_FILE *_freeres_list; void *_freeres_buf; size_t __pad5; int _mode; char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)]; }; # 44 "/usr/include/stdio.h" 2 3 4 # 52 "/usr/include/stdio.h" 3 4 typedef __gnuc_va_list va_list; # 63 "/usr/include/stdio.h" 3 4 typedef __off_t off_t; # 77 "/usr/include/stdio.h" 3 4 typedef __ssize_t ssize_t; typedef __fpos_t fpos_t; # 133 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/stdio_lim.h" 1 3 4 # 134 "/usr/include/stdio.h" 2 3 4 # 143 "/usr/include/stdio.h" 3 4 extern FILE *stdin; extern FILE *stdout; extern FILE *stderr; extern int remove (const char *__filename) __attribute__ ((__nothrow__ , __leaf__)); extern int rename (const char *__old, const char *__new) __attribute__ ((__nothrow__ , __leaf__)); extern int renameat (int __oldfd, const char *__old, int __newfd, const char *__new) __attribute__ ((__nothrow__ , __leaf__)); # 178 "/usr/include/stdio.h" 3 4 extern int fclose (FILE *__stream); # 188 "/usr/include/stdio.h" 3 4 extern FILE *tmpfile (void) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ; # 205 "/usr/include/stdio.h" 3 4 extern char *tmpnam (char[20]) __attribute__ ((__nothrow__ , __leaf__)) ; extern char *tmpnam_r (char __s[20]) __attribute__ ((__nothrow__ , __leaf__)) ; # 222 "/usr/include/stdio.h" 3 4 extern char *tempnam (const char *__dir, const char *__pfx) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (__builtin_free, 1))); extern int fflush (FILE *__stream); # 239 "/usr/include/stdio.h" 3 4 extern int fflush_unlocked (FILE *__stream); # 258 "/usr/include/stdio.h" 3 4 extern FILE *fopen (const char *__restrict __filename, const char *__restrict __modes) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ; extern FILE *freopen (const char *__restrict __filename, const char *__restrict __modes, FILE *__restrict __stream) ; # 293 "/usr/include/stdio.h" 3 4 extern FILE *fdopen (int __fd, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ; # 308 "/usr/include/stdio.h" 3 4 extern FILE *fmemopen (void *__s, size_t __len, const char *__modes) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ; extern FILE *open_memstream (char **__bufloc, size_t *__sizeloc) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (fclose, 1))) ; # 328 "/usr/include/stdio.h" 3 4 extern void setbuf (FILE *__restrict __stream, char *__restrict __buf) __attribute__ ((__nothrow__ , __leaf__)); extern int setvbuf (FILE *__restrict __stream, char *__restrict __buf, int __modes, size_t __n) __attribute__ ((__nothrow__ , __leaf__)); extern void setbuffer (FILE *__restrict __stream, char *__restrict __buf, size_t __size) __attribute__ ((__nothrow__ , __leaf__)); extern void setlinebuf (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int fprintf (FILE *__restrict __stream, const char *__restrict __format, ...); extern int printf (const char *__restrict __format, ...); extern int sprintf (char *__restrict __s, const char *__restrict __format, ...) __attribute__ ((__nothrow__)); extern int vfprintf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg); extern int vprintf (const char *__restrict __format, __gnuc_va_list __arg); extern int vsprintf (char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__)); extern int snprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, ...) __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 4))); extern int vsnprintf (char *__restrict __s, size_t __maxlen, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__)) __attribute__ ((__format__ (__printf__, 3, 0))); # 403 "/usr/include/stdio.h" 3 4 extern int vdprintf (int __fd, const char *__restrict __fmt, __gnuc_va_list __arg) __attribute__ ((__format__ (__printf__, 2, 0))); extern int dprintf (int __fd, const char *__restrict __fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) ; extern int scanf (const char *__restrict __format, ...) ; extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __attribute__ ((__nothrow__ , __leaf__)); # 1 "/usr/include/x86_64-linux-gnu/bits/floatn.h" 1 3 4 # 119 "/usr/include/x86_64-linux-gnu/bits/floatn.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/floatn-common.h" 1 3 4 # 24 "/usr/include/x86_64-linux-gnu/bits/floatn-common.h" 3 4 # 1 "/usr/include/x86_64-linux-gnu/bits/long-double.h" 1 3 4 # 25 "/usr/include/x86_64-linux-gnu/bits/floatn-common.h" 2 3 4 # 120 "/usr/include/x86_64-linux-gnu/bits/floatn.h" 2 3 4 # 431 "/usr/include/stdio.h" 2 3 4 extern int fscanf (FILE *__restrict __stream, const char *__restrict __format, ...) __asm__ ("" "__isoc99_fscanf") ; extern int scanf (const char *__restrict __format, ...) __asm__ ("" "__isoc99_scanf") ; extern int sscanf (const char *__restrict __s, const char *__restrict __format, ...) __asm__ ("" "__isoc99_sscanf") __attribute__ ((__nothrow__ , __leaf__)) ; # 459 "/usr/include/stdio.h" 3 4 extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__format__ (__scanf__, 2, 0))) ; extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__format__ (__scanf__, 1, 0))) ; extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0))); extern int vfscanf (FILE *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vfscanf") __attribute__ ((__format__ (__scanf__, 2, 0))) ; extern int vscanf (const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vscanf") __attribute__ ((__format__ (__scanf__, 1, 0))) ; extern int vsscanf (const char *__restrict __s, const char *__restrict __format, __gnuc_va_list __arg) __asm__ ("" "__isoc99_vsscanf") __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__format__ (__scanf__, 2, 0))); # 513 "/usr/include/stdio.h" 3 4 extern int fgetc (FILE *__stream); extern int getc (FILE *__stream); extern int getchar (void); extern int getc_unlocked (FILE *__stream); extern int getchar_unlocked (void); # 538 "/usr/include/stdio.h" 3 4 extern int fgetc_unlocked (FILE *__stream); # 549 "/usr/include/stdio.h" 3 4 extern int fputc (int __c, FILE *__stream); extern int putc (int __c, FILE *__stream); extern int putchar (int __c); # 565 "/usr/include/stdio.h" 3 4 extern int fputc_unlocked (int __c, FILE *__stream); extern int putc_unlocked (int __c, FILE *__stream); extern int putchar_unlocked (int __c); extern int getw (FILE *__stream); extern int putw (int __w, FILE *__stream); extern char *fgets (char *__restrict __s, int __n, FILE *__restrict __stream) __attribute__ ((__access__ (__write_only__, 1, 2))); # 632 "/usr/include/stdio.h" 3 4 extern __ssize_t __getdelim (char **__restrict __lineptr, size_t *__restrict __n, int __delimiter, FILE *__restrict __stream) ; extern __ssize_t getdelim (char **__restrict __lineptr, size_t *__restrict __n, int __delimiter, FILE *__restrict __stream) ; extern __ssize_t getline (char **__restrict __lineptr, size_t *__restrict __n, FILE *__restrict __stream) ; extern int fputs (const char *__restrict __s, FILE *__restrict __stream); extern int puts (const char *__s); extern int ungetc (int __c, FILE *__stream); extern size_t fread (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream) ; extern size_t fwrite (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __s); # 702 "/usr/include/stdio.h" 3 4 extern size_t fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream) ; extern size_t fwrite_unlocked (const void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream); extern int fseek (FILE *__stream, long int __off, int __whence); extern long int ftell (FILE *__stream) ; extern void rewind (FILE *__stream); # 736 "/usr/include/stdio.h" 3 4 extern int fseeko (FILE *__stream, __off_t __off, int __whence); extern __off_t ftello (FILE *__stream) ; # 760 "/usr/include/stdio.h" 3 4 extern int fgetpos (FILE *__restrict __stream, fpos_t *__restrict __pos); extern int fsetpos (FILE *__stream, const fpos_t *__pos); # 786 "/usr/include/stdio.h" 3 4 extern void clearerr (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int feof (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int ferror (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void clearerr_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int feof_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int ferror_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void perror (const char *__s); extern int fileno (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern int fileno_unlocked (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; # 823 "/usr/include/stdio.h" 3 4 extern int pclose (FILE *__stream); extern FILE *popen (const char *__command, const char *__modes) __attribute__ ((__malloc__)) __attribute__ ((__malloc__ (pclose, 1))) ; extern char *ctermid (char *__s) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__access__ (__write_only__, 1))); # 867 "/usr/include/stdio.h" 3 4 extern void flockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); extern int ftrylockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) ; extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)); # 885 "/usr/include/stdio.h" 3 4 extern int __uflow (FILE *); extern int __overflow (FILE *, int); # 902 "/usr/include/stdio.h" 3 4 # 2 "hello.c" 2 # 3 "hello.c" int main() { printf("hello world\n"); return 0; }
以上部分代码可以看出除了#include <stdio.h>指令之外其他指令并未被改变。
编译阶段
编译器将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文件格式确切地描述了一条低级机器语言指令。
可通过如下指令获得:gcc -S hello.c -o hello.s
用记事本打开,可查看到如下的内容:
.file "hello.c" .text .section .rodata .LC0: .string "hello world" .text .globl main .type main, @function main: .LFB0: .cfi_startproc endbr64 pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 leaq .LC0(%rip), %rax movq %rax, %rdi call puts@PLT movl $0, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size main, .-main .ident "GCC: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0" .section .note.GNU-stack,"",@progbits .section .note.gnu.property,"a" .align 8 .long 1f - 0f .long 4f - 1f .long 5 0: .string "GNU" 1: .align 8 .long 0xc0000002 .long 3f - 2f 2: .long 0x3 3: .align 8 4:
汇编阶段
汇编器将hello.s翻译成机器语言指令,把这些指令打包成一种可重定位目标程序的格式,并把结果保存在hello.o中,hello.o是一个二进制文件,它的字节编码是机器语言指令,而不是字符。
可通过如下指令获得:gcc -c hello.c,最终生成的hello.o文件需要使用objdump打开,具体指令为:objdump -d hello.o
获取的内容为:
hello.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <main>: 0: f3 0f 1e fa endbr64 4: 55 push %rbp 5: 48 89 e5 mov %rsp,%rbp 8: 48 8d 05 00 00 00 00 lea 0x0(%rip),%rax # f <main+0xf> f: 48 89 c7 mov %rax,%rdi 12: e8 00 00 00 00 call 17 <main+0x17> 17: b8 00 00 00 00 mov $0x0,%eax 1c: 5d pop %rbp 1d: c3 ret
链接阶段
hello.c程序中调用了printf函数,而printf函数存在于一个名为printf.o的单独的预编译好的目标文件中,而这个文件必须以某种方式合并到我们的hello.o程序中。连接器就负责处理这种合并,结果就得到hello文件,它是一个可执行文件,可以被加载到内存中由系统执行。
使用的指令为:gcc hello.o -o hello,最终生成了一个hello文件,同样此hello文件可用通过objdump打开:objdump -d hello
打开后的内容为:
hello: file format elf64-x86-64 Disassembly of section .init: 0000000000001000 <_init>: 1000: f3 0f 1e fa endbr64 1004: 48 83 ec 08 sub $0x8,%rsp 1008: 48 8b 05 d9 2f 00 00 mov 0x2fd9(%rip),%rax # 3fe8 <__gmon_start__@Base> 100f: 48 85 c0 test %rax,%rax 1012: 74 02 je 1016 <_init+0x16> 1014: ff d0 call *%rax 1016: 48 83 c4 08 add $0x8,%rsp 101a: c3 ret Disassembly of section .plt: 0000000000001020 <.plt>: 1020: ff 35 9a 2f 00 00 push 0x2f9a(%rip) # 3fc0 <_GLOBAL_OFFSET_TABLE_+0x8> 1026: f2 ff 25 9b 2f 00 00 bnd jmp *0x2f9b(%rip) # 3fc8 <_GLOBAL_OFFSET_TABLE_+0x10> 102d: 0f 1f 00 nopl (%rax) 1030: f3 0f 1e fa endbr64 1034: 68 00 00 00 00 push $0x0 1039: f2 e9 e1 ff ff ff bnd jmp 1020 <_init+0x20> 103f: 90 nop Disassembly of section .plt.got: 0000000000001040 <__cxa_finalize@plt>: 1040: f3 0f 1e fa endbr64 1044: f2 ff 25 ad 2f 00 00 bnd jmp *0x2fad(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5> 104b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) Disassembly of section .plt.sec: 0000000000001050 <puts@plt>: 1050: f3 0f 1e fa endbr64 1054: f2 ff 25 75 2f 00 00 bnd jmp *0x2f75(%rip) # 3fd0 <puts@GLIBC_2.2.5> 105b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1) Disassembly of section .text: 0000000000001060 <_start>: 1060: f3 0f 1e fa endbr64 1064: 31 ed xor %ebp,%ebp 1066: 49 89 d1 mov %rdx,%r9 1069: 5e pop %rsi 106a: 48 89 e2 mov %rsp,%rdx 106d: 48 83 e4 f0 and $0xfffffffffffffff0,%rsp 1071: 50 push %rax 1072: 54 push %rsp 1073: 45 31 c0 xor %r8d,%r8d 1076: 31 c9 xor %ecx,%ecx 1078: 48 8d 3d ca 00 00 00 lea 0xca(%rip),%rdi # 1149 <main> 107f: ff 15 53 2f 00 00 call *0x2f53(%rip) # 3fd8 <__libc_start_main@GLIBC_2.34> 1085: f4 hlt 1086: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1) 108d: 00 00 00 0000000000001090 <deregister_tm_clones>: 1090: 48 8d 3d 79 2f 00 00 lea 0x2f79(%rip),%rdi # 4010 <__TMC_END__> 1097: 48 8d 05 72 2f 00 00 lea 0x2f72(%rip),%rax # 4010 <__TMC_END__> 109e: 48 39 f8 cmp %rdi,%rax 10a1: 74 15 je 10b8 <deregister_tm_clones+0x28> 10a3: 48 8b 05 36 2f 00 00 mov 0x2f36(%rip),%rax # 3fe0 <_ITM_deregisterTMCloneTable@Base> 10aa: 48 85 c0 test %rax,%rax 10ad: 74 09 je 10b8 <deregister_tm_clones+0x28> 10af: ff e0 jmp *%rax 10b1: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 10b8: c3 ret 10b9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 00000000000010c0 <register_tm_clones>: 10c0: 48 8d 3d 49 2f 00 00 lea 0x2f49(%rip),%rdi # 4010 <__TMC_END__> 10c7: 48 8d 35 42 2f 00 00 lea 0x2f42(%rip),%rsi # 4010 <__TMC_END__> 10ce: 48 29 fe sub %rdi,%rsi 10d1: 48 89 f0 mov %rsi,%rax 10d4: 48 c1 ee 3f shr $0x3f,%rsi 10d8: 48 c1 f8 03 sar $0x3,%rax 10dc: 48 01 c6 add %rax,%rsi 10df: 48 d1 fe sar %rsi 10e2: 74 14 je 10f8 <register_tm_clones+0x38> 10e4: 48 8b 05 05 2f 00 00 mov 0x2f05(%rip),%rax # 3ff0 <_ITM_registerTMCloneTable@Base> 10eb: 48 85 c0 test %rax,%rax 10ee: 74 08 je 10f8 <register_tm_clones+0x38> 10f0: ff e0 jmp *%rax 10f2: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) 10f8: c3 ret 10f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 0000000000001100 <__do_global_dtors_aux>: 1100: f3 0f 1e fa endbr64 1104: 80 3d 05 2f 00 00 00 cmpb $0x0,0x2f05(%rip) # 4010 <__TMC_END__> 110b: 75 2b jne 1138 <__do_global_dtors_aux+0x38> 110d: 55 push %rbp 110e: 48 83 3d e2 2e 00 00 cmpq $0x0,0x2ee2(%rip) # 3ff8 <__cxa_finalize@GLIBC_2.2.5> 1115: 00 1116: 48 89 e5 mov %rsp,%rbp 1119: 74 0c je 1127 <__do_global_dtors_aux+0x27> 111b: 48 8b 3d e6 2e 00 00 mov 0x2ee6(%rip),%rdi # 4008 <__dso_handle> 1122: e8 19 ff ff ff call 1040 <__cxa_finalize@plt> 1127: e8 64 ff ff ff call 1090 <deregister_tm_clones> 112c: c6 05 dd 2e 00 00 01 movb $0x1,0x2edd(%rip) # 4010 <__TMC_END__> 1133: 5d pop %rbp 1134: c3 ret 1135: 0f 1f 00 nopl (%rax) 1138: c3 ret 1139: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) 0000000000001140 <frame_dummy>: 1140: f3 0f 1e fa endbr64 1144: e9 77 ff ff ff jmp 10c0 <register_tm_clones> 0000000000001149 <main>: 1149: f3 0f 1e fa endbr64 114d: 55 push %rbp 114e: 48 89 e5 mov %rsp,%rbp 1151: 48 8d 05 ac 0e 00 00 lea 0xeac(%rip),%rax # 2004 <_IO_stdin_used+0x4> 1158: 48 89 c7 mov %rax,%rdi 115b: e8 f0 fe ff ff call 1050 <puts@plt> 1160: b8 00 00 00 00 mov $0x0,%eax 1165: 5d pop %rbp 1166: c3 ret Disassembly of section .fini: 0000000000001168 <_fini>: 1168: f3 0f 1e fa endbr64 116c: 48 83 ec 08 sub $0x8,%rsp 1170: 48 83 c4 08 add $0x8,%rsp 1174: c3 ret
经过上面四个过程,我们就可以把一个源代码文件编译成机器能运行的可执行文件。这个可执行文件刚开始是保存在磁盘上,当计算机要运行这个程序的时候,hello就被加载到内存中,接着程序指令被不断复制到寄存器中由CPU来执行,最后把”hello world”从寄存器中打到显示设备上。这就是hello程序整个执行过程。
GCC编译选项
GCC常用选项
GCC(GNU Compiler Collection)是一个开源的编译器集合,它包括了 C、C++、Objective-C、Fortran、Ada 和 Go 等语言的编译器。GCC 提供了大量的选项,这里列举一些常用的:
- -o:指定输出文件的名称。例如,gcc -o output input.c 将编译c 并将生成的可执行文件命名为 output。
- -c:只编译但不链接。这用于生成目标文件(.o 文件)。例如,gcc -c input.c 将生成o。
- -S:只编译但不汇编,生成汇编代码。
- -E:只进行预处理。
- -I:指定头文件的搜索路径。例如,gcc -I /path/to/headers input.c 将在 /path/to/headers 目录下搜索头文件。
- -L 和 -l:分别用于指定库文件的搜索路径和链接库文件。例如,gcc -L /path/to/libs -l mylib input.c 将在 /path/to/libs 目录下搜索以 libmylib 开头的库文件。
- -g:生成调试信息。这对于使用调试器(如 GDB)进行调试是必需的。
- -O:优化代码。-O 后面可以跟一个数字来指定优化级别,如 -O0(关闭优化)、-O1(开启一些优化,但不会影响编译时间和可调试性)、-O2(开启更多优化,可能会影响编译时间和可调试性)、-O3(开启所有优化)。
- -W:开启警告。如 -Wall 开启所有警告,-Wextra 开启额外的警告。
- -std:指定编译的 C 或 C++ 标准。如 -std=c11 指定使用 C11 标准,-std=c++11 指定使用 C++11 标准。
- -shared:生成共享目标文件。通常在建立共享库时。
以上只是 GCC 选项中的一部分,更多详细的选项和信息,可以参考 GCC 的官方文档或者在命令行中使用 man gcc 命令查看。
GCC多文件编译
如果你的 C 或 C++ 项目包含多个源文件,你可以使用 GCC 进行多文件编译。在多文件编译时,通常有两种方法:分步编译和一步编译。
分步编译
首先使用 GCC 的 -c 选项将每个源文件编译成目标文件,然后再将所有目标文件链接成一个可执行文件。例如,如果你的项目包含 main.c、file1.c 和 file2.c 三个源文件,你可以这样编译:
gcc -c main.c # 生成 main.o gcc -c file1.c # 生成 file1.o gcc -c file2.c # 生成 file2.o gcc -o prog main.o file1.o file2.o # 链接目标文件生成可执行文件 prog
一步编译
GCC 可以一次接收多个源文件作为输入,并自动完成编译和链接的过程。使用这种方法,上面的例子可以简化为一行命令:
gcc -o prog main.c file1.c file2.c
在大型项目中,通常使用 Makefile 或其他构建工具来自动化编译过程。这些工具可以跟踪文件的依赖关系和修改,只编译修改过的文件和它们的依赖,从而大大减少编译时间。
最后,注意如果你的项目中使用了库,你可能需要使用 -I、-L 和 -l 等选项来指定头文件和库文件的位置。
静态库和动态库
在 GCC 中创建和链接静态库和动态库需要以下步骤:
创建静态库
首先,你需要使用 -c 选项编译源文件,但不进行链接,生成目标文件(.o 文件)。例如:
gcc -c file1.c # 生成 file1.o gcc -c file2.c # 生成 file2.o
然后,使用 ar 命令创建静态库(.a 文件):
ar rcs libmylib.a file1.o file2.o
这里,r 表示插入文件(如果库已经存在,则替换库中的同名文件),c 表示创建新库,s 表示创建目标文件索引。
创建动态库
创建动态库(.so 文件)需要使用 -shared 和 -fPIC 选项。例如:
gcc -shared -fPIC -o libmylib.so file1.c file2.c
这里,-shared 表示创建动态库,-fPIC 表示生成位置无关代码(Position Independent Code),这是创建动态库的要求。
链接静态库
链接静态库需要使用 -L 和 -l 选项,并且可以加上 -static 选项以强制链接静态库。例如:
gcc -static -L. -lmylib -o prog main.c
这里,. 表示库文件在当前目录下,-lmylib 表示链接名为 mylib 的库(即 libmylib.a 文件)。
链接动态库
链接动态库也需要使用 -L 和 -l 选项。例如:
gcc -L. -lmylib -o prog main.c
注意,运行使用动态库的程序时,系统需要知道动态库的位置。如果动态库不在系统的库搜索路径中,你需要设置 LD_LIBRARY_PATH 环境变量。例如,如果 libmylib.so 在当前目录下,你可以这样运行程序:
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ./prog
这些只是创建和链接库的基本步骤,实际操作可能会更复杂,例如你可能需要使用 Makefile 或其他构建工具来管理编译过程,或者使用 nm、objdump 等工具来查看和调试库文件。
GCC交叉编译
交叉编译是在一个平台(宿主主机)上生成另一个平台(目标主机)上运行的代码。这是嵌入式系统开发中常见的一种做法,因为嵌入式设备的计算能力有限,不适合直接在设备上编译代码。
在 GCC 中进行交叉编译需要使用交叉编译器。交叉编译器的名称通常包含目标平台的信息,例如 arm-linux-gnueabi-gcc 是用于 ARM 平台、使用 Linux 操作系统和 GNU EABI 的 GCC 交叉编译器。
一旦安装了交叉编译器,你就可以像普通 GCC 一样使用它。例如:
arm-linux-gnueabi-gcc -o prog main.c
这个命令将在宿主主机上编译 main.c,并生成 ARM 平台上的可执行文件 prog。
注意,进行交叉编译时,你可能需要为目标平台提供头文件和库文件。这通常涉及到 -I、-L 和 -l 等选项。如果你使用的是嵌入式 Linux 系统,你可能需要使用 Buildroot、Yocto Project 或其他工具来生成完整的根文件系统(包括库文件和头文件)。
此外,如果你的项目比较复杂,你可能需要使用 Makefile 或其他构建工具来管理编译过程,并且可能需要配置编译器的选项,例如优化级别、静态链接或动态链接等。
最后,注意在交叉编译后,你需要在目标主机上测试你的程序,确保它在目标平台上正确运行。