C语言的经典“Hello World”
#include<stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
在Linux下,当我们使用GCC来编译Hello World时,只使用最简单的命令:(假设源代码文件名为hello.c)
gcc -o hello hello.c
事实上,上述过程分解为4个步骤:
预编译---->编译----->汇编----->链接
1、预编译(主要处理那些源代码文件中的以#开始的预编译命令,比如#include、#define等)
将hello.c文件以及相关头文件stdio.h等被预编译器cpp编译为一个.i文件。
预编译的命令如下:
$ gcc -E hello.c -o hello.i(-E表示只进行预编译)
或者:
$ cpp hello.c >hello.i
主要规则:
(1)将所有的“#define”删除,并且展开所有的宏定义;
(2)处理所有条件预编译指令,比如“#if”,“#endif”等;
(3)处理“#include”预编译指令,将被包含的文件插入到预编译指令的位置;
(4)删除所有的注释;
(5)添加行号和文件名标识,以便于编译时编译器产生调试用的行号 信息;
(6)保留所有的#pragma编译器指令,因为编译器需要使用它们;
经过预编译过后的.i文件不包含任何宏定义,因为所有的宏都已经被展开,并且包含的文件信息也被插入到.i文件中,所以当我们无法判断宏定义是否正确或头文件包含是否正确时,可以查看预编译后的文件来确定问题。
2、编译(核心部分,此过程把预编译处理完的文件进行一系列词法、语法、语义分析及优化后生产相应的汇编代码文件)
编译命令:(用ccl来完成)
$ gcc -s hello.c -o hello.s
得到汇编输出文件hello.s
3、汇编(将汇编代码转为机器可以执行的指令,每一个汇编语句都对应一条机器指令,汇编过程实际上就是根据汇编指令和机器指令的对照表一一翻译就可以了)
汇编命令(用汇编器as来完成):
$ as hello.s -o hello.o
或者:
$ -c hello.s -o hello.o
或者使用gcc命令从C源代码文件开始,经过预编译、编译和汇编直接输出目标文件:
$ gcc -c hello.c -o hello..o
4、链接(将每个源代码模块独立的进行编译,然后按照需要将他们组装起来,这个组装模块的过程就是链接过程)
主要过程包括:空间分配、符号决议、重定位等;
每个模块的源代码文件经过编译器编译成目标文件(Object),目标文件和库一起链接形成最终可执行文件。
目标文件:
包括编译后的机器指令代码、数据、链接时所需要的信息,比如符号表、调试信息、字符串等。目标文件将这些信息按不同的属性,以“节”的形式存储,有时候也叫“段”。
程序源代码编译后的机器指令经常被放在“代码段”;代码段常见的名字“.code”".text";
已初始化的全局变量和局部静态变量数据经常放在“数据段”。数据段名字叫".data"
未初始化的全局变量和局部静态变量一般都放在“.bss”段;(因为未初始化的全局变量和局部静态变量默认值都为0,本来他们也可以被放在".data段",但是因为他们都是0,所以他们在.data段分配空间并存放数据0是没有必要的,程序运行的时候他们的确占内存空间,并且可执行文件必须记录所有未初始化的全局变量和局部静态变量的大小总和,记为.bss段。所以.bss段只是为未初始化的全局变量和局部静态变量预留位置而已,它并没有内容,所以在文件中也不占空间)
extern"C" C++为了与C兼容,在符号的管理上,C++有一个用来声明或定义一个C的符号的“extern C”关键字用法,
强符号与弱符号:
编译器默认函数和初始化了的全局变量称为强符号;
未初始化的全局变量称为弱符号。