$ gcc helloworld.c -o helloworld -v
Using built-in specs. # 编译链接详细信息
COLLECT_GCC=gcc       # 编译时所调用的总调度程序
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/lto-wrapper
Target: x86_64-redhat-linux 

# gcc配置信息
Configured with: 
../configure --prefix=/usr 
--mandir=/usr/share/man 
--infodir=/usr/share/info 
--with-bugurl=http://bugzilla.redhat.com/bugzilla 
--enable-bootstrap 
--enable-shared 
--enable-threads=posix 
--enable-checking=release 
--with-system-zlib 
--enable-__cxa_atexit 
--disable-libunwind-exceptions 
--enable-gnu-unique-object 
--enable-linker-build-id 
--with-linker-hash-style=gnu 
--enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto 
--enable-plugin 
--enable-initfini-array
--disable-libgcj
--with-isl=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/isl-install 
--with-cloog=/builddir/build/BUILD/gcc-4.8.5-20150702/obj-x86_64-redhat-linux/cloog-install 
--enable-gnu-indirect-function 
--with-tune=generic 
--with-arch_32=x86-64 
--build=x86_64-redhat-linux


Thread model: posix # 线程模型是 posix
gcc version 4.8.5 20150623 (Red Hat 4.8.5-36) (GCC)  # gcc版本

# -march=x86-64:intel 64位 x86 cpu
# -mtune=generic:编译得到的机器指令,属于通用指令集(同款的不同型号的cpu都支持的指令集)
# 如果需要指定某型号cpu的特殊指令集时,就不能写成 generic,而要写特殊指令集名称
COLLECT_GCC_OPTIONS='-o' 'helloworld' '-v' '-mtune=generic' '-march=x86-64' 


# 预编译、编译
/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/cc1 
-quiet -v helloworld.c -quiet -dumpbase helloworld.c 
-mtune=generic -march=x86-64 -auxbase helloworld -version -o /tmp/ccZWxdCt.s


GNU C (GCC) version 4.8.5 20150623 (Red Hat 4.8.5-36) (x86_64-redhat-linux)
        compiled by GNU C version 4.8.5 20150623 (Red Hat 4.8.5-36), GMP version 6.0.0, MPFR version 3.1.1, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../x86_64-redhat-linux/include"


#include "..." search starts here: # 包含""所指定的头文件:到程序员自己指定的路径下去搜索
#include <...> search starts here: # 包含<>所指定的头文件:到系统指定的路径下去找
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/include
 /usr/local/include
 /usr/include
End of search list.


GNU C (GCC) version 4.8.5 20150623 (Red Hat 4.8.5-36) (x86_64-redhat-linux)
        compiled by GNU C version 4.8.5 20150623 (Red Hat 4.8.5-36), GMP version 6.0.0, MPFR version 3.1.1, MPC version 1.0.1
GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: c0b461ba69dba093bfc939a7fa8b7724


COLLECT_GCC_OPTIONS='-o' 'helloworld' '-v' '-mtune=generic' '-march=x86-64'


# 汇编:由于as的路径已经被加入到了环境变量中,因此调用as时,并不需要指定as的路径。=
# 生成ccZWxdCt.s汇编文件是一个临时文件,/tmp目录专门用于存放Linux系统所生成的临时文件,
# 一旦编译得到了可执行文件,这个ccZWxdCt.s将会别删除
as -v --64 -o /tmp/ccN6ZXpL.o /tmp/ccZWxdCt.s


GNU assembler version 2.27 (x86_64-redhat-linux) using BFD version version 2.27-34.base.el7
COMPILER_PATH=/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/4.8.5/:/usr/libexec/gcc/x86_64-redhat-linux/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/
LIBRARY_PATH=/usr/lib/gcc/x86_64-redhat-linux/4.8.5/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/:/lib/../lib64/:/usr/lib/../lib64/:/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../:/lib/:/usr/lib/
COLLECT_GCC_OPTIONS='-o' 'helloworld' '-v' '-mtune=generic' '-march=x86-64'


