Robin Chow's C++ Blog

 

2007年10月23日

[导入]我为什么不想成为 VC 程序员

今天在论坛上看到这样一段话,感触颇深:
在我的早期职业生涯中,也曾是个VC程序员,也曾以为驾驭了VC就是驾驭了自己的技术生涯。但渐渐地,我发现无论我潜心研究多深,付出多少精力,在大多数
问题上,我无法给自己满意的答案。似乎自己在金字塔大大小小、宽宽窄窄的隧道里踯躅前行,但每走了一大段路后,你总能碰到一扇门,一扇单靠自己很难逾越的
门。后来发现,很多VC程序员也有同感:在做过若干年后,总会感觉面前立起一个槛,没办法逾越。这不是平台期之类的阶段,平台期之后通常会有一个大的提升
阶段,但在那扇门前,很少有人窥得到门后的景观。没错,很多人成熟起来后明白了这扇门来自何方,为甚所立,所挡为何,这扇门就是:Microsoft。
作为一个盈利组织,微软设立层层技术壁垒,使自己的产品立于不败之地,争取盈利最大化,为大大小小的投资人负责,这无可厚非。但作为一个靠技术生存的人,
除非你进入微软视窗系统开发部门,否则很难打开那扇门。所以,大部分的VC程序员,无论付出多少,最终沦为平庸,也就不奇怪了:你只能知道这些,这是游戏
规则的一条,只是你自己没仔细看清楚而已。
像大多数理想主义者在发现自己最终是在为最不理想主义的手牵着鼻子走之后,很多VC程序员开始沦为最现实的技术人员,自甘平庸,宁愿囚在windows囚
室中,但只要拥有一份过得去的薪水,一个技术高手的光环,就像《matrix》里描述的一类人,宁愿生活在虚幻中,但只要过得体面,那也成。在这间囚室
中,我发现自己只是在复制别人的工作,甚至都无法理解别人是怎么做的。我根本不会设计软件!
但也有一部分更为坚定的理想主义者,不但没有沦丧,反而升华,研究VC,只是他们追求乌托邦理想链条中的一环而已,在进行充分研究后,他们继续前行,只是
少了更多的羁伴。他们中的很多人甚至放弃了windows平台,开始宽泛地、深入地理解计算机科学的精髓及其外围。既然要透彻地领悟一样知识,那就不该有
不可逾越的门。自然而然地,这些真正的理想主义者开始投入开放源代码世界。在这个世界里,只要你愿意,你可以打开任何一扇门,贪婪地汲取任何你需要的养
料,你所需要接受的游戏规格却非常简单:与别人无偿地分享你的知识,并把这个游戏规则教给别人。在这个开放的世界里,不再有壁垒,你会明确地知道自己在干
什么,只要你愿意。因而,你也能够做出一些真正original的工作。这不正是在那个午后,填报志愿时,我们曾立志奉献一生的理想吗?
最后,送出一句忠告:不要固步自封,尽量放open;如果做不到的话,那就尽量承认自己尽管不过20岁,却已经老去了罢。

文章来源:http://my.donews.com/robinchow/2007/01/11/pjhceeuybqztyteeaggsrqunorchjhyzqgcv/

posted @ 2007-10-23 21:01 Robin Chow 阅读(295) | 评论 (0)编辑 收藏

[导入]Exception C++ 之 6

如果对||予以正确恰当的重载,那么在 if 语句中,||或许是一个函数调用。如果 其是一个函数调用,那么“短路求值规则”会被抑住,这样 if 语句中的所有条件将总是被求值。
例如:
if(e.Title() == "CEO" || e.Salary() > 10000) cout << "hello" << endl;
如果对==、||和>进行适当重载,则两个表达式都将被求值。

文章来源:http://my.donews.com/robinchow/2007/01/10/wtFbMixKLhXCHOcnoKJheNEPFczsPCQmVvoI/

posted @ 2007-10-23 21:01 Robin Chow 阅读(167) | 评论 (0)编辑 收藏

[导入]Effective C++读书笔记

条款1:尽量用const和inline而不用#define
1.为方便调试,最好使用常量。
注意:常量定义一般放在头文件中,可将指针和指针所指的类型都定义成const,如const char * const authorName = “Scott Meyers”;
类中常量通常定义为静态成员, 而且需要先声明后定义。可以在声明时或定义时赋值,也可使用借用enum的方法。如enum{Num = 5};
2.#define语句造成的问题。
如#define max(a, b) ((a) > (b) ? (a) : (b))
在下面情况下:
Int a= 5, b = 0;
max(++ a, b);
max(++ a, b + 10);
max内部发生些什么取决于它比较的是什么值。解决方法是使用inline函数,可以使用template来产生一个函数集。

条款2:尽量用而不用
用>> 和<<使得编译器自己可以根据不同的变量类型选择操作符的不同形式,而采取的语法形式相同。

条款3:尽量用new和delete而不用malloc和free
使用malloc和free的时候不会自己调用构造函数和析构函数,因此如果对象自己分配了内存的话,那么这些内存会全部丢失。另外,将new和malloc混用会导致不可预测的后果。

条款4:尽量使用C++风格的注释
C++的注释可以在注释里还有注释,所以注释掉一个代码块不用删除这段代码的注释。C则不行。

条款5:对应的new和delete要采用相同的形式
调 用new时用了[],调用delete时也要用 []。如果调用new时没有用[],那调用delete时也不要用[]。对于typedef来说,用new创建了一个typedef定义的类型的对象后, delete时必须根据typedef定义的类型来删除。因此,为了避免混乱,最好杜绝数组类型用typedef。

条款6:析构函数里对指针成员调用delete
删除空指针是安全的,因此在析构函数里可以简单的delete类的指针成员,而不用担心他们是否被new过。

条款7:预先准备好内存不足的情况
1.用try-cache来捕获抛出的异常。
2. 当内存分配请求不能满足时,调用预先指定的一个出错处理函数。这个方法基于一个常规,即当operator new不能满足请求时,会在抛出异常之前调用客户指定的一个出错处理函数—一般称之为new-handler函数。还可以创建一个混合风格的基类—这种基 类允许子类继承它某一特定的功能(即函数)。

条款8:写operator new和operator delete时要遵循常规
内存分配程序支持new-handler函数并正确地处理了零内存请求,并且内存释放程序处理了空指针。此外还必须要有正确的返回值。

