linux 下 C 编程和make的方法 (三、工程文档的组织)

中山野鬼 发布于 2012/03/28 17:15
阅读 3K+
收藏 25
    一些新手搞不清楚工程,和源代码,C文件,头文件的区别。这里特地为新手说明一下:
    无论你是否写过程序。你从用过软件。你会发现很少一个软件就一个文件。你可以在window下看一下某个具体软件的位置,并在这个位置打开文件夹,会发现有很多文件。从设计软件或程序的开发角度也是一样的,一个程序很多情况下,除非足够简单,你只用一个C文件即可。例如:
int main(int argc,char *argv[]){
    return argc;
}

这个文件你甚至都不需要#include <stdio.h>。

但如果你想打印个信息在屏幕上,如”hello world",则你需要调用别人帮你做好的库函数,例如printf,而此时,虽然即便你还是一个C文件可以生成程序,但是你已经用到另外两个文件。一个是头文件 stdio.h。一个是存放printf设计实现的库函数。他们在哪这不是目前关系的,但至少你的设计中包含了不止一个东西。此时,你无法用一个源代码(如你上面自己写的那个C文件)来描述你操作的范围。此时这个整体就是工程。工程是个抽象的名词,和系统一样,很抽象。


    需要说明,文档组织形式,没有什么国际标准。往往自己习惯的,就是最好的(基于这种习惯能保证你的工作效率)。这里先介绍一下我喜欢的工程组织形式,一个目录下至少存在这样几个子目录。
 src  ; inc ; doc ;obj;bin
    必要时,还需要有asm,output,input,lib
    src,很简单,对应里面存在的是c文件。和inc分离,是因为可能存在asm,或非C代码的程序文本,非C代码的程序文本独立使用一个目录组织,但C的 编译器使用外部其他语言编写的模块时,还是需要接口说明,这些说明往往也是放在.h里。因此分割为src和inc,这是个好注意,不是我发明的。
    inc,里面存放的是.h文件,或其他预编译时有效的文件
    doc,里面存放的是说明文档。文档就是文档。和工程的模块编译执行,毛关系没有。你大可以在转移工程内容时,脱离掉doc。分类出一个doc目录,方便 实际查询资料和代码管理。例如版本控制程序,多半是用递增方式维护的,虽然有不同版本,但是是将两个版本的差异进行保存。而文档,通常是流水方式,依次记 录过程中对代码的调整历史,这类资料独立出一个目录是有必要的。
    obj,里面存放的是.o文件,这个不用说了,
      
