Linux环境C语言编译与头文件等知识点小结

给你一囗甜甜゛ 提交于 2019-12-07 08:37:52

一、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)预处理) ,里面有很详细的介绍。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!