条款9:避免隐藏标准形式的new
在 类里定义了一个称为“operator new”的函数后,会不经意地阻止了对标准new的访问(到底如何隐藏的???)。一个办法是在类里写一个支持标准new调用方式的operator new,它和标准new做同样的事,这可以用一个高效的内联函数来封装实现。另一种方法是为每一个增加到operator new的参数提供缺省值。

条款10:如果写了operator new就要同时写operator delete
operator new和operator delete需要同时工作,如果写了operator new,就一定要写operator delete。对于为大量的小对象分配内存的情况,可以考虑使用内存池,以牺牲灵活性来换取高效率。

条款11:为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
如果没有自定已拷贝构造函数和赋值操作符,C++会生成并调用缺省的拷贝构造函数和赋值操作符,它们对对象里的指针进行逐位拷贝,这会导致内存泄漏和指针重复删除。因此,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值运算符函数。

条款12:尽量使用初始化而不要在构造函数里赋值
尽量使用成员初始化列表,一方面对于成员来说只需承担一次拷贝构造函数的代价,而非构造函数里赋值时的一次(缺省)构造函数和一次赋值函数的代价;另一方面const和引用成员只能被初始化而不能被赋值。

条款13:初始化列表中的成员列出的顺序和它们在类中声明的顺序相同
类的成员是按照它们在类里被声明的顺序进行初始化的,和它们在成员初始化列表中列出的顺序没有关系。

条款14:确定基类有虚析构函数
通过基类的指针去删除派生类的对象,而基类有没有虚析构函数时,结果将是不可确定的。因此必须将基类的析构函数声明为virtual。但是,无故的声明虚析构函数和永远不去声明一样是错误的,声明虚函数将影响效率。

条款15:让operator=返回*this的引用
当 定义自己的赋值运算符时,必须返回赋值运算符左边参数的引用,*this。如果不这样做,就会导致不能连续赋值,或导致调用时的隐式类型转换不能进行(隐 式类型转换时要用到临时对象,而临时对象是const的),或两种情况同时发生。对于没有声明相应参数为const的函数来说,传递一个const对象是 非法的。

条款16:在operator=中对所有数据成员赋值
当类里增加新的数据成员时,要记住更新赋值运算符函数。对基类的私有成员赋值时,可以显示调用基类的operator=函数。派生类的拷贝构造函数中必须调用基类的拷贝构造函数而不是缺省构造函数,否则基类的数据成员将不能初始化。

条款17:在operator=中检查给自己赋值的情况
显 示的自己给自己赋值不常见,但是程序中可能存在隐式的自我赋值:一个对象的两个不同名字(引用)互相赋值。首先,如果检查到自己给自己赋值就立即返回,可 以节省大量的工作;其次,一个赋值运算符必须首先释放掉一个对象的资源,然后根据新值分配新的资源,在自己给自己的情况下,释放旧的资源将是灾难性的。

条款18:争取使类的接口完整并且最小
必要的函数是拷贝构造函数,赋值运算符函数,然后在此基础上选择必要的、方便的函数功能进行添加。

条款19:分清成员函数,非成员函数和友元函数
■虚函数必须是成员函数。如果f必须是虚函数,就让它称为类c的成员函数。
■ioerator>>和operator<<决不能是成员函数。如果f是operator>>或operator<<,让f称为非成员函数。如果f还需要 访问c的非公有成员,让f称为c的友元。
■其它情况下都声明为成员函数。如果以上情况都不是,让f称为c的成员函数。
Result = onehalf * 2;能通过编译的原因:调用重载*操作符的成员函数,对参数2进行隐式类型转换。
Result = 2 * onehalf;不能通过编译的原因:不能对成员函数所在对象(即成员函数中this指针指向的对象)进行转换。

条款20:避免public接口出现数据成员
访问一致性,public接口里都是函数。
精确的访问控制,可以精确设置数据成员的读写权限。
功能分离,可以用一段计算来取代一个数据成员。举例:计算汽车行驶的平均速度。

条款21:尽量使用const
如果const出现在*号左边,指针指向的数据为常量;如果const出现在*号右边,则指针本身为常量;如果const在两边都出现,二者都是常量。
将operator的返回结果声明为const,以防止对返回结果赋值,这样不合常规。
c+ +中的const:成员函数不修改对象中的任何数据成员时,即不修改对象中的任何一个比特时,这个成员函数才是const的。造成的问题是可以修改指针指 向的值,而且不能修改对象中的一些必要修改的值。解决方案是将必要修改的成员运用mutable关键字。另一种方法是使用const_cast初始化一个 局部变量指针,使之指向this所指的同一个对象来间接实现。还有一种有用又安全的方法:在知道参数不会在函数内部被修改的情况下,将一个const对象 传递到一个取非const参数的函数中。

条款22:尽量用“传引用”而不用“传值”
传值将导致昂贵的对象开销,而传引用则非常高效。
传引用避免了“切割问题”,即当一个派生类的对象作为基类对象被传递是,派生类的对象的作为派生类所具有的所有行为特性会被“切割”掉,从而变成了一个简单的基类对象。

条款23:必须返回一个对象时不要试图返回一个引用缩写
典型情况:操作符重载。
常见的错误:
返回引用,返回的是局部对象的引用。
堆中构造,使用new分配内存,但是无人负责delete的调用,从而造成内存泄漏。
返回静态对象,导致调用同一函数比较时总是相等。
正确的方法是直接在堆栈中创建对象并返回。

条款24:在函数重载和设定参数缺省值间慎重选择
如果可以选择一个合适的缺省参数,否则就使用函数重载。
有一些情况必须使用重载:函数的结果取决于传入参数的个数;需要完成一项特殊的任务。

条款25:避免对指针和数字类型重载
对于f(0):0代表int还是null。编译器认为是int,这和人们的想象不一样。解决办法是使用成员模板,构造一个可以产生null指针对象的类。最重要的是,只要有可能,就要避免对一个数字和一个指针类型重载。

条款26:当心潜在二义性
情形1:可以通过构造函数和转换运算符产生另一个类的对象,这时编译器将拒绝对其中的一种方法进行选择。
情形2:f(int);f(char);对于f(double)时产生二义。
情形3:多继承时,两个基类有同名的成员。此时必须指定基类方可调用,而不考虑访问控制权限和返回值。

条款27:如果不想使用隐式生成的函数就要显式地禁止它
方法是声明该函数,并使之为private。显式地声明一个成员函数,就防止了编译器去自动生成它的版本;使函数为private,就防止了别人去调用它。为了防止成员函数和友元函数的调用,只声明而不定义这个函数。