# 链接信息
# collect2为链接器,由于collect2的路径没有加入环境变量,因此需要我们自己指明路径
# -dynamic-linker /lib64/ld-linux-x86-64.so.2  给程序指定动态链接器:程序运行起来后,用于加载动态库
 /usr/libexec/gcc/x86_64-redhat-linux/4.8.5/collect2
 --build-id
 --no-add-needed
 --eh-frame-hdr
 --hash-style=gnu
 -m elf_x86_64
 -dynamic-linker /lib64/ld-linux-x86-64.so.2
 -o helloworld
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64
-L/lib/../lib64
-L/usr/lib/../lib64
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/ccN6ZXpL.o
-lgcc --as-needed
-lgcc_s --no-as-needed
-lc
-lgcc --as-needed
-lgcc_s --no-as-needed
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

一. 什么是gcc配置信息

gcc也是一个程序,也是被别人开发出来的

编写gcc这个编译器程序的人,在编译gcc时所给的信息就是配置信息,这些信息会决定编译gcc哪些代码,
不编译哪些代码,最终得到针对某个环境(OS/CPU)的gcc可执行程序。


二. gcc程序如何面对众多环境的

gnu开发的gcc可以面对很多的环境,比如windows x86环境,Linux x86环境,Linux arm环境

编写gcc的人,为了让程序能够应对各种环境(OS、cpu),gcc程序里面会包含应对各个环境的代码,
如果你想得到针对某个环境gcc可执行程序,就必须只编译针对该环境的代码,其它代码不编译


三. 如何选择只编译gcc程序针对某个环境的代码

通过条件编译来选择,通过条件编译这的东西,就可以在预编译阶段决定你要保留哪些代码,
放弃哪些代码,编译时只编译你保留的代码

举一个简单的例子
比如:gcc.c

#define X86_LINUX

#ifdef X86_LINUX
针对x86、Linux环境的代码。
#endif

#ifdef ARM_LINUX
针对arm、Linux环境的代码。
#endif

#ifdef X86_WINDOWS
针对X86、windows环境的代码。
#endif

通过条件编译所需的宏,就能让条件编译保留和编译只针对某个环境的代码

但是由于c条件编译使用的宏实在是太多了,所以我们不可能自己一个一个的定义这些宏,
所以就需要通过配置信息自动生成需要的宏

配置信息保存在哪里呢?
配置信息保存在配置文件中,我们配置信息时,其实就是修改配置文件中的内容。

运行配置文件时,根据配置信息的要求,会自动生成需要的宏定义,
并把这些宏定义保存到相应的.h(头文件)中

再将.hc/c++程序,预处理时,条件编译根据.h中定义的宏定义,就能决定保留和编译哪些代码

编译时就只编译保留的代码,最后就得到了针对某个环境的gcc可执行程序,
不过这些配置信息会保留gcc中,gcc -v时会显示出来

通过了解gcc的配置信息,可以大概的知道一些gcc的特性


四. 了解 gcc 条件编译和配置信息的意义

(1)有助于我们后面学习C语言”条件编译”相关的内容
(2)复杂的c/c++程序,都会涉及到条件编译和配置信息
gcc也是一个复杂的c/c++程序,所以必然涉及条件编译和配置信息

我们后面讲ubootlinux内核时,里面会有大量的条件编译,编译时必须修改配置信息,
通过配置信息去打开和关闭uboot/linux内核的条件编译,选择哪些代码保留,哪些代码忽略,
然后编译保留的代码,得到针对某个环境(OS/CPU)的可执行程序


五. gcc配置信息

gcc的配置文件,这句话仅仅只是向我们表明,gcc的配置信息其实是来源于这个文件,
这个配置文件并不在我的电脑上,而是在gcc开发者的电脑上,编译gccgcc开发者会去设置这个配置文件

Configured with: ../src/configure

gcc bug报告说明书:如果你发现了gccbug,需要按照README.Bugs说明书的要求来提交gcc的bug

--with-bugurl=http://bugzilla.redhat.com/bugzilla

gcc编译器集合所支持的语言,不过想要编译java等其它语言,需要下载相应的插件

