您的位置:首页 > 运维架构 > Linux

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

2012-11-11 22:30 441 查看
一些新手搞不清楚工程,和源代码,C文件,头文件的区别。这里特地为新手说明一下:

无论你是否写过程序。你从用过软件。你会发现很少一个软件就一个文件。你可以在window下看一下某个具体软件的位置,并在这个位置打开文件夹,会发现 有很多文件。从设计软件或程序的开发角度也是一样的,一个程序很多情况下,除非足够简单,你只用一个C文件即可。例如:

1
int
main(
int
argc,
char
*argv[]){
2
return
argc;
3
}
这个文件你甚至都不需要#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文件,这个不用说了,

1
$
rm
obj/*
是多么的方便,如果你希望rebuild all的时候。

bin,里面存放的是静态库,动态库,执行文件,准确说是连接器的输出文件存放的地方,通常为了方便寻找到你折腾半天所想要的最终生成的东西。例如你编译 了半天,希望结果的文件能远程加载到SD卡里,open一个bin的专门目录,比在一堆文件中求索哪个才是你需要的要方便很多。当然这里的所谓库,都是由 当前这个总目录下生成的库文件,而不是别人给你的,别人给你的,通常建议,你增加个lib目录,或增加环境配置,这另谈。这里需要补充纠正一个观点,静态 库,并不是实际的连接工作。实际上是个归档的工作。将一堆obj归档到一个文件中。通常使用ar命令。但广义的说连接,这种归档整理的工作也算是一个,这
是野鬼版的胡搅蛮缠。

asm,里面都是汇编源码文件,实际是很多需要汇编实现的函数保存的文件。大多数人不需要。但我的工作经常用到手写汇编没办法,单片机,ARM,DSP都折腾过工程级的开发,所以没办法,习惯了src,asm分离。

input,output,一个里面存放的是程序执行时的输入资料,一个存放的是程序执行时的输出资料。这个在很多工程里,并不存在,只是我的工作经常遇 到大批量的输入数据资料,来验证代码的正确性和有效性。此时将输入输出归到子目录里,只是方便处理而已。同时,对于程序设计而言,也比较好规范出一个习 惯,对输入输出的接口有个比较规范的组织形式。不然,代码执行,总是在当前目标下查找资料,或者在环境变量path下查找,毕竟不能解决所有问题。

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

1
$
mkdir
src
2
$
mkdir
inc
3
$
mkdir
obj
4
$
mkdir
bin
至少先这4个。能简单的别复杂了。

1
$
mv
learn_make.c
src/
2
$
mv
learn_make.h
inc/
3
$
rm
*
这里会提示说,有些是目录,无法删除。这是好事情,我们需要将learn_make目录下的文件删除干净,目录不动。

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

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

我说了,我不指望我的资料可以提高新学者的智商;我的目的是,提高新学者管理组织工程的能力,使得当工程规模变大时,新学者可以相对其他人更好的驾驭工程。在你智商不明显高出竞争者时,你提高复杂事务的控制能力,是一个击败对手的比较可行的办法。
现在我们在learn_make下,运行gcc,看看有多少累。先说下,为什么在learn_make目录下执行gcc。因为除了这,你在那执行都麻烦。 你要在src里,当你的输出和引用头文件,都需要退出本目录,到上一级目录后,再进入对应子目录,既然一堆孩子都争吵希望gcc在自己目录下执行,那干脆 一碗水端平,老爸自己占便宜得了。

1
$gcc
src/learn_make.c
哈,这里肯定有个错误,因为learn_make.h我们移动到inc目录下了,说错误,是希望引出一个gcc的参数,-Idirectory。 同时也建议新手,就是错误,也要执行一次,当屏幕上,先存在错误提示,后出现正确提示时,这种差异,会刺激你的大脑,快速的记住知识。想迅速快速入门,那么最好你就不停的跳进我的坑里,看看坑是什么模样的,以后才有认识真正的陷阱的能力。

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

1
$gcc
-Iinc src/learn_make.c
2
$
ls
注意下,现在显示的是/learn_make目录。

一切又正常了。但没有达到我们的要求。因为当前目录下,有了个a.out。我们希望a.out存放在bin下。你完全可以继续这样执行。

1
$
mv
a.out
bin/
2
$bin/a.out
//此处是调用bin下的a.out执行。


但确实够“弱智”的。可以考虑修改一下特定输出目标。

1
$gcc
-Iinc src/learn_make.c -o bin/learn_make
2
$
ls
bin
//查看bin目录
3
$bin/learn_make
很爽吧,当前目录下,很干净,而且各自文件都归入到自己的位置。

但是生成的.o文件,是值得保留的。这样可以提高效率。因此我们需要继续调整方法

1
$gcc
-Iinc -c src/learn_make.c -o obj/learn_make.o
2
$
ls
obj
记得虽然你指定了目标文件,learn_make.o。但是如果没有-c, gcc会生成一个learn_make.o的可执行文件(这包含了链接的动作),而不是你想要的obj。

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

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


这里不需要 -Iinc了。因为需要inc目录是因为你的c文件需要#include "learn_make.h",在预处理时,需要在合适的位置(目录里)找到对应头文件,此时是对obj的连接工作,和头文件已经没有任何关系了。

1
$
ls
bin
2
$bin/learn_make
如果你现在还没有欲望学习make的话,不妨修改一下,learn_make.c的如下语句

01
#include
<stdio.h>
02
#include
"learn_make.h"
03
#include
"learn_make1.h"
04
#include
"learn_make2.h"
05
#include
"learn_make3.h"
06
int
main(
int
argc,
char
*argv[])
07
printf
(
"%s\n%s\n%s\n%s\n"
,\
08
TEST_GCC_CMD,TEST_GCC_CMD1,TEST_GCC_CMD2,TEST_GCC_CMD3);
09
 
10
return
0;
11
}
12
//以上是src/learn_make.c的文件
13
//以下是learn_make/inc1/learn_make1.h的内容
14
#ifndef
_LEARN_MAKE_1_H_
15
#define
_LEARN_MAKE_1_H_
16
#define
TEST_GCC_CMD1 "test gcc cmd1"
17
#endif//
_LEARN_MAKE_1_H_
18
//同样,你在learn_make/inc2
;learn_make/inc3里也创建类似的头文件,<code>方便主函数调用我不一一展开</code>
如上,类似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库的头文件或系统库的头文件目录下,因此你不能用

1
#include
<XXX.h>
的方式实现。你也不可能因为目录的不同用绝对路径实现例如

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

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

1
#include
"../inc/learn_make.h"
但这个问题治标不治本,同时不方便后期的源代码管理。所以还是老实的用-Idirectory吧。废话这么多,是希望把你说晕了,好跳到我的坑里,当我把你说晕,一定要用-I时,实际上,我已经挖好了make的坑,而且你已经打算主动跳进去了。

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