一、先说一下GCC
我们都知道,一个.c文件写好后,是需要一系列加工才可以被计算机执行的,其中发生打事情大致如下:
源代码(.c)-预编译->头文件和宏扩展-编译->汇编码(.s)-汇编->目标码(.o)-链接->可执行代码(a.out)
相信代码大家基本都会写了,下面我们来了解一下代码的加工过程
使用命令简单写一个.c文件:vi hello.c
你可以直接gcc helloworld.c 然后./a.out直接运行并出结果,但这样未免太过于不细节,我们完全意识不到这个.c文件到底经历了什么,以及我们后期想要多个任务合并到一起时,也不知从何下手。
可以用以下命令,来一步步探究代码的加工,并对其适时加以改进。
gcc -E hello.c -o hello.i 预编译(编译预处理)
-o:输出,后跟一个输出生成的新文件。
-E:预处理选项。
使用此命令含义:对hello.c源文件进行预处理,将stdio.h文件里所有内容拷贝到hello.c中,或者对一些定义的宏进行宏替换之类的这些工作之后生成的一个新文件hello.i文件。
gcc -S hello.i 获得汇编代码(hello.s)
-S:将高级语言文件编译成汇编语言文件,即将预处理输出文件hello.i汇编成hello.s文件,生成汇编代码,汇编语言可看做是一种低级语言,十分接近于机器码的实现,通过汇编过程转换成机器指令,汇编过程实际上就是将汇编语言翻译成为了机器码,计算机就可以读懂。
gcc -c hello.s 获得目标代码(hello.o)
-c:汇编,将自己的源文件翻译成计算机能够识别的可执行程序,即将上一步的汇编语言文件汇编成机器语言文件。
gcc hello.o -o hello 获得可执行代码(hello)
./hello 运行可执行代码
./代表当前目录下
由此,你写的C语言代码,最终转化成了计算机可以读懂的形式,然后执行出你命令的效果。
下面是一些开发时用的命令,用来规避一些错误,以及做一些指定
gcc -Wall xxx.c
-Wall:产生全部警告。
有时候你可能忘记给函数赋数据类型,这样的函数写上去后,编译器可能不会产生警告,因为他会默认给你加上int类型。
但是在实际开发时,这样的代码是极不稳定的,因为可能需要此函数返回的类型并不是int类型,会导致后面大错特错,此时你就需要将这个警告反馈给你,以便及时修改,所以可采用-Wall。
gcc -Werror xxx.c
-Werror:将警告作为错误处理。
基本与上同理,比如,你在一个函数里申请了一块局部变量并且函数的功能是要返回这个局部变量的地址,但此时这个局部变量已经在函数返回调用时刻消失了,那么将返回一个野指针。
后面将因为这个野指针而大错特错,而且对于这个操作,编译器只会警告,还是会将程序生成,也可以执行,但是此时的执行结果很可能就偏离正确结果了。
采用-Werror后,这样的警告将会成为错误,导致根本无法生成可执行文件,一定要把这个错误改了才可以正常生成。
gcc -x c++ cpp.c -lstdc++ -o cpp (即使后缀为.c,但因被指定为c++,将按照c++的规矩执行)
-x:指定源代码的语言(会忽略要执行文件的扩展名,按照指定的语言执行)
-O0/O1/O2/O3: 指定优化等级
O0不优化,缺省O1优化,O2强调速度,牺牲空间(大)换时间(短),O3强调体积,牺牲时间(长)换空间(小)
二、说一下头文件
1.头文件卫士
例如:
#ifndef __XXX_
#define __XXX_
...
#endif
效果是防止重定义的。
一个头文件可能会被多个源文件包含,写在头文件里的函数定义也会因此被预处理器扩展到多个包含该头文件的源文件中,并在编译阶段被编译到等多个不同的目标文件中,这将导致链接错误:multiple definition,多重定义。
gcc -I<头文件的附加搜索路径>
此命令是用来找头文件用的,比如,你自己写了一些头文件,如:#include"my.h",此时可以正常引用,但如果你不小心写成了#include<my.h>,就需要给编译器指定你这个头文件的路径,不然编译器只会去寻找默认的路径。
头文件中,尖括号和引号的区别:
#include <my.h>
先找-I指定的目录,再找系统目录。
#include "my.h"
先找-I指定的目录,再找当前目录,最后找系统目录。
2.预处理指令
#include - 将指定的文件内容插至此指令处
#define - 定义宏
#undef - 删除宏
#if - 如果
#ifdef - 如果宏已定义
#ifndef - 如果宏未定义
#else - 否则,与#if/#ifdef/#ifndef配合使用
#elif - 否则如果,与#if/#ifdef/#ifndef配合使用
#endif - 结束判定,与#if/#ifdef/#ifndef配合使用
#error - 产生错误,结束预处理
#warning - 产生警告,继续预处理
#line - 指定行号
#pragma - 设定编译器的状态或者指示编译器的操作
#pragma GCC dependency 被依赖文件
#pragma GCC poison 语法禁忌
#pragma pack(按几字节对齐:1/2/4/8)
#pragma pack() - 按缺省字节数对齐 默认四字节对齐
具体实现自行实验,需要多敲代码体会
3.预定义宏
无需自行定义,预处理器会根据事先设定好的规则将这些宏扩展成其对应的值。
__BASE_FILE__: 正在被处理的源文件名
__FILE__: 所在文件名
__LINE__: 所在行的行号
__FUNCTION__: 所在函数的函数名
__func__: 同__FUNCTION__
__DATE__: 处理日期
__TIME__: 处理时间
__INCLUDE_LEVEL__: 包含层数,从0开始
8.环境变量
在进程向下文中保存的一些数据:键(功能,是什么)=值(内容)。
env
C_INCLUDE_PATH
C语言头文件的附加搜索路径,相当于-I选项。
CPATH
同C_INCLUDE_PATH
CPLUS_INCLUDE_PATH
C++语言头文件的附加搜索路径,相当于-I选项。
LIBRARY_PATH
链接库路径
LD_LIBRARY_PATH
加载库路径
下一篇说库,可以先理解这些再往下继续。