您的位置:首页 > 编程语言

inux 下 C 编程和make的方法 (二、基础准备:编译与连接&GCC)

2012-11-11 22:27 471 查看
前期最基本的知识:

关于目标,我们要写个程序。可以有很多用途。但绝大多数情况下,是为了运行。我们运行的目的,不是为了RUN。估计没有哪个人会如此写个函数

1
while
(1){
2
i++;
3
}
没错,什么也不干。就是为了耗电,和证明自己的电脑可以用。特地加上i++的目的,还是要告诉编译器,“HI,别把我这段代码给省略了”.

因此,正常的思维逻辑应该是这样的因果关系:

因为我要实现的结果,用计算机更方便,所以我要让计算机来运行使得出现结果,为了让计算机运行,而现有的计算机软件无法实现,或实现不好,所以我需要亲自教育计算机怎么实现。没错,你就是手持鞭子的人,而计算机就是那个戴着眼罩的驴。但重点不是驴转了多少圈,重点是,有个磨出了你想要的东西。从这个地方,需要引申个话题。不是随便怎么抽鞭子就可以有结果的。

因此,抽鞭子是有方法和规则的,是受约束的,一鞭子下去把驴抽死了,肯定不是你想要看到的。同时,鞭子在手,不代表事情做完。

对比,程序编写,那么程序编写完了,准确说是录入完了,事情并没有做完。真正要出结果,还有两个步骤“编译”,“执行”。“执行”此处不展开。展开一下“编译”。

这里说的“编译”是个广义的词。包含了“编译和连接”。其实你要记住既有编译,又有链接,只要记住一句话,如下:

C语言的国际标准(好大的招牌,没办法,招牌大有好处,防止别人砸场子,特别是基础知识方面),要求,编译的对象是文件,也就是说,无论你有多少个C文件组成的工程,始终是每个文件独立编译的。那么就是因为这样,所以你的总要有个连接的动作吧。

(注意此处是C语言,一些语言的编译的内涵和此处的不一致,但C语言的编译,和整个计算机领域的编译是比较对应的,这也是学C的好处)。

这里说说,为什么要编译,为什么要连接(其实加强记忆的弱智版说法上面已经给了)。

为什么要编译:

因为机器执行是机器语言,如同那头驴,你和它交流“之乎者也”它是不理睬的。鞭子的动作传达了你的信息,它就很听话。这就是为什么要编译。根本原因就是你的写的程序,机器看不懂。除非你用机器码描述,二进制的记录在磁盘文件里,这是可以的。否则你始终要编译。包括汇编,别以为如下的文本代码机器能理解

1
add
a0,#1
2
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的。

gccxx.c

gccxx.c-oxx

gcc-cXX.c

gcc-cxx.cxx.o

gcc-cxx.c-oxx.o

gccxx.o-oxx

gcc根据文件后缀名,潜规则了一些操作。没办法,GCC还是很不错的,更重要是,其他编译器也有潜规则。所以你被潜规则是逃离不了的事实,因此,保持良好的心态,“与其默默的忍受被潜规则,不如默默的享受被潜规则”。

下面给出上面的潜规则。

gccxx.c。很简单,干活,干什么活,你没告诉他,他就一干到底,把程序变成执行文件。因为你告诉gcc的信息很少。只有一个.c算是潜规则,此时,gcc必然会做很多。包括了预编译,编译,连接。而且由于你没有告诉生成的程序(可执行的)是什么名字,因此默认的使用a.out。因为linux下,没有规定可执行程序用什么后缀名。因此,gcc给你的最终输出,起名叫做xx.exe,会很弱智,也很微软。但GCC也只会给你最终的文件,中间的生成文件均不会给你。会被丢弃掉。比如obj文件。

gccxx.c-oxx,这里比上面一个命令多了后面的XX,实际是你告诉了更多gcc信息。意思也就变成了,我要我自己的程序名称。此时,就不会出现a.out这个不知到哪来的文件了。最终生成的执行文件,就是xx。

gcc-cxx.c,-c表示现在的动作是编译,compile的简写,很容易记得。你记得,-c就是说,我只编译。由于gcc对文件后缀名是敏感的,那么这个时候在gcc的世界里,认为对象文件,也就是编译的生成文件,用.o表示,此时,就会自动生成一个名为xx.o的文件。

gcc-cxx.c-oxx.o这个就容易理解了。-o实际的含义是强制输出成你想要的名字而已。比如你可以