$rm obj/* 
       是多么的方便,如果你希望rebuild all的时候。
    bin,里面存放的是静态库,动态库,执行文件,准确说是连接器的输出文件存放的地方,通常为了方便寻找到你折腾半天所想要的最终生成的东西。例如你编译 了半天,希望结果的文件能远程加载到SD卡里,open一个bin的专门目录,比在一堆文件中求索哪个才是你需要的要方便很多。当然这里的所谓库,都是由 当前这个总目录下生成的库文件,而不是别人给你的,别人给你的,通常建议,你增加个lib目录,或增加环境配置,这另谈。这里需要补充纠正一个观点,静态 库,并不是实际的连接工作。实际上是个归档的工作。将一堆obj归档到一个文件中。通常使用ar命令。但广义的说连接,这种归档整理的工作也算是一个,这 是野鬼版的胡搅蛮缠。
    asm,里面都是汇编源码文件,实际是很多需要汇编实现的函数保存的文件。大多数人不需要。但我的工作经常用到手写汇编没办法,单片机,ARM,DSP都折腾过工程级的开发,所以没办法,习惯了src,asm分离。
    input,output,一个里面存放的是程序执行时的输入资料,一个存放的是程序执行时的输出资料。这个在很多工程里,并不存在,只是我的工作经常遇 到大批量的输入数据资料,来验证代码的正确性和有效性。此时将输入输出归到子目录里,只是方便处理而已。同时,对于程序设计而言,也比较好规范出一个习 惯,对输入输出的接口有个比较规范的组织形式。不然,代码执行,总是在当前目标下查找资料,或者在环境变量path下查找,毕竟不能解决所有问题。

    针对learn_make,我们开始做如下调整。

$mkdir src
$mkdir inc
$mkdir obj
$mkdir bin


至少先这4个。能简单的别复杂了。
$mv learn_make.c src/
$mv learn_make.h inc/
$rm *    
这里会提示说,有些是目录,无法删除。这是好事情,我们需要将learn_make目录下的文件删除干净,目录不动。

    未来每个文件都会有对应当前目录下的具体存放位置,通过我上面的理由做出的约束规范。后期这有个好处,C语言和其他语言一样,希望代码能够复用,尽可能的 让模块可以在不同工程里均有效实现。而C语言的目标是一次编写,到处编译(别迷信“一次编译,到处运行”这种广告用语,和街头的“老军医”没什么区别),

     因此C语言希望C的源代码,按照C的文本文件为最小单元,能够在不同工程里复用,因此通过目录,子目录的方式,可以有效提高操作者后期代码文本文件的组织能力(其实就是源代码,但希望能加强新手对源代码的认知,它只是个按照语言规则书写的文本文件)。

       我说了,我不指望我的资料可以提高新学者的智商;我的目的是,提高新学者管理组织工程的能力,使得当工程规模变大时,新学者可以相对其他人更好的驾驭工程。在你智商不明显高出竞争者时,你提高复杂事务的控制能力,是一个击败对手的比较可行的办法。

    现在我们在learn_make下,运行gcc,看看有多少累。先说下,为什么在learn_make目录下执行gcc。因为除了这,你在那执行都麻烦。 你要在src里,当你的输出和引用头文件,都需要退出本目录,到上一级目录后,再进入对应子目录,既然一堆孩子都争吵希望gcc在自己目录下执行,那干脆 一碗水端平,老爸自己占便宜得了。
$gcc src/learn_make.c   
 哈,这里肯定有个错误,因为learn_make.h我们移动到inc目录下了,说错误,是希望引出一个gcc的参数,-Idirectory。 同时也建议新手,就是错误,也要执行一次,当屏幕上,先存在错误提示,后出现正确提示时,这种差异,会刺激你的大脑,快速的记住知识。想迅速快速入门,那么最好你就不停的跳进我的坑里,看看坑是什么模样的,以后才有认识真正的陷阱的能力。

    需要补充说明的是,gcc对参数是大小写区分的,-c和-C是不一样的,-i和-Idirectory也是不一样的。这里我特意使用了gcc参考资料的描 述,而没有使用  -I的描述是希望新学者注意,目录名和I是没有空格的。他们组成了一个新单词。此处小写的directory是抽象的描述你希望表达的目录。如果你存在多 个目录里面都有头文件需要引用,假设当前目录下,存在inc1, inc2两个放头文件的子目录,那么你需要  -Iinc1 -Iinc2。

$gcc -Iinc src/learn_make.c
$ls
  注意下,现在显示的是/learn_make目录。
    一切又正常了。但没有达到我们的要求。因为当前目录下,有了个a.out。我们希望a.out存放在bin下。你完全可以继续这样执行。
  
$mv a.out bin/
$bin/a.out //此处是调用bin下的a.out执行。


但确实够“弱智”的。可以考虑修改一下特定输出目标。
   
$gcc -Iinc src/learn_make.c -o bin/learn_make
$ls bin //查看bin目录
$bin/learn_make
  很爽吧,当前目录下,很干净,而且各自文件都归入到自己的位置。
    但是生成的.o文件,是值得保留的。这样可以提高效率。因此我们需要继续调整方法
$gcc -Iinc -c src/learn_make.c -o obj/learn_make.o
$ls obj
记得虽然你指定了目标文件,learn_make.o。但是如果没有-c, gcc会生成一个learn_make.o的可执行文件(这包含了链接的动作),而不是你想要的obj。

    有些所谓高手说我这是废话,资料上不是说的清清楚楚吗?但是就是这么清楚,这样笔误仍然会导致一个.o的出现,后果很严重。这是个很大的“陷阱”所以我要反复弱智的说一下。   

$gcc obj/learn_make.o -o bin/learn_make



     这里不需要 -Iinc了。因为需要inc目录是因为你的c文件需要#include "learn_make.h",在预处理时,需要在合适的位置(目录里)找到对应头文件,此时是对obj的连接工作,和头文件已经没有任何关系了。
$ls bin
$bin/learn_make    
如果你现在还没有欲望学习make的话,不妨修改一下,learn_make.c的如下语句
   
#include <stdio.h>
#include "learn_make.h"
#include "learn_make1.h"
#include "learn_make2.h"
#include "learn_make3.h"
int main(int argc,char *argv[])
    printf("%s\n%s\n%s\n%s\n",\
TEST_GCC_CMD,TEST_GCC_CMD1,TEST_GCC_CMD2,TEST_GCC_CMD3);
 	 
    return 0;
}
//以上是src/learn_make.c的文件
//以下是learn_make/inc1/learn_make1.h的内容
#ifndef _LEARN_MAKE_1_H_
#define _LEARN_MAKE_1_H_
#define TEST_GCC_CMD1 "test gcc cmd1"
#endif// _LEARN_MAKE_1_H_
//同样,你在learn_make/inc2 ;learn_make/inc3里也创建类似的头文件,方便主函数调用我不一一展开 


  如上,类似learn_make.h的方式,编辑learn_make1.h, learn_make2.h, learn_make3.h, 分别存放到learn_make/下的新目录inc1,inc2,inc3下。
    你为了保障你的编译可以通过,你得这写。
    $gcc -Iinc -Iinc1 -Iinc2 -Iinc3 -c src/learn_make.c -o obj/learn_make.o

    “很长,很暴力”。新手会说“你弱智啊”,为什么要搞这么多inc 目录。恩,这个例子很弱智,但表现了一些大型工程中会遇到的典型问题。

     大型工程,首先是分组实现的,不同组的代码会在不同目录下,这样方便管理。同时你可能引用第三方的库,需要依赖第三方的头文件。但这些头文件你都不方便放到glibc库的头文件或系统库的头文件目录下,因此你不能用

#include <XXX.h>

的方式实现。你也不可能因为目录的不同用绝对路径实现例如

#include "/usr/src/learn_make/inc/learn_make.h"
  这是很不开源的做法。这样意味着,使用你代码的人必须重现你的全部工作环境。你可能会说“关我屁事!”,可惜这个人说不定就是你自己呢。

     所以C语言设计,千万不要将文件的路径问题,在代码中固化下来。与人与己都是有利的。相对路径会好点,例如,你也潜规则大家,默认C文件在src下,头文件在inc下,而且src和inc在同一个目录下,则可以

#include "../inc/learn_make.h"


   但这个问题治标不治本,同时不方便后期的源代码管理。所以还是老实的用-Idirectory吧。废话这么多,是希望把你说晕了,好跳到我的坑里,当我把你说晕,一定要用-I时,实际上,我已经挖好了make的坑,而且你已经打算主动跳进去了。
加载中
0
0xTang
0xTang
哈哈,跳进去了!
0
中山野鬼
中山野鬼

引用来自“NullPointException”的答案

哈哈,跳进去了!
能理解清楚原理才行。 有什么我表达不好的地方,还望给出修正意见。
0xTang
0xTang
说的已经很清楚了,多谢啦!
0
Z
ZhuJun
以前一直没耐心看make,今天静下心看了,受益良多,比我看的参考资料更可读!!
0
猫叔猫叔
猫叔猫叔
想编程个小程序,看别人的作品有这个东西,不学不行。还写的不错。
0
defu
defu
“在你智商不明显高出竞争者时,你提高复杂事务的控制能力,是一个击败对手的比较可行的办法。”,说的太好了。
0
换大米

gcc obj\learn_make.o -o  bin\learn_make.exe 少写个 -o的话会提示C:\MinGW\msys\1.0\src\mingwrt/../mingw/crt1.c:260: multiple definition of `mainC
RTStartup'这是为何,

虽然我知道不写o是错误的但我仅仅知道这是个错误,那原因所在呢

貌似没有-o的情况下

gcc会将learn_make.exe learn_make.o都当做obj处理了,那么连接的时候才会出现 multiple definition of `mainCRTStartup 这样的问题

0
捷宝大大
我会说我瞬间懂了吗?谢楼主!
0
依燃飯忒稀
依燃飯忒稀
很不错,看懂了
返回顶部
顶部