条款28:划分全局名字空间
使用名字空间,以防止不同库的名字冲突。对于不支持名字空间的编译器,可以使用struct来模拟名字空间,但是此时运算符只能通过函数调用来使用。

条款29:避免返回内部数据的句柄
对于const成员函数来说,返回句柄可能会破坏数据抽象。如果返回的不是指向const数据的句柄,数据可能被修改。对非const成员函数来说,返回句柄会带来麻烦,特别是涉及到临时对象时。句柄就象指针一样,可以是悬浮的。

条款30:避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低
如果获得了私有或保护成员(包括成员函数)的地址(指针或引用),那么就可以象对待公有成员一样进行访问。如果不得不返回其引用或指针,可以通过返回指向const对象的指针或引用来达到两全其美的效果。

条款31:千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用
如 果返回局部对象的引用,那个局部对象其实已经在函数调用者使用它之前就被销毁了。而返回废弃指针的问题是必须要有人负责调用delete,而且对于 product=one*two*three*four;的情况,将产生内存泄漏。因此,写一个返回废弃指针的函数无异于坐等内存泄漏的来临。

条款32:尽可能地推迟变量的定义
不 仅要强变量的定义推迟到必须使用它的时候,还要尽量推迟到可以为它提供一个初始化参数位置。这样做,不仅可以避免对不必要的对象进行构造和析构,还可以避 免无意义的对缺省构造函数的调用。而且,在对变量初始化的场合下,变量本身的用途不言自明,在这里定义变量有益于表明变量的含义。

条款33:明智使用内联
内联函数的本质是将每个函数调用以它的代码体来替换。
大多数编译器拒绝“复杂”的内联函数(例如,包含循环和递归的函数);还有,即使是最简单的虚函数调用,编译器的内联处理程序对它也爱莫能助。
若编译器不进行内联,则将内联函数当作一般的“外联”函数来处理。这称为“被外联的内联”。
找出重要的函数,将它内联。同时要注意代码膨胀带来的问题,并监视编译器的警告信息,看看是否有内联函数没有被编译器内联。

条款34:将文件间的编译依赖性降至最低
■如果可以使用对象的引用和指针,就要避免使用对象本身。定义某个类型的引用和指针只会涉及到这个类型的声明,定义此类型的对象则需要类型定义的参与。
■尽可能使用类的声明,而不使用类的定义。因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类。
■不要在头文件中再包含其它头文件,除非缺少了它们就不能编译。相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己去包含其它的头文件。
■最后一点,句柄类和协议类都不大会使用类联函数。使用任何内联函数时都要访问实现细节,而设计句柄类和协议类的初衷正是为了避免这种情况。

条款35:使公有继承体现“是一个”的含义
如果类D从类B公有继承时,类型D的每一个对象也是类型B的一个对象,但反之不成立。任何可以使用类型B的对象的地方,类型D的对象也可以使用。
特别注意一般理解中的“是一个”,比如企鹅是鸟,并不严密。如果涉及到飞这个动作,二者之间不适合使用公有继承。

条款36:区分接口继承课实现继承
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。也可以为纯虚函数提供一种缺省实现。
声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。

条款37:绝不要重新定义继承而来的非虚函数
如果重新定义继承而来的非虚函数,将导致对象对函数的调用结果由指向其的指针决定,而不是由对象本身的类型来决定。另外,也是类的设计产生矛盾,因为公有继承的含义是“是一个”,改变继承而来的方法显然是不合理的。

条款38:绝不要重新定义继承而来的缺省参数值
虚函数动态绑定,而缺省参数是静态绑定。因此重新定义继承而来的缺省参数值可能造成调用的是定义在派生类,但使用了基类中缺省参数值的虚函数。

条款39:避免“向下转换”继承层次
采用向下转换时,将不利于对代码进行维护,可以采用虚函数的方法来解决。
不得不进行向下转换时,采用安全的向下转换:dynamic_cast运算符。dynamic_cast运算符先尝试转换,若转换成功就返回新类型的合法指针,若失败则返回空指针。

条款40:通过分层来体现“有一个”或“用...来实现”
公有继承的含义是“是一个”。对应地,分层的含义是“有一个”或“用...来实现”。例如,要实现set类,因为list中可以包含重复元素,因此set不是一个list。set可以用list来实现,即在set中包含一个list。

条款41:区分继承和模板
当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类。
当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类。

条款42:明智地使用私有继承
关于私有继承的两个规则:和公有继承相反,如果两个类之间的继承关系为私有,编译器一般 不会将派生类对象转换为基类对象;从私有基类继承而来的成员都称为了派生类的私有成员,即使它们在基类中是保护或公有成员。
私有继承意味这“用...”来实现,但是应尽可能使用分层,必须时才使用私有继承。
条款43:明智地使用多继承
多 继承后果:二义性,如果一个派生类从多个基类继承了一个成员名,所有对这个名字的访问都是二义的,你必须明确说出你所指的是哪个成员。这可能导致虚函数的 失效,并且不能对多继承而来的几个相同名称虚函数同时进行重定义。钻石型继承,此时向虚基类传递构造函数参数时要在继承结构中最底层派生类的成员初始化列 表中指定。同时还要仔细想想虚函数的优先度。
然而在适当时候还是可以使用多继承,例如将接口的公有继承和实现的私有继承结合起来的情况。
以增加中间类的代价来消除多继承有时侯是值得的。一般应该避免使用多继承以减少继承结构的复杂性。

条款44:说你想说的,理解你所说的
理解不同的面向对象构件在C++中的含义:
· 共同的基类意味着共同的特性。
· 公有继承意味着 "是一个"。
· 私有继承意味着 "用...来实现"。
· 分层意味着 "有一个" 或 "用...来实现"。
下面的对应关系只适用于公有继承的情况:
· 纯虚函数意味着仅仅继承函数的接口。
· 简单虚函数意味着继承函数的接口加上一个缺省实现。
· 非虚函数意味着继承函数的接口加上一个强制实现。

条款45:弄清C++在幕后为你所写、所调用的函数
如 果没有声明下列函数,编译器会声明它自己的版本:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符和一个缺省构造函数。对于拷贝构造函数 和赋值运算符,官方的规则是:缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行“以成员为单位的”逐一拷贝构造(赋值)。
特别要注意由于编译器自动生成的函数造成的编译错误。

条款46:宁可编译和链接是出错,也不要运行时出错
通常,对设计做一点小小的改动,就可以在编译期间消除可能产生的运行时错误。这常常涉及到在程序中增加新的数据类型。例如对于需要类型检查的Month,可以将其设为一个Month类:构造函数私有,产生对象使用静态成员函数,每个Month对象为const。