gcc-cxx.c-oxxx.o,也可以。

gccxx.o-oxx,前面说过了。gcc有潜规则,如果你是.o文件则是obj,也就是目标文件,那么此时gcc会知道。哦,你让我操作目标文件。那我就链接吧。由此形成执行文件。后面的xx也就是执行文件。

因此可以准确说,如下几个动作是和

gccxx.c等同的。

$gcc-cxx.c-oxx.o

$gccxx.o-oa.out

$rmxx.o

这个时候,肯定有人问,大爷键盘不累吗?一句话的事情为什么要分几个命令

去执行。这里我们关注两个事情。

1、多次执行,你可以不使用a.out,而改成你想要的名字,够爽吧。但这样,我们完全可以gccxx.c-oXX实现。

2、如果你有100个文件组成的程序。先要编译100个文件形成.o,再对100个.o文件进行连接形成执行程序。但如果你只改动了1个C文件显然你需要重新编译,这个时候,磁盘肯定会骂你。“至于吗?删掉99个文件,再重新折腾一边,当我磁盘是神灯啊。有事没事都擦一擦”。没错,分开写的目的,对于一个C文件而言没有意义,但那是对于实际大多数工程(包含多个C文件时)就有意义了。可以不要重新对所有文件进行在编译。记得编译和链接的不同,你就能理解
了。

说了这么多,主要是让新手能够实实在在的使用gcc命令,不用过gcc命令,你就不知道make的好处。

下面贴一下两个文件和全套操作步骤建议新手,重头到位执行一边。

前期准备,

确认GCC存在。ubuntu下,可以

$gcc-version

大多数linux自带make。

确认你具备一个目录下的读写操作权限。

1
$
mkdir
learn_make
2
$
cd
learn_make
3
4
$gedit
learn_make.c//注意我用的是scribeslearn_make.c,你可以在ubuntu下
sudo
apt-get
install
scribes,为什么用scribes参见下面。
如下内容

1
#include
<stdio.h>
2
#include
"learn_make.h"
3
4
int
main(
int
argc,
char
*argv[]){
//你先别问main的函数有几种写法,为什么这么写
5
printf
(
"%s\n"
,TEST_GCC_CMD);
//一个打印对命令"%s\n"是一种格式化的规则,TEST_GCC_CMD在头文件中定义了。
6
return
0;
//main
的正常返回为0。main对返回值对于程序调用程序时,还是很有意义的,养成别随便返回的好习惯
7
}
//编辑完毕后保存关闭,退出

1
$gedit
learn_make.h
如下:

01
#ifndef
_LEARN_MAKE_H_//我自己参考其他软件写法的习惯,对于头文件的防重复编译的定义,
02
//采用文件命全大写,前后加_的方式实现
03
#define
_LEARN_MAKE_H_
04
/*
05
如果你想知道为什么要
06
#ifndef
XXXX
07
#define
XXXX
08
你尝试这个头文件里,加上#include
"learn_make.h"并尝试自己手工,
09
将#include对应的文件内容COPY到这个位置,如同预处理那样做的,
10
你再简单的分析一下,是否会无限循环的加载文件内容,就可以理解了。
11
*/
12
#define
TEST_GCC_CMD"testgcccmd"
13
#endif
//_LEARN_MAKE_H_
14
//这里的写法只是让你知道这个#endif对应那个#if
#ifdef#ifndef等等存在作用域的预处理工作
//编辑完毕后,保存关闭,退出

01
$gcc
learn_make.c
02
$
ls
03
$./a.out
04
$
rm
a.out
05
$gcc
learn_make.c-ohaha
06
$
ls
07
$./haha
08
$
rm
haha
09
$gcc
-clearn_make.c
10
$
ls
11
$gcc
learn_make.o-ohaha_by_o
12
$
ls
13
$./haha_by_o
14
$
rm
learn_make.o
15
$
rm
haha_by_o
16
$gcc
-clearn_make.c-oheihei
17
$
ls
18
$gcc
heihei-ohaha_by_heihei//你可以尝试gccheihei.o-ohaha_by_heihei,书上永远教对的,我喜欢说错的事情。
19
$
ls
20
$./haha_by_heihei
对于新手。我可以这么说,如果你不实际把上面操作都敲一边,你就没有学习make的必要了。因为鼓励你学习make的一个动力就是上面的这些繁琐的事情,可以脚本化的简单实现。

转自:http://my.oschina.net/luckystar/blog/67071
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