--enable-languages=c,c++,objc,obj-c++,java,fortran,ada,go,lto

路径固定前缀,也就是说gcc所使用到的路径都是/usr打头的,换句话说gcc所用到的文件,都在这个/usr目录下

--prefix=/usr

GCC编译器集合“自带库”所在目录

--libdir=/usr/lib

ubuntu 下才有的:
使用gcc编译带界面的java程序,图形界面底层调用的是ubuntu的基础图形库

--enable-java-awt=gtk

ubuntu 下才有的:
gcc可以编译java,但是java程序运行需要相应的运行环境(最起码要有个java虚拟机jvm
以下信息描述的就是java的运行环境

--with-java-home=/usr/lib/jvm/java-1.5.0-gcj-5-amd64/jre 
--enable-java-home 
--with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-5-amd64 
--with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-5-amd64 
--with-arch-directory=amd64 
--with-ecj-jar=/usr/share/java/eclipse-ecj.jar 
--enable-objc-gc

gcc所运行的cpucpu架构的详细信息,对于应用开发者来说,我们不需要进行详细的了解
这些是做硬件、微电子、编译器开发者应该详细了解的内容

--enable-multiarch 
--disable-werror 
--with-arch-32=i686 
--with-abi=m64 
--with-multilib-list=m32,m64,mx32 
--enable-multilib 
--with-tune=generic
--enable-checking=release
--build=x86_64-linux-gnu

gcc本身的运行环境:cpu:64位intel X86 cpu OS:Linux

--host=x86_64-linux-gnu

gcc编译出的可执行程的运行环境

gcc helloworld.c -o helloworld,helloworld

的运行环境为cpu:64位intel X86 cpu OS:Linux

--target=x86_64-linux-gnu

六、collect2 链接,启动代码

crt1.o、crti.o、crtbegin.o:用于生成程序的“启动代码”,
这三个.o是由GCC编译器集合提供的(由GCC开发者编写的),
crtc/c++ run time, 运行时环境)

启动代码的作用:搭建c/c++的运行环境

crt1.o:汇编写的。

  • 里面的_start是整个程序的开始(入口)
  • main函数是由crt1.o调用
  • c/c++函数运行所需要的栈,是由crt1.o建立的
  • crti.o:在调用main之前,实现C的一些初始化工作,比如全局变量初始化
  • crtbegin.o:在调用main之前,实现c++的一些初始化,比如调用全局构造函数,创建全局对象
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crt1.o 
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crti.o 
 /usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtbegin.o

指定所有库所在路径,后面链接“库”时就到这些路径下寻找,后面讲c函数库时,会介绍-L选项

-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64
-L/lib/../lib64
-L/usr/lib/../lib64
-L/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../.. /tmp/ccN6ZXpL.o

链接自己的.occN6ZXpL.o,是之前as汇编helloworld.c时,所得到的临时.o文件,
我们这里自己的.o只有一个,如果源文件有很多.c的话,对应的这里就会有很多的.o

/tmp/ccN6ZXpL.o

链接libc库,其实libc只是C标准函数库中的一个子库,
libc只包含printf、scanf、strcpy、malloc、time等这些常用函数,

正是因为gcc有自动链接libc这个常用函数库,
所以我们才能在程序中使用这些常用函数,而不会报错说倒找不到这些函数

但是像C标准函数库中的其它函数就不行了,比如使用数学函数,
我们必须自己在gcc时自己加-lm去链接C标准函数库的子库——数学库,比如:

gcc ***.c -o *** -lm

否者无法使用数学库函数,数学库中的函数属于非常用函数,gcc是不会帮你自动链接数学库的。

crtend.o、crtn.o:用于生成扫尾代码,程序运行结束时,做一些扫尾工作,
这两个.o也是由gcc开发者编写的,为了方便描述,我们往往将扫尾代码认为是启动代码的一部分