条款47:确保非局部静态对象在使用前被初始化
如果在某个被编译单元中,一个对象的初始化要依赖于另一个被编译单元中的另一个对象的值,并且这第二个对象本身也需要初始化,就有可能造成混乱。
虽 然关于 "非局部" 静态对象什么时候被初始化,C++几乎没有做过说明;但对于函数中的静态对象(即,"局部" 静态对象)什么时候被初始化,C++却明确指出:它们在函数调用过程中初次碰到对象的定义时被初始化。如果不对非局部静态对象直接访问,而用返回局部静态 对象引用的函数调用来代替,就能保证从函数得到的引用指向的是被初始化了的对象。

条款48:重视编译器警告
重视编译器产生的每一条警告信息。在忽略一个警告之前,一定要准确理解它想告诉你的含义。

条款49:熟悉标准库
对 于C++头文件,最大的挑战是把字符串头文件理清楚:是旧的C头文件,对应的是基于char*的字符串处理函数; 是包装了std的C++头文件,对应的是新的string类(看下文);是对应于旧C头文件 的std版本。如果能掌握这些,其余的也就容易了。
库中的一切都是模板。

条款50:提高对C++的认识
C++的设计目标:和C兼容,效率,和传统开发工具及环境的兼容性,解决真实问题的可应用性。
参考C++标准,理解C++的设计过程。
文章来源:http://my.donews.com/robinchow/2007/01/10/ofzrlddyftbhlvscqmkjicnypymamyhaehrq/

posted @ 2007-10-23 21:01 Robin Chow 阅读(141) | 评论 (0)编辑 收藏

[导入]理解Linux配置文件

分类和使用
Subodh Soni(subodh@in.ibm.com)
IBM 软件工程师
2001 年 12 月

本 文说明了 Linux 系统的配置文件,在多用户、多任务环境中,配置文件控制用户权限、系统应用程序、守护进程、服务和其它管理任务。这些任务包括管理用户帐号、分配磁盘配 额、管理电子邮件和新闻组,以及配置内核参数。本文还根据配置文件的使用和其所影响的服务的情况对目前 Red Hat Linux 系统中的配置文件进行了分类。
介绍
每个 Linux 程序都是一个可执行文件,它含有操作码列表,CPU 将执行这些操作码来完成特定的操作。例如,ls 命令是由 /bin/ls 文件提供的,该文件含有机器指令的列表,在屏幕上显示当前目录中文件的列表时需要使用这些机器指令。几乎每个程序的行为都可以通过修改其配置文件来按照您 的偏好或需要去定制。

Linux 中有没有一个标准的配置文件格式?
一句话,没有。不熟悉 Linux 的用户(一定)会感到沮丧,因为每个配置文件看起来都象是一个要迎接的新挑战。在 Linux 中,每个程序员都可以自由选择他或她喜欢的配置文件格式。可以选择的格式很多,从 /etc/shells 文件(它包含被一个换行符分开的 shell 的列表),到 Apache 的复杂的 /etc/httpd.conf 文件。

什么是系统配置文件?
内核本身也可 以看成是一个“程序”。为什么内核需要配置文件?内核需要了解系统中用户和组的列表,进而管理文件权限(即根据权限判定特定用户(UNIX_USERS) 是否可以打开某个文件)。注意,这些文件不是明确地由程序读取的,而是由系统库所提供的一个函数读取,并被内核使用。例如,程序需要某个用户的(加密过 的)密码时不应该打开 /etc/passwd 文件。相反,程序应该调用系统库的 getpw() 函数。这种函数也被称为系统调用。打开 /etc/passwd 文件和之后查找那个被请求的用户的密码都是由内核(通过系统库)决定的。

除非另行指定,Red Hat Linux 系统中大多数配置文件都在 /etc 目录中。配置文件可以大致分为下面几类:

访问文件
/etc/host.conf 告诉网络域名服务器如何查找主机名。(通常是 /etc/hosts,然后就是名称服务器;可通过 netconf 对其进行更改)
/etc/hosts 包含(本地网络中)已知主机的一个列表。如果系统的 IP 不是动态生成,就可以使用它。对于简单的主机名解析(点分表示法),在请求 DNS 或 NIS 网络名称服务器之前,/etc/hosts.conf 通常会告诉解析程序先查看这里。
/etc/hosts.allow 请参阅 hosts_access 的联机帮助页。至少由 tcpd 读取。
/etc/hosts.deny 请参阅 hosts_access 的联机帮助页。至少由 tcpd 读取。


引导和登录/注销
/etc/issue & /etc/issue.net 这些文件由 mingetty(和类似的程序)读取,用来向从终端(issue)或通过 telnet 会话(issue.net)连接的用户显示一个“welcome”字符串。它们包括几行声明 Red Hat 版本号、名称和内核 ID 的信息。它们由 rc.local 使用。
/etc/redhat-release 包括一行声明 Red Hat 版本号和名称的信息。由 rc.local 使用。
/etc/rc.d/rc 通常在所有运行级别运行,级别作为参数传送。例如,要以图形(Graphics)模式(X-Server)引导机器,请在命令行运行下面的命令:init 5。运行级别 5 表示以图形模式引导系统。
/etc/rc.d/rc.local 非正式的。可以从 rc、rc.sysinit 或 /etc/inittab 调用。
/etc/rc.d/rc.sysinit 通常是所有运行级别的第一个脚本。
/etc/rc.d/rc/rcX.d 从 rc 运行的脚本(X 表示 1 到 5 之间的任意数字)。这些目录是特定“运行级别”的目录。当系统启动时,它会识别要启动的运行级别,然后调用该运行级别的特定目录中存在的所有启动脚本。例 如,系统启动时通常会在引导消息之后显示“entering run-level 3”的消息;这意味着 /etc/rc.d/rc3.d/ 目录中的所有初始化脚本都将被调用。


