概述
关于目标,我们要写个程序。可以有很多用途。但绝大多数情况下,是为了运行。我们运行的目的,不是为了RUN。估计没有哪个人会如此写个函数
1
2
3
|
while
(1){
i++;
}
|
因此,正常的思维逻辑应该是这样的因果关系:
因为我要实现的结果,用计算机更方便,所以我要让计算机来运行使得出现结果,为了让计算机运行,而现有的计算机软件无法实现,或实现不好,所以我需要亲自 教育计算机怎么实现。没错,你就是手持鞭子的人,而计算机就是那个戴着眼罩的驴。但重点不是驴转了多少圈,重点是,有个磨出了你想要的东西。从这个地方, 需要引申个话题。不是随便怎么抽鞭子就可以有结果的。
因此,抽鞭子是有方法和规则的,是受约束的,一鞭子下去把驴抽死了,肯定不是你想要看到的。同时,鞭子在手,不代表事情做完。
对比,程序编写,那么程序编写完了,准确说是录入完了,事情并没有做完。真正要出结果,还有两个步骤“编译”,“执行”。“执行”此处不展开。展开一下“编译”。
这里说的“编译”是个广义的词。包含了“编译和连接”。其实你要记住既有编译,又有链接,只要记住一句话,如下:
C语言的国际标准(好大的招牌,没办法,招牌大有好处,防止别人砸场子,特别是基础知识方面),要求,编译的对象是文件,也就是说,无论你有多少个C文件组成的工程,始终是每个文件独立编译的。那么就是因为这样,所以你的总要有个连接的动作吧。
(注意此处是C语言,一些语言的编译的内涵和此处的不一致,但C语言的编译,和整个计算机领域的编译是比较对应的,这也是学C的好处)。
这里说说,为什么要编译,为什么要连接(其实加强记忆的弱智版说法上面已经给了)。
为什么要编译:
因为机器执行是机器语言,如同那头驴,你和它交流“之乎者也”它是不理睬的。鞭子的动作传达了你的信息,它就很听话。这就是为什么要编译。根本原因就是你 的写的程序,机器看不懂。除非你用机器码描述,二进制的记录在磁盘文件里,这是可以的。否则你始终要编译。包括汇编,别以为如下的文本代码机器能理解
1
2
|
add a0,#1
mov a1,a0
|
由此总结一下,你要让机器实现你的目的,需要保证以下几个事实存在。
1、机器开着。
2、给机器看得懂的东西。
3、让机器根据你的要求,执行。
为了让2能更好的实现。你通常需要以下几个步骤。
1、根据你的目标,设想一下机器执行的方法,并给出计划图。你打算这样或那样的让机器去工作。并形成你的计划图。这就是编程。
2、翻译一下。包括连接。否则C翻译完了,就是一一对应的OBJ文件,你仍然无法实现第3步。
那么以下是一个很多新手容易错误理解的概念。
编程一定要用特定的软件实现。例如,这个语言,一定要用VC2008,那个语言一定要用eclipse。
什么是编程。弱智解释:
编程就是个计划,参考一下工具的使用规范,你编造出一个流程。不要认为编程是基于什么工具下的方式,你只是在借助工具,没有鞭子,就扯裤腰带啊,和你抽驴 有问题吗?驴会因为你用裤腰带而告你虐待动物,或对你很无奈的说一句“HI,虽然很疼,但毕竟不是鞭子,我不会继续前进”。
当然为了有效实现,当你确认好具体工具时,需要依赖工具的规范执行,例如你需要使用C语言的编程的规范,但这个和具体编程软件又没有关系了。如上面说的, 让机器执行的第二个步骤的展开,是,你形成计划,并翻译成机器的语言,你基本的工具是C的编译器,而不是编写你计划的文本工具。
这里简单展开一下编译的工作组成。我的意见是三个阶段:
1、预编译。
2、前端编译。相当于书本里的“分析部分”
3、后端编译。相当于书本里的“综合部分”
先分割2,3,前段编译是做些和具体硬件没有关系的事情。例如,你是要抽驴,或用个二冲程发动机驱动的剪草机,你的计划在实现过程中肯定有些特定对象的东 西。这些就是后端折腾的。但是你的整个计划也有和特定对象没有关系的,这就是前端。如果一个C代码,可以在不同的机器上运行(当然需要重新编译),那么你 的C代码的逻辑(和特定机器无关)的处理,则是前端。落实到具体步骤的执行,则是后端。为什么C容易移植以及广泛的被硬件平台支持,其中一个原因是:C语 言的编译器被广泛使用和深入研究,良好的分割了前端和后端,而硬件厂家,重点放在后端实现上。(所以当JAVA说自己是“与系统无关,跨平台”的语言,C 的前端编译器会很无耻的轻松飘过,因为找骂的事情通常是后端部分,而如果被JAVA忽悠而迷信“与系统无关,跨平台”这句话的程序员,一样会和后端编译那 样,被骂)
那么为什么要独立提出“预编译”因为,C的国际标准中,对于预处理,有明确的保留字,例如#include ,#define之类的。同时,预处理的工作目的不是在编译,在于方便编译。是为了后续实际编译工作对文本做的重新组织。甚至你完全可以很“可耻”的独立 替换掉预编译部分,将C语言构造成另一种语言,而仍然使用C语言的编译器。例如你可以增加一个关键词“class",当出现“class"的描述,你自己 来检测语法,并且,将"class"的一些扩展用法,替换成C语言的实现方式,最终再调用C语言的编译器,这些可以独立区分出来的事情,都属于预编译部 分,其实你虽然提出了一个新语言,但并不是真正的独立语言,你仍然是基于C的。
不过需要非常明确的是,C++不是上述的情况。C++有自己的国际标准和自己的编译系统。但从理论上说。你完全可以根据C++的国际标准,做一套预编译系统,然后调用C的编译器同样实现。至于效率问题,则看你的预编译手段是否高明了。
这里引申一个新手容易犯的理解错误。
头文件是C程序的一部分。
标准只是规定了,根据文件来编译,同时我暂时没有查到一定要用C后缀的名字的约束要求。当然.c和.h一样,已经嵌入到编译软件里,成为一些默认的格式规 则。但这个不能来说明,.h(默认为头文件),属于编译的对象。除非你的.h的书写也是个标准的C语言的代码文件。
头文件,只有在#include时,会被预编译,插入到对应.c文件的对应位置,最终,编译器不会考虑对头文件进行处理。之所以处理它,是因为它的内容, 实际被嵌入到C文件中,此时实际还是对C文件内容进行的编译工作。而不是对.h文件。但这里需要说明一点,现在有一种“预编译”技术。试想,如果很多C文 件都包含相同的头文件,为什么我们不能把这些头文件中,非常独立,或者落到每个C文件内都完全相同的逻辑组织,先一次折腾好呢?但这个只是属于优化的范 畴。和头文件不属于编译对象不是一个范畴。
废话了这么多,是希望大家能理解程序从书写到执行中的必要经历过程。这样可以明确如下的命令。gcc的 。
gcc xx.c
gcc xx.c -o xx
gcc -c XX.c
gcc -c xx.c xx.o
gcc -c xx.c -o xx.o
gcc xx.o -o xx
gcc根据文件后缀名,潜规则了一些操作。没办法,GCC还是很不错的,更重要是,其他编译器也有潜规则。所以你被潜规则是逃离不了的事实,因此,保持良好的心态,“与其默默的忍受被潜规则,不如默默的享受被潜规则”。
下面给出上面的潜规则。
gcc xx.c 。很简单,干活,干什么活,你没告诉他,他就一干到底,把程序变成执行文件。因为你告诉gcc的信息很少。只有一个.c算是潜规则,此时,gcc必然会做 很多。包括了预编译,编译,连接。而且由于你没有告诉生成的程序(可执行的)是什么名字,因此默认的使用a.out。因为linux下,没有规定可执行程 序用什么后缀名。因此,gcc给你的最终输出,起名叫做 xx.exe,会很弱智,也很微软。但GCC也只会给你最终的文件,中间的生成文件均不会给你。会被丢弃掉。比如obj文件。
gcc xx.c -o xx,这里比上面一个命令多了后面的XX,实际是你告诉了更多gcc信息。意思也就变成了,我要我自己的程序名称。此时,就不会出现a.out这个不知到哪来的文件了。最终生成的执行文件,就是 xx。
gcc -c xx.c ,-c表示现在的动作是编译,compile的简写,很容易记得。你记得,-c就是说,我只编译。由于gcc对文件后缀名是敏感的,那么这个时候在gcc 的世界里,认为对象文件,也就是编译的生成文件,用.o表示,此时,就会自动生成一个名为xx.o的文件。
gcc -c xx.c -o xx.o这个就容易理解了。-o实际的含义是强制输出成你想要的名字而已。比如你可以
gcc -c xx.c -o xxx.o,也可以。
gcc xx.o -o xx,前面说过了。gcc有潜规则,如果你是.o文件则是obj,也就是目标文件,那么此时gcc会知道。哦,你让我操作目标文件。那我就链接吧。由此形成执行文件。后面的xx也就是执行文件。
因此可以准确说,如下几个动作是和
gcc xx.c等同的。
$gcc -c xx.c -o xx.o
$gcc xx.o -o a.out
$rm xx.o
这个时候,肯定有人问,大爷键盘不累吗?一句话的事情为什么要分几个命令
去执行。这里我们关注两个事情。
1、多次执行,你可以不使用a.out,而改成你想要的名字,够爽吧。但这样,我们完全可以gcc xx.c -o XX实现。
2、如果你有100个文件组成的程序。先要编译100个文件形成.o,再对100个.o文件进行连接形成执行程序。但如果你只改动了1个C文件显然你需要 重新编译,这个时候,磁盘肯定会骂你。“至于吗?删掉99个文件,再重新折腾一边,当我磁盘是神灯啊。有事没事都擦一擦”。没错,分开写的目的,对于一个 C文件而言没有意义,但那是对于实际大多数工程(包含多个C文件时)就有意义了。可以不要重新对所有文件进行在编译。记得编译和链接的不同,你就能理解 了。
说了这么多,主要是让新手能够实实在在的使用gcc命令,不用过gcc命令,你就不知道make的好处。
下面贴一下两个文件和全套操作步骤建议新手,重头到位执行一边。
前期准备,
确认 GCC存在。ubuntu 下,可以
$gcc -version
大多数linux自带make。
确认你具备一个目录下的读写操作权限。
1
2
3
4
|
$
mkdir
learn_make
$
cd
learn_make
$gedit learn_make.c
//
注意我用的是scribes learn_make.c ,你可以在 ubuntu下
sudo
apt-get
install
scribes,为什么用scribes参见下面。
|
如下内容
1
2
3
4
5
6
7
|
#include <stdio.h>
#include "learn_make.h"
int
main(
int
argc,
char
*argv[]){
//你先别问main的函数有几种写法,为什么这么写
printf
(
"%sn"
,TEST_GCC_CMD);
//一个打印对命令"%sn"是一种格式化的规则,TEST_GCC_CMD在头文件中定义了。
return
0;
//main 的正常返回为0。main对返回值对于程序调用程序时,还是很有意义的,养成别随便返回的好习惯
}
|
//编辑完毕后保存关闭,退出
1
|
$gedit learn_make.h
|
如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#ifndef _LEARN_MAKE_H_ //我自己参考其他软件写法的习惯,对于头文件的防重复编译的定义,
//采用文件命全大写,前后加_的方式实现
#define _LEARN_MAKE_H_
/*
如果你想知道为什么要
#ifndef XXXX
#define XXXX
你尝试这个头文件里,加上#include "learn_make.h"并尝试自己手工,
将#include对应的文件内容COPY到这个位置,如同预处理那样做的,
你再简单的分析一下,是否会无限循环的加载文件内容,就可以理解了。
*/
#define TEST_GCC_CMD "test gcc cmd"
#endif //_LEARN_MAKE_H_
//这里的写法只是让你知道这个#endif对应那个#if #ifdef #ifndef等等存在作用域的预处理工作
|
//编辑完毕后,保存关闭,退出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
$
gcc
learn_make.c
$
ls
$.
/a
.out
$
rm
a.out
$
gcc
learn_make.c -o haha
$
ls
$.
/haha
$
rm
haha
$
gcc
-c learn_make.c
$
ls
$
gcc
learn_make.o -o haha_by_o
$
ls
$.
/haha_by_o
$
rm
learn_make.o
$
rm
haha_by_o
$
gcc
-c learn_make.c -o heihei
$
ls
$
gcc
heihei -o haha_by_heihei
//
你可以尝试
gcc
heihei.o -o haha_by_heihei ,书上永远教对的,我喜欢说错的事情。
$
ls
$.
/haha_by_heihei
|
最后
以上就是奋斗夕阳为你收集整理的linux下的C编程和makefile的使用的全部内容,希望文章能够帮你解决linux下的C编程和makefile的使用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复