crtend.o:扫尾做什么?比如调用c++析构函数,释放全局对象的空间
crtn.o:扫尾做什么?比如,接收main函数的返回值并处理

  • 如果程序是裸机运行的,返回值到扫尾代码这里就结束了,裸机时返回值的意义不大
  • 如果程序是基于OS运行的,扫尾代码会将返回值交给OS
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/crtend.o
/usr/lib/gcc/x86_64-redhat-linux/4.8.5/../../../../lib64/crtn.o

简化后的链接过程,链接时,各个模块的顺序很重要

  • collect2:链接程序
  • -dynamic-linker /lib64/ld-linux-x86-64.so.2:动态链接器
  • crt1.o crti.o crtbegin.o:启动代码
  • ccyIcm4A.o:自己程序的.o,这里目前只有一个
  • -lclibc常用C函数库——C标准库的子库
  • crtend.o crtn.o:扫尾代码

七.总结gcc -v显示的详细信息

(1)如果四个过程都有的话
详细信息就是:

gcc配置信息 + cc1信息(预处理+编译) + as信息 + collect2信息

(2)如果只是某个过程的话
比如:

 gcc -S helloworld.i -o helloworld.s -v

详细信息就是:gcc配置信息 + 单个过程的信息


八. 再说说启动代码

(1)启动代码是由“一个汇编文件 + 其它的 .c 文件”构成的,启动代码并不是我们程序员写的

  • 广义上,汇编文件 + 其它的.c文件 都是启动代码。
  • 狭义上,汇编文件 才是启动代码

(2)启动代码的作用

  • 其中的核心作用就是,在内存中建立C程序运行所需的“堆和栈”,堆和栈其实就是程序运行时所需的运行环境之一,
    所谓运行环境,就是程序运行时必须要的支撑,就跟人一样,人活着也需要环境(生活环境/工作环境)的支撑,否则就麻烦了。
  • 程序运行起来后就变成了动态运行的程序——进程,所以程序的运行环境,也被称为“进程环境”,
    进程环境不仅仅只有“堆和栈”,还需要其它的,
    我们在《Linux系统编程/网络编程》第4章,将会详细的介绍什么“进程环境”,所需环境有哪些

(3)我想看启动代码的源码,怎么办?
如果想看启动代码的话,也主要是汇编部分,因为.c部分,会根据程序运行时有无OS支持,会有很大的区别

比如单片机没有OS,它的启动代码.c部分与有OS时启动代码的.c部分有很大区别,
不仅.c,汇编部分也有区别,只不过区别没有那么大而已

.c部分我们就不关心了,如果你想看启动代码汇编部分,完全可以看单片机C程序的启动代码汇编部分

当有OS支持时,编译器提供的启动代码大都会被做成现成的.o,我们是看不到源码的,
基本只有在裸机程序中才能看到启动代码汇编部分的源码,.c源码也能看见

所谓裸机程序就是没有OS支持的程序,比如绝大多数单片机程序就是典型的裸机程序,为什么呢?

因为单片机很少运行OS,由于单片机开发的特殊性,有时候我们往往需要修改启动代码汇编部分,
所以编译器会提供汇编启动代码的源码,所以完全可以通过看单片机的汇编启动代码,
从而了解C的汇编启动代码干了什么事情

在有OS支持时,启动代码是不需要修改的,所以人家才以.o心事提供

讲单片机时,我们会分析单片机C程序的汇编启动代码,所以到时候你就知道它到底做了些什么事情,
它是怎么去建立堆和栈的,建立堆和栈的代码长什么样子,那时你就不会再对启动代码感到神秘了

当然后续课程讲Uboot移植时,由于Uboot也是一个裸机程序,我们也能够看到Uboot的汇编启动代码的源码,你也能知道启动代码到底做了些什么事情,总之对于启动代码不要感到神秘,也没什么可神秘的

不仅C程序,所有高级语言所写的程序,都是需要启动代码的,启动代码要做的事情也都是类似的

当然就算你不了解启代码是咋回事,对于我们开发来说,也不会有什么太大的问题,
但是如果你能了解什么是启动代码,大概做了些什么事情,当然是最好的了,
因为这非常有助于你去理解与之相关的一些其它的深入问题