文件系统
内核提供了一个接口,用来显示一些它的数据结构,这些数据结构对于决定 诸如使用的中断、初始化的设备和内存统计信息之类的系统参数可能很有用。这个接口是作为一个独立但虚拟的文件系统提供的,称为 /proc 文件系统。很多系统实用程序都使用这个文件系统中存在的值来显示系统统计信息。例如,/proc/modules 文件列举系统中当前加载的模块。lsmod 命令读取此信息,然后将其以人们可以看懂的格式显示出来。下面表格中指定的 mtab 文件以同样的方式读取包含当前安装的文件系统的 /proc/mount 文件。 /etc/mtab 这将随着 /proc/mount 文件的改变而不断改变。换句话说,文件系统被安装和卸载时,改变会立即反映到此文件中。
/etc/fstab 列举计算机当前“可以安装”的文件系统。这非常重要,因为计算机引导时将运行 mount -a 命令,该命令负责安装 fstab 的倒数第二列中带有“1”标记的每一个文件系统。
/etc/mtools.conf DOS 类型的文件系统上所有操作(创建目录、复制、格式化等等)的配置。


系统管理
/etc/group 包含有效的组名称和指定组中包括的用户。单一用户如果执行多个任务,可以存在于多个组中。例如,如果一个“用户”是“project 1”工程组的成员,同时也是管理员,那么在 group 文件中他的条目看起来就会是这样的:user: * : group-id : project1
/etc/nologin 如果有 /etc/nologin 文件存在,login(1) 将只允许 root 用户进行访问。它将对其它用户显示此文件的内容并拒绝其登录。
etc/passwd 请参阅“man passwd”。它包含一些用户帐号信息,包括密码(如果未被 shadow 程序加密过)。
/etc/rpmrc rpm 命令配置。所有的 rpm 命令行选项都可以在这个文件中一起设置,这样,当任何 rpm 命令在该系统中运行时,所有的选项都会全局适用。
/etc/securetty 包含设备名称,由 tty 行组成(每行一个名称,不包括前面的 /dev/),root 用户在这里被允许登录。
/etc/usertty
/etc/shadow 包含加密后的用户帐号密码信息,还可以包括密码时效信息。包括的字段有:
登录名
加密后的密码
从 1970 年 1 月 1 日到密码最后一次被更改的天数
距密码可以更改之前的天数
距密码必须更改之前的天数
密码到期前用户被警告的天数
密码到期后帐户被禁用的天数
从 1970 年 1 月 1 日到帐号被禁用的天数

/etc/shells 包含系统可用的可能的“shell”的列表。
/etc/motd 每日消息;在管理员希望向 Linux 服务器的所有用户传达某个消息时使用。


联网
/etc/gated.conf gated 的配置。只能被 gated 守护进程所使用。
/etc/gated.version 包含 gated 守护进程的版本号。
/etc/gateway 由 routed 守护进程可选地使用。
/etc/networks 列举从机器所连接的网络可以访问的网络名和网络地址。通过路由命令使用。允许使用网络名称。
/etc/protocols 列举当前可用的协议。请参阅 NAG(网络管理员指南,Network Administrators Guide)和联机帮助页。
C 接口是 getprotoent。绝不能更改。
/etc/resolv.conf 在程序请求“解析”一个 IP 地址时告诉内核应该查询哪个名称服务器。
/etc/rpc 包含 RPC 指令/规则,这些指令/规则可以在 NFS 调用、远程文件系统安装等中使用。
/etc/exports 要导出的文件系统(NFS)和对它的权限。
/etc/services 将网络服务名转换为端口号/协议。由 inetd、telnet、tcpdump 和一些其它程序读取。有一些 C 访问例程。
/etc/inetd.conf inetd 的配置文件。请参阅 inetd 联机帮助页。包含每个网络服务的条目,inetd 必须为这些网络服务控制守护进程或其它服务。注意,服务将会运行,但在 /etc/services 中将它们注释掉了,这样即使这些服务在运行也将不可用。格式为:
/etc/sendmail.cf 邮件程序 sendmail 的配置文件。比较隐晦,很难理解。
/etc/sysconfig/network 指出 NETWORKING=yes 或 no。至少由 rc.sysinit 读取。
/etc/sysconfig/network-scripts/if* Red Hat 网络配置脚本。


系统命令
系 统命令要独占地控制系统,并让一切正常工作。所有如 login(完成控制台用户身份验证阶段)或 bash(提供用户和计算机之间交互)之类的程序都是系统命令。因此,和它们有关的文件也特别重要。这一类别中有下列令用户和管理员感兴趣的文件。 /etc/lilo.conf 包含系统的缺省引导命令行参数,还有启动时使用的不同映象。您在 LILO 引导提示的时候按 Tab 键就可以看到这个列表。
/etc/logrotate.conf 维护 /var/log 目录中的日志文件。
/etc/identd.conf identd 是一个服务器,它按照 RFC 1413 文档中指定的方式实现 TCP/IP 提议的标准 IDENT 用户身份识别协议。identd 的操作原理是查找特定 TCP/IP 连接并返回拥有此连接的进程的用户名。作为选择,它也可以返回其它信息,而不是用户名。请参阅 identd 联机帮助页。
/etc/ld.so.conf “动态链接程序”(Dynamic Linker)的配置。
/etc/inittab 按年代来讲,这是 UNIX 中第一个配置文件。在一台 UNIX 机器打开之后启动的第一个程序是 init,它知道该启动什么,这是由于 inittab 的存在。在运行级别改变时,init 读取 inittab,然后控制主进程的启动。
/etc/termcap 一个数据库,包含所有可能的终端类型以及这些终端的性能。


守护进程
守 护进程是一种运行在非交互模式下的程序。一般来说,守护进程任务是和联网区域有关的:它们等待连接,以便通过连接提供服务。Linux 可以使用从 Web 服务器到 ftp 服务器的很多守护进程。 /etc/syslogd.conf syslogd 守护进程的配置文件。syslogd 是一种守护进程,它负责记录(写到磁盘)从其它程序发送到系统的消息。这个服务尤其常被某些守护进程所使用,这些守护进程不会有另外的方法来发出可能有问 题存在的信号或向用户发送消息。

/etc/httpd.conf Web 服务器 Apache 的配置文件。这个文件一般不在 /etc 中。它可能在 /usr/local/httpd/conf/ 或 /etc/httpd/conf/ 中,但是要确定它的位置,您还需要检查特定的 Apache 安装信息。
/etc/conf.modules or /etc/modules.conf kerneld 的配置文件。有意思的是,kerneld 并不是“作为守护进程的”内核。它其实是一种在需要时负责“快速”加载附加内核模块的守护进程。


