一、C语言的有关文件类型:
虽然,在Linux下“一切皆文件”,并且文件类型这个概念不是那么重要,一个“.c”文件可以用gcc编译,一个“.abcdef”文件照样可以用gcc编译。但是有类型的文件毕竟比无类型的文件更加具有直观性,所以我们还是得说说这个文件类型。
C语言的有关文件类型如下所示:
.c/*源代码文件*/
.h/*C语言头文件*/
.i/*经过预处理之后的源代码文件*/
.s/*汇编代码文件*/
.o/*目标代码文件(二进制机器指令文件)*/
.a/*静态对象库文件*/
.so/*共享(动态)对象库文件*/
一个程序的编译到运行gcc file.c执行了什么?其文件类型有何变化?如下图所示,最终我们只能看到a.out这个可执行文件:
二、gcc的有关选项参数与生成文件:
如果直接gcc file.c,就只会生成a.out执行文件,如果我们想看一下一个C语言文件从预处理到连接过程中各个文件的内容该怎么办?我们可以给gcc加上指定选项,进行指定操作,而不是由编译器直接生成a.out。首先说说这里会用到的重要的选项,如下所示:
-o/*指定目标文件名*/
-E/*只进行预处理,不编译*/
-c/*只编译,不链接*/
-S/*汇编生成.s文件*/
-Wall/*显示所有警告,代码较长时最好加上*/
我们来测试一下:
(1)最初,我们只有一个main.c文件,我们使用“-E”参数生成了main.i文件(此处要加一个“-o main.i”,否则不会生成文件,只会将生成文件的内容在屏幕上输出一下,因为预处理的文件内容过大)
我测试用的代码如下:
#include<stdio.h>
int main(void)
{
return 0;
}
预处理把stdio.h中不管有关没关的内容都进了main.i文件之中,如图所示(总共853行):
(2)、接着,我们用“-S”参数编译(狭义的编译),只把有用的预编译的函数加以处理,生成汇编代码文件,汇编代码也不算太长,不算太难懂,但相比于C代码就长多了,也不好读,如下所示:
(3)、最后我们使用”-c”参数生成了main.o文件,由于该文件是二进制机器代码文件所以我们看到的ASCII形式的文件如下图所示:
似乎是乱码??其实不是,只不过二进制代码中0太多,ASCII码显示的@字符就有点多,我们可以在Vim命令行模式下用“%!xxd”将ASCII形式的文件转换成二进制文件(16进制表示形式)来观察,如下所示(这个就似乎有那么点味道了,机器代码确实比C语言和汇编要长很多,而且基本读不懂):
(4)、最后生成的a.out文件如果要用main.o加选项的方式实现,就必须知道动态库的位置,这里暂时不做演示说明,因为生成的a.out(或者指定名字为main)文件与man.o形式上差不了太多,都是二进制形式。
三、C语言的有关宏定义:
C语言中有很多宏定义,对于一些测试有很好的帮助,如下所示:
__LINE__ /*显示所在行,%d输出*/
__FILE__ /*当前文件名,%s输出*/
__DATE__ /*当前日期,%s输出*/
__TIME__ /*当前时间,%s输出*/
__FUNCTION__ /*所在函数名,%s输出*/
我们做个小测试:
(1)、代码:
# include<stdio.h>
void test_one()
{
printf("function name is:%s\n",__FUNCTION__);
}
void test_two()
{
printf("function name is:%s\n",__FUNCTION__);
}
int main()
{
test_one();
test_two();
printf("function name is:%s\n",__FUNCTION__);
printf("Line is:%d\n",__LINE__);
printf("Date is:%s\n",__DATE__);
printf("Time is:%s\n",__TIME__);
return 0;
}
(2)、测试结果:
四、C语言有关预处理指令:
最后我们说一下C语言的运处理的指令:
#include/*将指定的文件插入#include的位置*/
#include_next/*与#include一样,只是从当前目录之后的目录查找,类似于#include"",但#include""只能查找指定目录一级*/
#define/*定义宏*/
#undef/*删除宏定义*/
/*条件编译的几个预处理指令,结合使用*/
#ifdef/*判定宏是否定义*/
#ifndef/*判定宏是否未定义*/
#if/*if判定*/
#elif/*else if判定*/
#else/*else判定*/
#endif/*结束判定*/
#error/*产生错误挂起预处理程序*/
#waring/*产生一个警告*/
#pragma/*用来提供额外信息的标准方法,用来指定平台*/
(1)、条件编译(自定义头文件):
在系统内和中,头文件的定义都采用条件编译的凡是来编写,我们的自定义文件也是一样(为了防止头文件重复包含),格式如下:
#ifndef HEADFILE_H_
#define HEADFILE_H_
......
#endif
对于#ifdef、#undef、#define,则可以使我们在对系统的宏定义重定义时不会出错。
(2)、测试一下#error和#waring:
①不会有任何警告语错误:
②版本过高,发出警告,但编译成功:
③版本过低,发出错误信息,编译失败:
(3)、#pragma有三种用法:
①#pragma GCC dependency "filename"
/*当其它文件filename比当前文件新时产生一个警告*/
②#pragma GCC posion word
/*将某个单词word设为“毒药”,不允许使用,当代码中有该word时,预处理就会产生一个错误。比如:pragma GCC posion goto,禁止使用goto这种太过古老得且会致使代码逻辑混乱的关键字*/
③、第三种用法可参照我的内存地址对齐再学习(# pragma pack(n)预处理) ,里面有很详细的介绍。