linux下的C编程和makefile的使用

来源:互联网 发布:amd处理器优化 编辑:程序博客网 时间:2024/04/30 07:41
   关于目标,我们要写个程序。可以有很多用途。但绝大多数情况下,是为了运行。我们运行的目的,不是为了RUN。估计没有哪个人会如此写个函数
?
1
2
3
while(1){
       i++;
    }
        没错,什么也不干。就是为了耗电,和证明自己的电脑可以用。特地加上i++的目的,还是要告诉编译器,“HI,别把我这段代码给省略了”.
    因此,正常的思维逻辑应该是这样的因果关系:
    因为我要实现的结果,用计算机更方便,所以我要让计算机来运行使得出现结果,为了让计算机运行,而现有的计算机软件无法实现,或实现不好,所以我需要亲自 教育计算机怎么实现。没错,你就是手持鞭子的人,而计算机就是那个戴着眼罩的驴。但重点不是驴转了多少圈,重点是,有个磨出了你想要的东西。从这个地方, 需要引申个话题。不是随便怎么抽鞭子就可以有结果的。
    因此,抽鞭子是有方法和规则的,是受约束的,一鞭子下去把驴抽死了,肯定不是你想要看到的。同时,鞭子在手,不代表事情做完。
    对比,程序编写,那么程序编写完了,准确说是录入完了,事情并没有做完。真正要出结果,还有两个步骤“编译”,“执行”。“执行”此处不展开。展开一下“编译”。
    这里说的“编译”是个广义的词。包含了“编译和连接”。其实你要记住既有编译,又有链接,只要记住一句话,如下:
    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
$mkdirlearn_make
 $cdlearn_make
 
 $gedit learn_make.c  //注意我用的是scribes learn_make.c ,你可以在 ubuntu下sudoapt-get installscribes,为什么用scribes参见下面。

如下内容

?
1
2
3
4
5
6
7
#include <stdio.h>
#include "learn_make.h"
  
int main(int argc,char*argv[]){//你先别问main的函数有几种写法,为什么这么写
    printf("%s\n",TEST_GCC_CMD);//一个打印对命令"%s\n"是一种格式化的规则,TEST_GCC_CMD在头文件中定义了。
    return0;//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
    $gcclearn_make.c
    $ls
    $./a.out
    $rma.out
    $gcclearn_make.c -o haha
    $ls
    $./haha
    $rmhaha
    $gcc-c learn_make.c
    $ls
    $gcclearn_make.o -o haha_by_o
    $ls
    $./haha_by_o
    $rmlearn_make.o
    $rmhaha_by_o
    $gcc-c learn_make.c -o heihei
    $ls
    $gccheihei -o haha_by_heihei //你可以尝试gccheihei.o -o haha_by_heihei ,书上永远教对的,我喜欢说错的事情。
    $ls
    $./haha_by_heihei

       对于新手。我可以这么说,如果你不实际把上面操作都敲一边,你就没有学习make的必要了。因为鼓励你学习make的一个动力就是上面的这些繁琐的事情,可以脚本化的简单实现。
0 0