用户程序
在 Linux(和一般的 UNIX)中,有无数的“用户”程序。最常见的一种用户程序配置文件是 /etc/lynx.cfg。这是著名的文本浏览器 lynx 的配置文件。通过这个文件,您可以定义代理服务器、要使用的字符集等等。下面的代码样本展示了 lynx.cfg 文件的一部分,修改这部分代码可以改变 Linux 系统的代理服务器设置。缺省情况下,这些设置适用于在各自的 shell 中运行 lynx 的所有用户,除非某个用户通过指定 --cfg = "mylynx.cfg" 重设了缺省的配置文件。

/etc/lynx.cfg 中的代理服务器设置
.h1 proxy
.h2 HTTP_PROXY
.h2 HTTPS_PROXY
.h2 FTP_PROXY
.h2 GOPHER_PROXY
.h2 NEWS_PROXY
.h2 NNTP_PROXY
# Lynx version 2.2 and beyond supports the use of proxy servers that can act as
# firewall gateways and caching servers. They are preferable to the older
# gateway servers. Each protocol used by Lynx can be mapped separately using
# PROTOCOL_proxy environment variables (see Lynx Users Guide). If you have
# not set them externally, you can set them at run time via this configuration file.
# They will not override external settings. The no_proxy variable can be used
# to inhibit proxying to selected regions of the Web (see below). Note that on
# VMS these proxy variables are set as process logicals rather than symbols, to
# preserve lowercasing, and will outlive the Lynx image.
#
.ex 15
http_proxy:http://proxy3.in.ibm.com:80/
ftp_proxy:http://proxy3.in.ibm.com:80/
#http_proxy:http://penguin.in.ibm.com:8080
#ftp_proxy:http://penguin.in.ibm.com:8080/

.h2 NO_PROXY
# The no_proxy variable can be a comma-separated list of strings defining
# no-proxy zones in the DNS domain name space. If a tail substring of the
# domain-path for a host matches one of these strings, transactions with that
# node will not be proxied.
.ex
no_proxy:demiurge.in.ibm.com, demiurge





更改配置文件
在更改配置文件时,如果程序不是由系统管理员或内核控制的,就要确保重新启动过使用该配置的程序。普通用户通常没有启动或停止系统程序和/或守护进程的权限。

内核
更 改内核中的配置文件会立即影响到系统。例如,更改 passwd 文件以增加用户将立即使该用户变为可用。而且任何 Linux 系统的 /proc/sys 目录中都有一些内核可调参数。只有超级用户可以得到对所有这些文件的写访问权力;其它用户只有只读访问权力。此目录中文件的分类的方式和 Linux 内核源代码的分类方式一样。此目录中的每个文件都代表一个内核数据结构,这些数据结构可以被动态地修改,从而改变系统性能。

注意:在更改其中任何文件的任何值之前,您应该确保自己全面了解该文件,以避免对系统造成不可修复的损害。
/proc/sys/kernel/ 目录中的文件 文件名 描述
threads-max 内核可运行的最大任务数。
ctrl-alt-del 如果值为 1,那么顺序按下这几个键将“彻底地”重新引导系统。
sysrq 如果值为 1,Alt-SysRq 则为激活状态。
osrelease 显示操作系统的发行版版本号
ostype 显示操作系统的类型。
hostname 系统的主机名。
domainname 网络域,系统是该网络域的一部分。
modprobe 指定 modprobe 是否应该在启动时自动运行并加载必需的模块。


守护进程和系统程序
守 护进程是永远运行在后台的程序,它默默地执行自己的任务。常见的守护进程有 in.ftpd(ftp 服务器守护进程)、in.telnetd(telnet 服务器守护进程)和 syslogd(系统日志记录守护进程)。有些守护进程在运行时会严密监视配置文件,在配置文件改变时就会自动重新加载它。但是大多数守护进程并不会自动 重新加载配置文件。我们需要以某种方式“告诉”这些守护进程配置文件已经被发生了改变并应该重新加载。可以通过使用服务命令重新启动服务来达到这个目的 (在 Red Hat Linux 系统上)。

例如,如果我们更改了网络配置,就需要发出:
service network restart。

注意:这些服务最常见的是 /etc/rc.d/init.d/* 目录中存在的脚本,在系统被引导时由 init 启动。所以,您也可以执行如下操作来重新启动服务:
/etc/rc.d/init.d/ start | stop | status
start、stop 和 status 是这些脚本接受的输入值,用来执行操作。

用户程序
用 户或系统程序在每次启动时都会读取其配置文件。尽管如此,请记住,有些系统程序在计算机打开时情况不一样,它们的行为依赖于在 /etc/ 中的配置文件中读到的内容。所以,用户程序第一次启动时将从 /etc/ 目录中存在的文件读取缺省配置。然后,用户可以通过使用 rc 和 .(点)文件来定制程序,正如下面一节所示。

用户配置文件:.(点)文件和 rc 文件
我们已经看到怎样容易地配置程序。但 是如果有的人不喜欢在 /etc/ 中配置程序的方式该怎么办呢?“普通”用户不能简单地进入 /etc 然后更改配置文件;从文件系统的角度来看,配置文件的所有者是 root 用户!这就是大多数用户程序都定义两个配置文件的原因:第一个是“系统”级别的,位于 /etc/;另一个属于用户“专用”,可以在他或她的主目录中找到。

例如,我在我的系统中安装了非常有用的 wget 实用程序。/etc/ 中有一个 /etc/wgetrc 文件。在我的主目录中,有一个名为 .wgetrc 的文件,它描述了我定制的配置(只有在我,也就是用户运行 wget 命令时,才会加载这个配置文件)。其它用户在他们自己的主目录(/home/other)中也可以有 .wgetrc 文件;当然,只有这些用户运行 wget 命令时,才会读取这个文件。换句话说,/etc/wgetrc 文件为 wget 提供了“缺省”值,而 /home/xxx/.wgetrc 文件列举了某个用户的“定制项”。重要的是这只是“一般规则”,并非所有情况都如此。例如,一个象 pine 一样的程序,在 /etc/ 中并没有任何文件,它只在用户主目录中有一个定制配置文件,名为 .pinerc。其它程序可能只有 /etc/ 中的缺省配置文件,而且可能不允许用户“定制”这些配置文件(/etc 目录中只有少数 config. 文件是这种情况)。

通常使用的 rc 和 .(点)文件
文件名 描述
~/.bash_login 请参考“man bash”。如果 ~/.bash_profile 不存在,bash 则将 ~/.bash_login 作为 ~/.bash_profile 处理。
~/.bash_logout 请参考“man bash”。在退出时由 bash 登录 shell 引用。
~/.bash_profile 由 bash 登录 shell 引用 /etc/profile 之后引用。
~/.bash_history 先前执行的命令的列表。
~/.bashrc 请参考“man bash”。由 bash 非登录交互式 shell 引用(没有其它文件)。除非设置了 BASH_ENV 或 ENV,非交互式 shell 不引用任何文件。
~/.emacs 启动时由 emac 读取。
~/.forward
如果这里包含一个电子邮件地址,那么所有发往 ~ 的所有者的邮件都会被转发到这个电子邮件地址。
~/.fvwmrc ~/.fvwm2rc fvwm 和 fvwm2(基本的 X Window 管理器)的配置文件。
~/.hushlogin 请参考“man login”。引起“无提示”登录(没有邮件通知、上次登录信息或者 MOD 信息)。
~/.mail.rc 邮件程序的用户初始化文件。
~/.ncftp/ ncftp 程序的目录;包含书签、日志、宏、首选项和跟踪信息。请参阅 man ncftp。ncftp 的目的是为因特网标准文件传输协议(Internet standard File Transfer Protocol)提供一个强大而灵活的接口。它旨在替换系统所使用的标准的 ftp 程序。
~/.profile 请参考“man bash”。如果 ~/.bash_profile 和 ~/.bash_login 文件不存在,bash 则将 ~/.profile 作为 ~/.bash_profile 处理,并被其它继承 Bourn 的 shell 使用。
~/.pinerc Pine 配置
~/.muttrc Mutt 配置
~/.exrc 这个文件可以控制 vi 的配置。
示例:set ai sm ruler
在此文件中写入上面一行会让 vi 设置自动缩进、匹配括号、显示行号和行-列这几个选项。
~/.vimrc 缺省的“Vim”配置文件。和 .exrc 一样。
~/.gtkrc GNOME 工具包(GNOME Toolkit)。
~/.kderc KDE 配置。
~/.netrc ftp 缺省登录名和密码。
~/.rhosts 由 r- 工具(如 rsh、rlogin 等等)使用。因为冒充主机很容易,所以安全性非常低。
必须由用户(~/ 的所有者)或超级用户拥有。
列出一些主机,用户可以从这些主机访问该帐号。
如果是符号链接则被忽略。

~/.rpmrc 请参阅“man rpm”。如果 /etc/rpmrc 不存在则由 rpm 读取。
~/.signature 消息文本,将自动附加在从此帐号发出的邮件末尾。
~/.twmrc twm(The Window Manager)的配置文件。
~/.xinitrc 启动时由 X 读取(而不是由 xinit 脚本读取)。通常会启动一些程序。
示例:exec /usr/sbin/startkde
如果该文件中存在上面这行内容,那么在从这个帐号发出 startx 命令时,这一行就会启动“KDE 视窗管理器”(KDE Window Manager)。
~/.xmodmaprc 此文件被传送到 xmodmap 程序,而且可以被命名为任何文件(例如 ~/.Xmodmap 和 ~/.keymap.km)。
~/.xserverrc 如果 xinit 可以找到要执行的 X,xinit 就会将该文件作为 X 服务器运行。
~/News/Sent-Message-IDs gnus 的缺省邮件历史文件。
~/.Xauthority 由 xdm 程序读和写,以处理权限。请参阅 X、xdm 和 xauth 联机帮助页。
~/.Xdefaults,
~/.Xdefaults-hostname 在主机 hostname 的启动过程中由 X 应用程序读取。如果找不到 -hostname 文件,则查找 .Xdefaults 文件。
~/.Xmodmap 指向 .xmodmaprc;Red Hat 有使用这个名称的 .xinitrc 文件。
~/.Xresources 通常是传送到 xrdb 以加载 X 资源数据库的文件的名称,旨在避免应用程序需要读取一个很长的 .Xdefaults 文件这样的情况。(有些情况曾经使用了 ~/.Xres。)

~/mbox
用户的旧邮件。


参考资料

要了解更多信息,请阅读 Jack Wallen, Jr. 的 Linux configuration files。


在 developerWorks 上还有:
Technical FAQ for Linux users
What good is a Linux client?
Using the xinetd program for system administration


流览 developerWorks 上的更多 Linux 参考资料。


流览 developerWorks 上的更多开放源代码参考资料。 

文章来源:http://my.donews.com/robinchow/2007/01/10/fbucnsfniuesfiidxfmikaeigzppejtjlomy/

posted @ 2007-10-23 21:01 Robin Chow 阅读(416) | 评论 (0)编辑 收藏

[导入]跟我一起写 Makefile

概述
——

什 么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和 professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解 HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完 成大型工程的能力。

因为,makefile关系到了整个工程的编译规 则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需 要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是 一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

现 在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖 性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。

在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。

 

关于程序的编译和链接
——————————

在 此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器 需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件 中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链 接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只 管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给 中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文 件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成 Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

好,言归正传,GNU的make有许多的内容,闲言少叙,还是让我们开始吧。

 

Makefile 介绍
———————

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
    1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
    2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
    3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。


一、Makefile的规则

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

    target ... : prerequisites ...
            command
            ...
            ...

    target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

    prerequisites就是,要生成那个target所需要的文件或是目标。

    command也就是make需要执行的命令。(任意的Shell命令)

这 是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说 白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是 Makefile的规则。也就是Makefile中最核心的内容。

说到底,Makefile的东西就是这样一点,好像我的这篇文档也该结束了。呵呵。还不尽然,这是Makefile的主线和核心,但要写好一个Makefile还不够,我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。:)


二、一个示例

正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。

    edit : main.o kbd.o command.o display.o \
           insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o \
                       insert.o search.o files.o utils.o

    main.o : main.c defs.h
            cc -c main.c
    kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
    command.o : command.c defs.h command.h
            cc -c command.c
    display.o : display.c defs.h buffer.h
            cc -c display.c
    insert.o : insert.c defs.h buffer.h
            cc -c insert.c
    search.o : search.c defs.h buffer.h
            cc -c search.c
    files.o : files.c defs.h buffer.h command.h
            cc -c files.c
    utils.o : utils.c defs.h
            cc -c utils.c
    clean :
            rm edit main.o kbd.o command.o display.o \
               insert.o search.o files.o utils.o

反 斜杠(\)是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中, 然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。

在这个makefile中,目标文件 (target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在 定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执 行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比 targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

这 里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去 找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们 可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。

 

三、make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

    1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
    2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
    3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
    4、如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
    5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这 就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的 文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我 找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于 是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如 file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的 啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。

而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。


四、makefile中使用变量

在上面的例子中,先让我们看看edit的规则:

      edit : main.o kbd.o command.o display.o \
                  insert.o search.o files.o utils.o
            cc -o edit main.o kbd.o command.o display.o \
                       insert.o search.o files.o utils.o

我 们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在 clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果 makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们 可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

     objects = main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

    objects = main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)
    main.o : main.c defs.h
            cc -c main.c
    kbd.o : kbd.c defs.h command.h
            cc -c kbd.c
    command.o : command.c defs.h command.h
            cc -c command.c
    display.o : display.c defs.h buffer.h
            cc -c display.c
    insert.o : insert.c defs.h buffer.h
            cc -c insert.c
    search.o : search.c defs.h buffer.h
            cc -c search.c
    files.o : files.c defs.h buffer.h command.h
            cc -c files.c
    utils.o : utils.c defs.h
            cc -c utils.c
    clean :
            rm edit $(objects)


于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。


五、让make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

只 要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make 找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。


    objects = main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)

    main.o : defs.h
    kbd.o : defs.h command.h
    command.o : defs.h command.h
    display.o : defs.h buffer.h
    insert.o : defs.h buffer.h
    search.o : defs.h buffer.h
    files.o : defs.h buffer.h command.h
    utils.o : defs.h

    .PHONY : clean
    clean :
            rm edit $(objects)

这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。


六、另类风格的makefile

即然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。

    objects = main.o kbd.o command.o display.o \
              insert.o search.o files.o utils.o

    edit : $(objects)
            cc -o edit $(objects)

    $(objects) : defs.h
    kbd.o command.o files.o : command.h
    display.o insert.o search.o files.o : buffer.h

    .PHONY : clean
    clean :
            rm edit $(objects)

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。


七、清空目标文件的规则

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

        clean:
            rm edit $(objects)

更为稳健的做法是:

        .PHONY : clean
        clean :
                -rm edit $(objects)

前 面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的 事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在 文件的最后”。


上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?准备好了就来。

 

Makefile 总述
———————

一、Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个 Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有 就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“\#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


二、Makefile的文件名

默 认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、 “makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名 第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的 “makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当 然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”, “Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如: make -f Make.Linux或make --file Make.AIX。


三、引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

    include <filename>

    filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include 前面可以有一些空字符,但是绝不能是[Tab]键开始。include和 <filename>可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫 foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

    include foo.make *.mk $(bar)

    等价于:

    include foo.make a.mk b.mk c.mk e.mk f.mk

make 命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件 都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

    1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
    2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如 果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重 试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在 include前加一个减号“-”。如:

    -include <filename>
    其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。


四、环境变量 MAKEFILES

如 果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的 Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的 Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

但 是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make 时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么 你可以看看当前环境中有没有定义这个变量。


五、make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

    1、读入所有的Makefile。
    2、读入被include的其它Makefile。
    3、初始化文件中的变量。
    4、推导隐晦规则,并分析所有规则。
    5、为所有的目标文件创建依赖关系链。
    6、根据依赖关系,决定哪些目标要重新生成。
    7、执行生成命令。

1- 5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么, make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要 使用了,变量才会在其内部展开。

当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。


文章来源:http://my.donews.com/robinchow/2007/01/10/eookhacfeksbxfdsqfxbkaljurwnvytzmhkt/

posted @ 2007-10-23 21:01 Robin Chow 阅读(205) | 评论 (0)编辑 收藏

[导入]UNICODE 介绍

UNICODE 介绍

内容篇幅较长,请点击这里阅读全文。

王一伟 2007-01-05 13:18


文章来源:http://my.donews.com/robinchow/2007/01/10/hpjgzaypfnngnyhndzdahfirtdoujqtqfdxf/

posted @ 2007-10-23 21:01 Robin Chow 阅读(149) | 评论 (0)编辑 收藏

[导入]详解compile和link

详解compile和link

详解compile & link
有些人写C/C++(以下假定为C++)程序,对unresolved external link或者duplicated external simbol的错误信息不知所措(因为这样的错误信息不能定位到某一行)。或者对语言的一些部分不知道为什么要(或者不要)这样那样设计。了解本文之后,或许会有一些答案。


Francis Arcanum 2007-01-05 16:03

technorati tags:


文章来源:http://my.donews.com/robinchow/2007/01/10/lreejbbjfzmyhwqrdgmnsxfrdnejscnetrid/

posted @ 2007-10-23 21:01 Robin Chow 阅读(141) | 评论 (0)编辑 收藏

[导入]DLL(Dynamic Link Libraries)专题

DLL(Dynamic Link Libraries)专题

比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序 EXE文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的 EXE 程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。


文章来源:http://my.donews.com/robinchow/2007/01/10/gyedntmqpvxuumljqmoaodnacyxpzpbfngjb/

posted @ 2007-10-23 21:01 Robin Chow 阅读(244) | 评论 (0)编辑 收藏

[导入]Exceptional C++ 之 5

异常安全是指代码能够自己处理异常,即出现异常时仍能正常工作;异常中立是指代码将所有异常都转给调用者。

如果只需捕获“...”部分的异常,如:
try {TryCode();} catch(...) {CatchCode(parms); throw;}

可以改写成:
struct Janitor{
    Janitor(Parms p) : pa(p) {};
    ~Janitor() {if uncaught_exception() CatchCode(pa); }
    Parms pa;
};

在try代码之前创建一个Janitor对象,如果抛出异常,那么将执行CatchCode函数。

另:异常安全需要多多研究。
文章来源:http://my.donews.com/robinchow/2007/01/10/esmxouitpvwdhegigrvhvlffqxlbvsrzkdwz/

posted @ 2007-10-23 21:01 Robin Chow 阅读(222) | 评论 (0)编辑 收藏

[导入]Exceptional C++ 之 4

  1. 使用const&而不是传值拷贝。
  2. 尽量使用先增操作,避免使用后增操作。因为在进行后增操作时,对象不但必须自己递增,而且还要返回一个包含递增前之值的临时对象。
  3. 时刻注意因为参数转换而产生的隐藏的临时对象。一个避免它的好办法就是尽可能显式(explicit)的使用构造函数。
  4. 绝对不要返回局部对象的引用。

文章来源:http://my.donews.com/robinchow/2007/01/10/hbvgnqjrwkhzuhmyzymicgwlniycpcvweqdr/

posted @ 2007-10-23 21:01 Robin Chow 阅读(132) | 评论 (0)编辑 收藏

仅列出标题  下一页

导航

统计

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

最新评论

阅读排行榜

评论排行榜