LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 1197|回复: 6

GNU codeing standards 拙译

[复制链接]
发表于 2006-2-18 10:59:27 | 显示全部楼层 |阅读模式
小弟由于要在linux上进行编程了,一直没有一个统一的风格来约束自己,刚好看到GNU coding standards一文,觉得很好,就想把它翻译过来和大家共享一下,由于水平有限,翻译的不好,大家就凑合着看吧.
另外,我只是从第三章开始翻译,前面的只是一些非技术性的问题,我就没有翻译.
GNU coding standards原文在这里:
http://www.gnu.org/prep/standards/standards.html
下面是我翻译的,现在正在翻译过程中,刚刚翻译了两章多一点,
3 General Program Design
3 一般性程序设计
这章将讨论一些你进行程序设计时要考虑的问题.
        * 编程语言:        使用那种语言
        * 兼容性:        与现有程序的兼容性
        * 使用扩展: 使用非标准的特性
        * 标准C: 使用标准C
        * 条件编译: 只有符合A条件时才编译代码
3.1 使用哪种语言
        当你要选择一种编译后能够高效运行的语言时,首选C. 使用其他语言就象使用了语言的非标准特性一样: 它将为用户带来麻烦. 即使GCC支持那种语言, 用户将会觉得很不方便,为了要编译你的程序而不得不装那种语言的编译器. 比如 ,如果你用C++语言,那么人们将不得不安装GNU C++编译器.
        C有C++和其他语言无法相比的优势:更多的人了解C,因此更多的人将会觉得更容易阅读和修改C语言写的程序.
        因此一般说来使用C要比其他可供选择的语言好的多.
        但是有两种情况例外:
        * 为那种语言开发的专用工具. 这是由于那些要编译安装这个工具的人已经安装了那种语言
        * 如果程序只是面向很少一部分人,那么使用那种语言对其他人的影响很小,因此随你的喜好选择.
        许多程序设计为具有可扩展性:它们包含对一种比C语言更高级的语言的解释器. 一般说来,程序的很大一部分也是用那种语言写的.Emacs就是这种技术的代表.
        标准的可扩展性解释器是GNU软件GUILE,它实现了一种语言方案(一种特别的简洁的Lisp语言).http://www.gnu.org/software/guil ... 致性非常重要.
3.2 与其他程序的兼容
        除了极少数的例外,GNU工具程序和库应该与BerKeley Unix 保持向上兼容,与标准C保持向上兼容如果标准C指定了它们的行为,与posix保持兼容如果posix指定了它们的行为.
        当标准发生冲突时,有必要为每一种都提供兼容的模式.
        标准C和posix禁止了许多扩展.可以放心使用这些扩展,只要包含能够关闭它们的选项 --ansi,--posix,--compatible.但是,如果这个扩展对于真正的程序或者脚本有显著的影响,那么他就不符合真正的向上兼容.因此你应该重新设计接口使得它能够向上兼容.
        许多GNU程序都压缩那些与posix冲突的扩展,如果定义了环境变量POSIXLY_CORRECT. 请使你的程序在适当的时候也能识别这个变量.
        当一个特色只有用户使用(不是程序或者命令文件),这种情况在Unix中很少见,可以放心用不同的或者更好的来替换它.(比如,vi被Emacs替换.) 但是能够提供兼容的特性会更好.(有一个免费的vi变种,因此我们提供了它).  
        欢迎新增的他有用特性,不关它们是否已经有先例.
3.3 使用非标准特性
        与Unix工具比起来,许多GNU工具已经提供了许多方便的扩展.在设计你的程序时,是否使用这些扩展确实是一个困难的问题.
        一方面,使用这些扩展能创建更清晰的程序.另一方面,人们将不能编译这些除他们有这些GNU工具.这将使这些程序只能在较少的机器上运行.
        对于一些扩展,很容易提供用与不用的选择.比如,你可以使用"关键子"INLINE 定义函数,并且把它定义为宏,依据编译器的不同,可以扩展为inline或者空.
        一般说来,如果没有这些扩展可以的话最好就不要使用,除非这些扩展能够带来大的改进.
        这个规则对那些大的,固定的,可以在许多平台上运行的程序(比如Emacs)例外,在这些程序中使用GNU扩展将会使用户不高兴,因此我们不使用.
        另外一种例外是设计作为编译的一部分的程序: 所有那些为了引导GNU编译工具而必须使用其他编译器的程序.如果他们需要GNU编译器,那么人们将不能编译这些程序除非他们已经安装了GNU编译器.在特定情况下,那将会是极其糟糕的.
3.4 Standard C and Pre-Standard C
3.4  标准C和原始C
        C89 现在已经广泛使用,因此在新的程序中使用他它的特性将没有任何问题.但是有一个例外,千万不要使用标准C的"trigraph"特性.
        C99还没有广泛使用,因此不要在程序中使用它的特性.但是可以使用那些已经存在的特性.
        然而,大多数程序中,很容易支持原始的c编译器.如果你知道怎么做,请随便.如果你在维护的程序有这种支持,你应该尽量保持下来.为了支持原始C,必要一标准原型格式来书写函数定义,
        int
        foo (int x, int y)
        ...
用原始C的风格,这样写.
     int
     foo (x, y)
          int x, y;
     ...
并且使用一个单独的声明来指明参数原型.
        无论如何你都需要这样一个声明,把它放在头文件中,这样在所有调用这个函数的文件里就不用再写一遍.并且,一旦你声明了,以原始C风格定义函数你不会丢失任何信息.
        这种技术对于宽度窄于int的类型不合适.如果你认为一个参数类型宽度比int窄,直接把它声明为int.
        有些特殊情况下,这种技术很难用上.比如,如果一个函数的参数需要系统类型dev_t,你就陷入困境,因为dev_t在一些机器上比int短,但是你又不能用int来代替,因为dev_t在一些机器上比int宽,因此你没法以非标准方式安全地使用一种类型.既支持非标准C又能传递这样的参数的唯一方法就是使用Autoconf来检查dev_t的宽度,然后选择相宜给你的类型.这个问题这样处理可能不划算.
        为了支持那些不能识别原型的非标准C编译器,你可能想使用这样的宏定义:
    /* Declare the prototype for a general external function.  */
     #if defined (__STDC__) || defined (WINDOWSNT)
     #define P_(proto) proto
     #else
     #define P_(proto) ()
     #endif
3.5 Conditional Compilation
3.5 条件编译
        果已经知道配置选项,那么在编译你的程序时,应该使用if(...)而不是条件编译,因为在前一种情况下,编译器能够对所有可能的路径进行广泛的检查.
比如: 要写
        if(HAS_FOO)
                ...
        else
                ...
而不是:
        #ifdef HAS_FOO
                ...
        #else
                ...
        #endif
   
        对于这两种情况,现代的编译器比如GCC将会生成完全相同的代码,并且我们已经在几个项目中成功地使用了类似的技术.当然,前一种方法假定HAS_FOO被定义为0或1.
        尽管它并不象银色子弹(silver bullet)那样解决了所有的可移植性问题,并且并不是总是很恰当,但是遵照这种策裸可以每年节省GCC开发者许多小时,甚至许多天.
        在处理GCC中的类似函数的宏比如REVERSIBLE_CC_MODE时,不能简单地使用if(>...)语句.这里有一种简单的方法. 再引入另外一个宏HAS_REVERSIBLE_CC_MODE宏即可. 例如:
       #ifdef REVERSIBLE_CC_MODE  
       #define HAS_REVERSIBLE_CC_MODE 1  
       #else  
       #define HAS_REVERSIBLE_CC_MODE 0  
       #endif
4 Program Behavior for All Programs  
4 编程行为
本章主要描述如何写出健壮的软件的约定.也描述了一些一般的标准,比如错误消息,命令行接口,如何写库.
        *非GNU标准: 我们考虑一些其他的标准比如: POSIX;我们不"遵守"它们.
        *语义:写出健壮的程序.
        *库:库的行为
        *错误: 格式化错误消息
        *用户接口:一般性的用户接口标准.
        *图形接口: 图形接口的标准
        *命令行接口:命令行接口的标准
        *选项列表:长选项列表
        *内存使用:什么时候,如何关注内存需求
        *文件使用: 使用那些文件,哪里使用
4.1 非GNU标准
        GNU工程把其他组织发布的标准当作建议,而不是命令.我们考虑那些标准,但是不"遵照"它们". 在开发一个GNU程序时,你应该公正地看待,如果按照外界的标准能够是GNU系统更好,那么就要实现这些标准.当它不能时,就不用实现.
        大多数情况下,遵照公开的标准给用户带来方便--它意味着用户的程序和脚本能够更具有可移植性. 例如,GCC实现了几乎标准C指明的所有的特性.如果没有,C程序员将会不高兴. GNU工具包几乎全部遵照POSIX.2.如果我们的程序不兼容,那么shell脚本的设计者和用户将会不高兴.
        但是我们对于哪个标准都没有严格遵守,为了使GNU系统对用户更友好,有一些特别的地方我们决定不遵守标准.
         比如,标准C要求几乎所有对C的扩展都是禁止的.多么愚蠢啊!GCC实现了很多扩展,其中的一些还在后来被标准采用.如果想让这些扩展按照标准的"苛求"给出错误消息,你可以使用'--pedantic'选项,这个实现除了让有些人说GCC对标准100%地实现了,再没有其他任何理由.
        POSIX.2指出df和du缺省的输出大小应该以512bytes为单元. 用户需要的是以1k为单元,因此这才是我们所做的缺省设置.如果你想严格的遵守POSIX的"苛求",你必须设定环境变量'POSIXLY_CORRECT' (这个本来应该叫'POSIX_ME_HARDER').
        GNU工具包也违反了POSIX.2关于长想选项和普通选项混合使用时的规定.这个微小的不兼容在实际应用中从来就不是问题,但是却很有用.
        特别地,不要拒绝信新特性,或者去掉旧的特性,仅仅因为一个标准说它是"严禁"或者"过时".
4.2 Writing Robust Programs  
4.2 写出健壮的程序
        GNU程序通过动态地分配所有数据结构来来避免对任何数据结构的长度和数量作出限制,包括文件名,行数,文件和符号。在大部分的Unix工具中,“没有提示就把长行截断”。这在GNU工具中是不可接受的。
        读取文件的工具不应该丢弃NUL字符或者任何别的不可输出的字符,包括那些代码大于0177的字符。唯一明智的例外是专门为某些特定的终端或者打印机设计工具,但是这些终端或者打印机不能处理那些字符。只要可能,尽量使你的程序能够在多字节字符下工作,使用比如UTF-8或者其他的编码。
        除非你希望忽略错误,否则检查系统调用的返回值进行错误检查。错误信息要包含每一个失败的系统调用的错误消息的系统信息(从perror或者其他等效的地方获得),如果可能,也要包含文件名,和工具名字.仅仅给出"cannot open foo.c" 或者"stat failed "是不够的.
        每次都要检查malloc或者realloc是否返回了0,即使调用realloc来缩小内存块.一个系统里,如果块大小是2的指数,如果你要求较小的空间,那么realloc可能分配一个不同的块.
        在Unix中,realloca会清除原来的存储块,.GNU realloc 没有这个bug: 如果分配失败,原来的存储块不会被改变.放心地假设这个bug已经被修复了. 如果你需要在Unix上运行你的程序,并且希望避免由于分配失败而导致原来的存储块丢失,那么你可以使用GNU malloc.
        你必须明白,free会改变你释放的那些存储块.因此,你必须在free释放存储块之前,从存储块中取出你要的东西.
        如果在一个非交互性的程序中malloc失败,请报告一个严重错误. 在一个交互式程序中(从用户那里读取命令),最好取消这个命令然后返回到命令读取循环中.这允许用户结束其他进程来释放虚拟内存,然后重新尝试这个命令.
        使用getopt_long来解析参数,除非参数语法是这种方式不合理.
        在程序执行的过程中,如果要写入静态存储空间,请使用显式的C代码来初始化.对于那些不会改变的数据,保留C初始化过的声明.
        不要使用那些晦涩的Unix数据结构的地层接口(比如file directories,utmp,内核内存布局),因为它们很难有很好的兼容性.如果你需要列出一个目录中的所有文件,使用readdir 或者其他高级的接口. 这些都GNU很好地兼容.
        首选的信号处理工具是BSD变体的信号,和posix sigaction函数; 可选的USG信号处理接口是一个较次的设计.
        现在,让程序保持可移植的最简单的方法是使用posix signal函数.如果你在GNU libc 版本是1的GNU/Linux系统上使用signal,为了得到BSD的行为,你应当包含bsd/signal.h而不是signal.h.由你自己来决定是支持那些signal只有USG行为的系统还是放弃.
        在进行错误检查时如果检测到"不可能"的情况,直接退出.经常没有任何地方打出任何信息.这些检查指出了bug的存在.无论谁想修复这些bug都不得不阅读源码,运行调试器.因此在源码中对这个问题进行注释. 相关的数据保存在变量中,这些变量很容易由调试器进行调试,因此没有办法把这些bug移到别处.
        不要利用程序的退出状态值来表示错误数.那不能正常工作,因为退出状态值最多8位(0到256).一个进程就可能有256个错误.如果你试图把256作为退出状态值,父进程将会只能看到状态值为0,看起来像是程序已经成功.
        如果你创建临时文件,检查TMPDIR环境变量.如果这个变量定义了,那么使用它而不是/tmp.
        另外,要注意在都有写权限的临时文件夹创建临时文件时可能会引起安全问题.在C中,你可以通过下面的方法来创建临时文件以避免这个问题:
          fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
或者使用来自libiberty的mkstemps函数.
Next: Errors, Previous: Semantics, Up: Program Behavior
作,使用比如UTF-8或者其他的编码。
        除非你希望忽略错误,否则检查系统调用的返回值进行错误检查。错误信息要包含每一个失败的系统调用的错误消息的系统信息(从perror或者其他等效的地方获得),如果可能,也要包含文件名,和工具名字.仅仅给出"cannot open foo.c" 或者"stat failed "是不够的.
        每次都要检查malloc或者realloc是否返回了0,即使调用realloc来缩小内存块.一个系统里,如果块大小是2的指数,如果你要求较小的空间,那么realloc可能分配一个不同的块.
        在Unix中,realloca会清除原来的存储块,.GNU realloc 没有这个bug: 如果分配失败,原来的存储块不会被改变.放心地假设这个bug已经被修复了. 如果你需要在Unix上运行你的程序,并且希望避免由于分配失败而导致原来的存储块丢失,那么你可以使用GNU malloc.
        你必须明白,free会改变你释放的那些存储块.因此,你必须在free释放存储块之前,从存储块中取出你要的东西.
        如果在一个非交互性的程序中malloc失败,请报告一个严重错误. 在一个交互式程序中(从用户那里读取命令),最好取消这个命令然后返回到命令读取循环中.这允许用户结束其他进程来释放虚拟内存,然后重新尝试这个命令.
        使用getopt_long来解析参数,除非参数语法是这种方式不合理.
        在程序执行的过程中,如果要写入静态存储空间,请使用显式的C代码来初始化.对于那些不会改变的数据,保留C初始化过的声明.
        不要使用那些晦涩的Unix数据结构的地层接口(比如file directories,utmp,内核内存布局),因为它们很难有很好的兼容性.如果你需要列出一个目录中的所有文件,使用readdir 或者其他高级的接口. 这些都GNU很好地兼容.
        首选的信号处理工具是BSD变体的信号,和posix sigaction函数; 可选的USG信号处理接口是一个较次的设计.
        现在,让程序保持可移植的最简单的方法是使用posix signal函数.如果你在GNU libc 版本是1的GNU/Linux系统上使用signal,为了得到BSD的行为,你应当包含bsd/signal.h而不是signal.h.由你自己来决定是支持那些signal只有USG行为的系统还是放弃.
        在进行错误检查时如果检测到"不可能"的情况,直接退出.经常没有任何地方打出任何信息.这些检查指出了bug的存在.无论谁想修复这些bug都不得不阅读源码,运行调试器.因此在源码中对这个问题进行注释. 相关的数据保存在变量中,这些变量很容易由调试器进行调试,因此没有办法把这些bug移到别处.
        不要利用程序的退出状态值来表示错误数.那不能正常工作,因为退出状态值最多8位(0到256).一个进程就可能有256个错误.如果你试图把256作为退出状态值,父进程将会只能看到状态值为0,看起来像是程序已经成功.  
        如果你创建临时文件,检查TMPDIR环境变量.如果这个变量定义了,那么使用它而不是/tmp.
        另外,要注意在都有写权限的临时文件夹创建临时文件时可能会引起安全问题.在C中,你可以通过下面的方法来创建临时文件以避免这个问题:
          fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600);
或者使用来自libiberty的mkstemps函数.
4.3 库的行为
        尽力使库函数都可重入. 如果它们需要动态内存分配,至少要尽力让除了malloc以外的地方都重入.
        为了避免名字冲突,下面是一些库中标识符命名的约定.
        为库标识符选一个超过两个字符的前缀.所有的全局函数和变量名字都应该以这个前缀开始.另外,这些标识符名在一个库重应该是唯一的. 一般来说,把它们都放在单独的源文件中.(我的理解:比如malloc函数对应malloc.c,free函数对应free.c,不要把它们放在一个文件中,让文件系统来保证标识符的唯一.)
        如果两个全局标识符总是一起使用,那么可以例外.这样能使没有一个合法的程序能够使用一个而不用另外一个.所以,这两个标识符可以在一个文件中.
        全局标识符如果没有为用户提供文档来说明入口,就应该以'_'开头.以'_'开头的标识符也应该遵守这个库的前缀规则,以免和其他库冲突.如果你喜欢,这些标识符可以和其他用户接口放在一个文件中.
        静态函数和变量名怎么命名可以随你的喜好,不用遵守任何命名约定.
4.4 格式化错误信息
  编译器生成的错误消息可能如下:
          源文件:行号:消息
  如果你想给出列号,使用下面的这些格式:
  源文件:行号:列号:消息
  源文件:行号.列号:消息
        错误消息也可以同时给出错误的开始点和结束点.有几种格式可以让你避免给出重复的信息,比如重复的行号.下面是几种合理的格式:
        source-file-name:lineno-1.column-1-lineno-2.column-2: message  
     source-file-name:lineno-1.column-1-column-2: message  
     source-file-name:lineno-1-lineno-2: message  

当一个错误跨越几个文件时,可以使用下面的格式:
        file-1:lineno-1.column-1-file-2:lineno-2.column-2: message
对于非交互式程序的错误消息格式,如果能给出恰当的源文件,应该象下面这样:
   program:source-file-name:lineno: message  
   如果不能给出源文件:
      program: message  
     如果想给出列号,可以使用这种格式:
      program:source-file-name:lineno:column: message  
        在一个交互式的程序中(这种程序从终端中读取命令),错误消息中最好不要包含程序名.可以在提示符中或者屏幕布局中指明哪个程序在运行.(同样是这个程序,如果是从一个不是终端的源中读取输入时,它就是非交互式的,因此输出错误消息时最好采用非交互式风格.)
        当错误消息跟在一个程序名和(或者)一个文件名后面时,不要以大写字符开头,因为它不是一个句子的开始.(从概念上来说,句子的开头是在一行的开始处.),并且也不要以句号结尾.
        交互式程序的错误消息和其他象提示程序如何使用的消息,应该以大写字母开头,但不应该以句号结尾.
4.5 Standards for Interfaces Generally  
4.5 接口标准概要
不要让程序的行为依赖于启动时的名字.有时为程序建立一个连接来创建不同的名字是很有用的,但是不同的名字不能改变程序的行为.
        相反,应该使用一个选项或者编译开关,或者两者都提供来选择可选的行为.
        同样地,不要让程序的行为依赖于所使用的输出设备.系统设计的一个重要原则就是设备无关;不要为了让一些人偶尔少敲几个选项而进行妥协.(当使用终端时,错误消息格式的改变是可以接受的,因为这个问题人对人们来说是不必依赖的,而且也是不重要的.)
        如果你认为当输出是终端时,这种方式好,而输出是文件或者管道时,另一种方式好.那么一般来说,让对于输出是终端更好的那种方式作为缺省,然后为另一种方式提供一个选项.
        兼容性要求一些程序必须依赖于输出设备的类型. 那将会是一种灾难,如果ls或sh不按照用户所想的方式工作.有时侯,我们实现程序的较好的那个版本,那样就可以不依赖于输出设备的类型.比如,我们提供了dir,它很想ls,除了缺省的输出格式总是多列的.
4.6 Standards for Graphical Interfaces  
4.6        图形界面的标准
        如果你写的程序提供GUI的话,那么让他即能在X window下工作,也能在GTK+下工作.除非功能要求只能二者选其一(比如,"在控制台模式下显式jpeg图象").
        另外,提供命令行接口来控制功能.(许多情况下,GUI可以是单独的程序,它调用命令行程序来工作.)
        请考虑提供一个CORBA接口(为了能够在GNOME下使用),一个库接口(为C用户提供),或许还可能提供键盘驱动的控制台接口(为控制台模式的用户提供).一旦你完成了提供相应功能和GUI的工作,那么其他这些工作不会很多.
4.7 Standards for Command Line Interfaces   
4.7 命令行接口的标准  
        遵守posix关于命令行程序选项的约定是个不错的注意.这样做最简单的办法是使用getopt来分析选项.注意GNU版本的getopt,无论选项出现在参数中的哪个地方都可以正确处理除非使用特殊的参数'--'.这个不是posix指定的,而是GNU扩展.  
        请定义等价与unix风格的单字符选项的长选项. 我们希望GNU对用户更友好. 你可以用GNU函数getopt_long轻松地做到这一点.  
        长选项的一个好处是可以在各个程序之间保持一致. 比如, 用户可以期待所有的GNU程序都有"verbose"选项.并且准确地拼做'--verbose'. 为了达到统一,在选择程序的选项名字时,首先查找一下通用的长选项名字表.(看看选项表)  
        一般来说,将输入文件作为普通参数来对待是个不错的注意; 任何输出文件应该通过选项指定(最好是'-o'或者'--output').即使出于兼容性的需要允许了输出文件作为普通参数,也要把提供一个选项来指明输出文件作为另一种方式.这样能保证GNU工具更一致,让用户需要记得的东西更少.  
        所有的程序应该支持两个标准选项:'--version' 和 '--help'. CGI程序应该既把这些作为命令行选项,也要作为PATH_INFO 给出.比如,在浏览器中访问http://example.org/p.cgi/-help时,输出应该和在命令行下启动'p.cgi --help'一样.  
下面这一段以前几乎没有用到,怕翻译的词不达意,就没有翻译,
4.9 Memory Usage   
4.9 内存使用  
        如果一个程序一般都用几M的内存,就不要操心如何减少内存使用.比如,如果出于某种原因,操作超过几M的文件是不可能的,那么把整个文件读到内存中来操作就是合理的.  
        然而,对于象cat或者tail这样的程序,它能够处理大文件是很有用的,因此不使用那种人为地限制它能处理的文件的大小的技术是很重要的.如果一个程序以行为单位进行工作并且能够处理任意用户给定的文件,它应该只将一行保存在内存中,因为这个不困难,而且用户想处理的输入文件可能内存中一次放不下.  
        如果你的程序创建了复杂的数据结构,就让它们呆在内存中,如果malloc失败,那么直接给出一个严重的错误.  
4.10 File Usage   
4.10 使用文件
        程序应该对于/usr和/etc是只读文件系统这种情况做好准备.因此,如果程序管理日志文件,锁文件,备份文件,分数文件(score files),或者其他任何出于内部目的而编辑的文件,这些文件不应该保存在/usr或者/etc下.  
        有两种例外. /etc是保存系统信息的地方.如果一个程序的工作就是更新系统配置,那么编辑/etc下面的文件是合理的.并且,如果用户明确要求编辑一个目录下的某个文件,那么将其他文件放在这个目录下也是合理的.  
5 最好地使用C,这个标题翻译的太差了,一直想不到合适的.
5 Making The Best Use of C

这一章提供一些当你在写GNU软件时怎么才能最好地使用C语言的建议.
  * 格式: 如何格式化你的源码
  * 注释: 如何对你的工作做出注释
  * 语法上的约定: 如何干净地使用C的构造
  * 名字: 如何命名变量,函数,文件.
  * 系统兼容: 如何在不同系统之间保持可移植
  * CPU层可移植: 支持更多的CPU类型.
  * 系统调用: 可移植性与'标准'库函数
  * 国际化: 国际化的技术
  * 字符集: 缺省使用ASCII
  * 引用字符:在C locale中使用'...'
  * Mmap: 如何才能安全的使用mmap
5.1 格式化你的源码
将标志着C函数体开始的左花括号放在一行的开头是很重要的,并且不要把其他的左花括号,做圆括号或者方括号放在一行的开头. 好几种工具通过位于一行的开头的左圆括号来查找C函数的开始.这些工具将不能正常工作,如果没有使用那样的格式.
函数定义时将函数名放在一行的开头也是很重要的. 这能够帮助用户查找函数的定义,也能帮助一些工具识别函数. 因此,使用标准C的语法,格式如下标准C语法,格式如下:
     static char *
     concat (char *s1, char *s2)
     {
       ...
     }
或者,你想使用
    static char *
     concat (s1, s2)        /* Name starts in column one here */
          char *s1, *s2;
     {                     /* Open brace in column one here */
       ...
     }
在标准C中,如果参数在一行中放不下,可以分开成下面的:

     int
     lots_of_args (int an_integer, long a_long, short a_short,
                   double a_double, float a_float)
     ...
接下来,我们给出了C语言格式其他方面我们推荐的风格,这也是indent 在1.2或者
更新的版本缺省的风格. 它对应以下选项:
     -nbad -bap -nbc -bbo -bl -bli2 -bls -ncdb -nce -cp1 -cs -di2
     -ndj -nfc1 -nfca -hnl -i2 -ip5 -lp -pcs -psl -nsc -nsob
        我们不认为这些推荐就是必须的,因为对于用户来说,不同的程序使用不同的风格不会引起任何问题.
        但是无论使用那种风格,请保持一致,因为一个程序中有多种风格,将会使程序
看起来丑陋.如果你修改一个现有的程序,请遵照程序原来的风格.
        对于函数体,我们推荐的风格是:
    if (x < foo (y, z))
       haha = bar[4] + 5;
     else
       {
         while (z)
           {
             haha += foo (z, z);
             z--;
           }
         return ++x + bar ();
       }
我们发现左圆括号前边和逗号后边留空将会使程序易读性更好,特别是逗号后面留有空白.
当你将一个表达式分到多行时,在操作符前分行而不要在后面. 下面是正确的方法:
             if (foo_this_is_long && bar > win (x, y, z)
         && remaining_condition)
不要让不同优先级的两个操作符出于相同的缩进. 比如,不要这样写:
    mode = (inmode[j] == VOIDmode
             || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])
             ? outmode[j] : inmode[j]);
相反,多用几个圆括号来让缩进来显示层次:
     mode = ((inmode[j] == VOIDmode
              || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])))
             ? outmode[j] : inmode[j]);
插入多余的圆括号可以使Emacs更恰当地缩进代码. 比如,如果你手动书写的,下面的缩进看起来很好,
     v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
         + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000;
但是Emacs将改变它.添加一对圆括号看起来同样美观,并且Emacs将会保持它们:
     v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
          + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000);
象下面这样格式化do-while语句:

     do
       {
         a = foo (a);
       }
     while (a > 0);
请使用进纸符(control-L)在合理的地方(不是在函数内部)将程序分页. 不要管一页有多长,因为它们不用切合实际的纸张.进纸符应该独占一行.
5.2 对你的工作进行注释
        每一个程序一上来就应该给出一个简洁的注释来说明它是用来干什么的. 比如: 'fmt -filter for simple filling ot text'. 这短注释应该位于包含main函数的源文件的顶部.也应该在每一个源文件头部写一段简洁的注释,包含文件名和一两行来说明文件的大致用途.
        请用英语书写GNU程序的注释,因为几乎所有国家的程序员都能读懂. 如果你英语写的不好,请用尽可能好的英语来书写注释,然后找其他人再写一遍. 如果你不能写英语注释,那么找其他人和你一块儿工作,然后将你的注释翻译为英语.
        请在每一个函数旁都放一段注释来说明函数是干什么的,需要什么样的参数,参数都有那些可能的值,用来干什么. 不要重复表达C参数声明就能表达的意思,如果一个C类型是按照常规的用法来使用. 如果有什么非标准的用法(比如一个char *类型的参数指向的却是一个字符串第二个字符的地址而不是第一个,或者任何可能的不能获得预期效果的值 (比如,包含换行符的字符串就不能保证工作),一定要写出来.如果有返回值,一定要解释返回值的意义.
        请在一句注释的结尾放两个空格,好让Emacs sentences 命令能够工作.也请写下完整的句子,并且第一个字母大写. 如果一个小写的标识符位于句首,不要改成大写.改变拼写将会使它成为另外一个标识符.如果你不喜欢句首释疑个小写字母,那么用另一种方式来表达(比如,"这个标识符的小写是...").
        如果使用参数名字来表达参数值将会使函数的注释更清晰.参数名字本身应该是小写,但是当你要表达参数值而不是参数本身时请用大写字母. 因此,使用" the inode number NODE_NUM"而不是用"an inode".
        一般来说,在注释中重新表述函数名字没有任何意义,因为读者能够自己去看函数名字是什么.除非注释太长了而让函数本身出于屏幕底部.
        对于每一个静态变量都应该象这样注释:
          /* Nonzero means truncate lines in the display;
        zero means continue them.  */
     int truncate_lines;
     每一个'#endif'都应该有注释,除非条件语句很短(仅仅几行)并且没有嵌套.注释表明条件语句的结束,并且指明这段的意义.'#else'应该有一段注释来描述接下来的代码的条件和意义. 比如:
     
     #ifdef foo
       ...
     #else /* not foo */
       ...
     #endif /* not foo */
     #ifdef foo
       ...
     #endif /* foo */
但是,相反,对'#ifndef'这样注释
      #ifndef foo
       ...
     #else /* foo */
       ...
     #endif /* foo */
     #ifndef foo
       ...
     #endif /* not foo */
5.3 Clean Use of C Constructs
        请明确地声明所有对象的类型.比如,你应该明确地声明函数的所有参数,你应该声明函数的返回类型是int而不是省略掉.
        一些程序员喜欢使用GCC的'-Wall'选项,改变所有产生警告的代码.如果你想这样做,你可以做. 其他的程序员不想使用'-Wall',因为对于那些他们不想改变的合法的,有效的代码也产生警告.如果你想这样做,你可以做.编译器只是你的仆人而不是主人.
        外部函数的声明和出现在源文件后面的函数都应该一起出现在接近文件开头的一个地方(文件中第一个函数定义的前面的某个地方),或者都放在一个头文件中.不要把外部声明放在函数内部.
        在实践中,一个函数内的同一个局部变量(名字象tem之类)一用再用地用做不同的用途.更好的用法是,为每一个不同的用途声明一个单独的局部变量,并且起一个有意义的名字.这不仅使程序易读,而且能够帮助好的编译器进行更好的优化.你也可以将每一个局部变量的声明放到一个包含它所有使用的域内.这也能是程序更加清晰.
        不要使用局部变量或参数来覆盖全局变量.
        不要在一次声明中跨行声明多个变量.新的一行中要是新的声明,例如,不要这样:

     int    foo,
            bar;
可以这样写:

     int foo, bar;
或者
   int foo;
   int bar;
如果是全局变量,每一个变量前只要要有一句注释.
如果一个if-else语句嵌在另外一个if语句中,总是用花括号将if-else括起来.因此,永远不要这样写代码:
     if (foo)
       if (bar)
         win ();
       else
         lose ();
要这样写:
   if (foo)
       {
         if (bar)
           win ();
         else
           lose ();
       }
如果一个if语句嵌套在另外一个else语句中,或者将else if写在一行中,象下面:
     if (foo)
       ...
     else if (bar)
       ...
或者把嵌套的if写到好括号内:
     if (foo)
       ...
     else
       {
         if (bar)
           ...
       }
        不要把structure的声明和变量声明或typedefs放到一块儿.相反,应该单独地声明structure,然后使用它来声明变量或进行typedefs.
        不要在if的条件中进行赋值操作,比如,不要这样写:
    if ((foo = (char *) malloc (sizeof *foo)) == 0)
       fatal ("virtual memory exhausted");
相反,应该这样:

     foo = (char *) malloc (sizeof *foo);
     if (foo == 0)
       fatal ("virtual memory exhausted");
        不要让程序看起来丑陋.不要对void进行任何强制转换.0不用任何转换就可以很完美地当作NULL常量使用,除了当调用一个变参函数时(比如,printf,译者注).
5.4 命名变量,函数和文件
        程序中全局变量和函数的名字可以作为另一种形式的注释,因此不要选择简洁的名字--相反,要为变量或函数起一个能够给出有用信息的名字.在GNU程序中,名字应该用英语,象其他注释一样.
        局部变量的名字可以短一点,因为它们只在一个上下文下使用,并且(大概地)注释也解释了它们的用途.
        尽量限制符号名字中的缩写的数量.对于少量的缩写,解释它们的意思,然后频繁地使用是很好的,但是不要使用很多的模糊的缩写.
        请使用下划线来分隔一个名字中的多个单词,好让Emacs单词命令能够很好地处理. 要坚持小写字母;为宏和enum常量名字保留大写字母,对于名字前缀要遵照一个统一的约定.比如,你可以这样使用名字:ignore_space_change_flag;不要象这样使用名字:iCantReadThis.
        用于判断命令行选项是否给出的变量应该以选项的意义命名,而不是选项本身.注释中不仅要给出选项的确切意义而且要给出选项本身.比如,
     /* Ignore changes in horizontal whitespace (-b).  */
     int ignore_space_change_flag;
        当你定义整数常量时,用enum而不是'#define'.GDB知道枚举常量.
        MS-DOS文件系统会截断文件明,你可能想确认文件装入时不会发生名字冲突. 你可以使用doschk程序来测试.
        一些GNU程序设计时就限制稳健名的长度不超过14个字符,这是为了避免文件放到老的System V系统时发生名字冲突.对于现有的使用这种特性的GNU程序,请保留这种特性,但是对于新的GNU程序就没有必要这样做.doschk也能报告长与14个字符的文件名.
        5.5 各系统间的可移植性
        在unix世界中,'可移植性'一词是说可以移植到不同版本的unix上.对于一个GNU程序来说,这种可移植性是必要的,但是并不是最高需求.
        GNU软件是为了能够用GNU C编译器编译后,可以在GUN内核上运行,在各种cpu上运行.因此上面提到的可移植性是必要的,可是又太局限.但是支持基于linux的GNU系统是很重要的,因为这种是流行的GNU系统.除了这个,能够支持其他的开源系统(各种BSD系统)更好,如果你想,支持其他的类unix系统就最好了.支持各种类unix系统是值得的,虽然不是最重要的.一般来说,也不是很困难,因此你最好那样做.但是你也不必把这个当作义务,如果实现这种支持很困难的话.
        想要支持累unix系统最简单的方法就是使用Autoconf.你的程序所需要知道的目标平台的信息不可能超所Autoconf所能提供的,就因为绝大多数程序需要的信息已经给出来了.
        当有更高层次的选择(readdir)时,尽量避免使用半内部的数据结构(比如,目录结构).
        对于那些和unix无关的系统,比如MSDOS,Windows,VMS,MVS,和一些老的Macintosh系统,支持它们通常需要很多的工作.如果是那样,与其支持这些不兼容的系统,不如花这些时间来为GNU和GNU/Linux系统添加一些有用的特性.
        如果你支持Windows,请不要缩写为"win".在黑客术语中,称呼"win"是一种赞扬.如果想,你可以在你自己的程序中自由地赞扬Microsoft Windows,但是不要在GNU软件包中这样做.除了将"Windows"缩写为"un",你可以写出全称或者缩写为"woe"或"w".比如,在GNU Emacs中,在Windows特定的文件中,我们使用"w32",但是对于Windows相关的宏,我们使用WINDOWSNT.
        在编译你的C文件时,定义"特性测试宏"_GNU_SOURCE是个好注意.当你在GNU和GNU/Linux上编译时,这将会允许GNU库扩展函数的声明. 但是如果在你的程序中,以其他方式定义相同的函数名字,将会导致编译产生错误消息.(如果你想让程序在其他系统上具有更可移植,你实际上可以不使用这些函数.)
        但是,无论你是否使用GNU扩展,不要把这些名字挪做他用.这样做将会使你的代码很难移植到其他GNU系统上.
5.6 在各种cpu上保持可移植
发表于 2006-2-18 18:08:57 | 显示全部楼层
辛苦了。。。
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-2-19 09:55:30 | 显示全部楼层
5.6 在各种cpu上保持可移植
        由于cpu的不同,甚至各个GNU系统也不尽相同.比如,字节顺序的不同和内存对齐的要求不一样.处理这些差异是完全必要的.然而,不要试图考虑支持int小于32位的cpu.在GNU中,我们不支持16位的cpu.
        类似地,不要试图考虑long长度小于预定义的size_t类型.比如,下面的代码就很好:
     printf ("size = %lu\n", (unsigned long) sizeof array);
     printf ("diff = %ld\n", (long) (pointer2 - pointer1));
C89要求这样能够工作,并且我们知道唯一的例外是:Microsoft Windows上的64位程序.我们把它留给那些想把GNU程序移植到这种环境上的人,由他们来决定如何做.
        不要假定int对象的地址也就是它最小的那个字节的地址.在big-endian的机器上不是这样的.因此,不要犯这样的错误:
    int c;
     ...
     while ((c = getchar()) != EOF)
       write(file_descriptor, &c, 1);
         当传递参数给函数时,一般不用担心指针和整数的不同.在绝大多数64位系统上指针比int宽.相反,在大多数32位机器上,象long long int和off_t这样的整数类型比指针宽.因此现在使用原型来定义函数比较好,并且参数类型也很重要.
        特别地,如果函数参数个数或者类型可变,那么声明的时候应该是用含有'...'的原型,还要引用stdarg.h.比如,可以看Gnulib的错误处理模块,它声明和定义了下面的函数:

     /* Print a message with `fprintf (stderr, FORMAT, ...)';
        if ERRNUM is nonzero, follow it with ": " and strerror (ERRNUM).
        If STATUS is nonzero, terminate the program with `exit (STATUS)'.  */

     void error (int status, int errnum, const char *format, ...);
使用Gnulib错误处理模块最简单的方法就是从Gnulib库中获取两个源文件error.c和error.h.源码位置在 http://savannah.gnu.org/cgi-bin/ ... 面是一个示例:

     #include "error.h"
     #include <errno.h>
     #include <stdio.h>

     char *program_name = "myprogram";

     FILE *
     xfopen (char const *name)
     {
       FILE *fp = fopen (name, "r");
       if (! fp)
         error (1, errno, "cannot read %s", name);
       return fp;
     }
如果能够,尽量不要把指针转换为整数.这中转换大大降低了可移植性,在大多数情况下,这种情况很容易避免.有时侯,这种转换是必要的--比如,Lisp解释器把类型信息和地址存放在一个字中--有将不得不明确地处理不同的字大小.你也不得不处理那些系统中malloc返回的正常地址范围开始处远远大于0的情况.
5.7 调用系统函数
回复 支持 反对

使用道具 举报

发表于 2006-2-19 13:03:20 | 显示全部楼层
不错,不错. 为啥不发到程序设计版?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-2-21 21:56:52 | 显示全部楼层
Post by 弥敦路九号
不错,不错. 为啥不发到程序设计版?
这个不是程序设计版吗?
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-2-21 22:05:40 | 显示全部楼层
5.7 调用系统函数
        各种的C语言实现差异很大.标准C减少了但是没有根除这些不兼容性.同时,许多GNU软件包都支持原始C编译器,因为这样做并不困难.这章将向你推荐一些如何使用大体上的标准C库函数以避免不必要的不可以移植的方法.
        *不要使用sprintf的返回值.在一些系统上它返回写入的字符的个数,但是并不是所有的系统.
        *要知道vfprintf并不总是可用的.
        *main应该声明返回值为int.它应该调用exit结束,或者返回一个整型状态码.确保main从不返回一个不确定的值.
        *不要显式地声明系统函数.
        在有些系统上,几乎所有的系统函数的声明都是错误的.为了最小化冲突,把声明系统函数的任务放在系统头文件中去.如果头文件中没有声明某个函数,那么就让它没有声明吧.
        虽然不声明这样看起来有些不合适,在那些没有声明函数的系统上,对于绝大多数系统库函数这样做实际上工作得很好.因此,缺点只是理论上的,实际上声明经常引起冲突.
        * 如果你必须声明一个系统函数,不要指定参数类型.使用原始C风格的声明,而不是标准C的原型.对函数声明的越确切,越容易引起冲突.
        *特别地,不要条件声明malloc或realloc.绝大多数GNU程序只使用这些函数一次,都是方便地使用xmalloc和xrealloc.这些函数各自调用malloc和realloc,并且检查返回值.因为xmalloc和xrealloc是在你的程序中定义的,你可以在其他文件中声明,不至于有任何引起冲突的危险.
        在大多数系统上,int长度和指针一样,因此,调用malloc和realloc会正常工作.对于一些少见的特列(大多数64位机器),你可以害死用条件声明malloc和realloc--或者把这些声明放在系统相关的配置文件中.
        *字符串函数需要特别的处理.一些Unix系统有一个string.h头文件;其他的有strings.h头文件.没有一个头文件是可移植的.有两种方法可以解决问题:使用Autoconf来决定引用哪个头文件,或者一个都不引用.如果
        *如果你哪个头文件都没有引用,你就不能通过头文件来获取字符串函数的声明. 那样引起的问题比你想象的要小.无论如何,较新的字符串函数应该避免使用,因为许多系统仍然不支持他们.你可以使用的字符串函数如下:
                strcpy   strncpy   strcat   strncat
                strlen   strcmp    strncmp
                strchr   strrchr
        复制和连接函数不用声明就能正常工作,只要你不使用它们的值(values).不声明就使用它们的值将会在指针宽度和int不一样的系统上发生错误,也许在其他系统上也会发生错误.使用那些值没有多大用处,因此不要使用就得了.
        比较和strlen函数在大多数系统上不用声明就可以正常工作,可能所有的GNU软件能跑得系统都是这样.你也许觉得在一些系统上值得对它们采用条件声明.
        搜索函数必须声明为返回char*.幸运的是它们的返回类型没有不一样的.但是有的系统名字不一样.有些系统把这些函数叫做index和rindex;而其他的叫做strchr和strrchr.一些系统这两对名字都支持,但是没有一对能够在所有的系统上工作.
        你应该选定一对函数名字,然后在你的整个程序中一直使用.(现在,在新程序中最好使用strchr和strrchr,因为它们是标准名字.).声明这些函数的返回类型是char *.在那些不支持这些名字的系统上,把它们定义为另一对的宏.比如,如果你想到处使用strchr和strrchr,刻意把下面的这些放在文件的开头(或者头文件中):

                #ifndef HAVE_STRCHR
                #define strchr index
                #endif
                #ifndef HAVE_STRRCHR
                #define strrchr rindex
                #endif
               
                char *strchr ();
                char *strrchr ();
           
这里我们假定系统定义HAVE_STRCHR和HAVE_STRRCHR意味着对应的函数存在.一种让它们恰当地定义的方法是使用Autoconf.
5.8 国际化
        GNU有一个库叫做GNU gettext. 这个库可以方便将一个程序中的消息转换为各种语言.你应该在每个程序中都使用这个库.在程序中,这些消息使用英语来显示,让gettext来提供将它们翻译为其他语言的方法.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2006-3-2 20:34:22 | 显示全部楼层
5.8 国际化
GNU有一个库叫做GNU gettext. 这个库可以方便将一个程序中的消息转换为各种语言.你应该在每个程序中都使用这个库.在程序中,这些消息使用英语来显示,让gettext来提供将它们翻译为其他语言的方法.
使用GNUgettext就是通过gettext宏来获取需要翻译的字符串--象这样:
printf(gettext("rocessing file `%s`..."));
这允许GNUgettext将字符串"rocessing file `%s`..."替换为另一种译本.
一旦使用GNU gettext,当你添加新的字符串的时候,请为调用gettext留出地方.
在软件包中使用GNUgettext需要为这个软件包指定一个文本域名(text damain name). 文本域名用于将一种译本(translations)和另一种译本区分开来.一般来说,文本域名应该和软件包名字一致--比如,gnu的文件实用工具 'fileutils'.
为了能让gettext很好地工作,不要在写代码时对单词或者语句结构做出假设.当你你想让一个语句的文本显示依赖于数据时,使用两个或者更多的可供选择的完整语句,而不是在一个语句中插入根据具体运行时环境而可变的单词或者短语.
下面就是一个不要使用的例子:
printf ("%d file%s processed", nfiles,
nfiles != 1 ? "s" : "");
这个例子的问题就是在于假定复数就是加's'. 如果你使用gettext来格式化这串字符,如下,
printf (gettext ("%d file%s processed"), nfiles,
nfiles != 1 ? "s" : "");
这段消息可以使用不同的单词,但是将复数仍然使用加's'.下面的是一种较好的方式:
printf ((nfiles != 1 ? "%d files processed"
: "%d file processed"),
nfiles);
这种方式,你可以对每一个字符串独立地使用gettext:

printf ((nfiles != 1 ? gettext ("%d files processed")
: gettext ("%d file processed")),
nfiles);
这样无论单词file的复数形式是那种(译注:相对于各种语言而言),同时对于那些需要对于单词"processed"特别对待的语言也能很好地处理 (also handles languages that require agreement in the word for "processed".)
在句子结构层也有一个相似的问题,如下:

printf ("# Implicit rule search has%s been done.\n",
f->tried_implicit ? "" : " not");
为这段代码添加gettext不能在所有的语言上都正常工作,因为对于否定在一些语言中,有些语言需要添加的单词不止一个.相比之下,象下面这样写代码将会使gettext能够正确地处理:

printf (f->tried_implicit
? "# Implicit rule search has been done.\n",
: "# Implicit rule search has not been done.\n");
5.9 字符集
在GNU源码注释,文档,以及其他环境中最好完全使用ASCII字符集(纯文本,7位的字符),除非有关于程序的区域的更好的理由.比如,如果源码处理法国大革命的日历,可以使用字面上还有重音的字符,象“Floréal”这样的月份名字.在修改日志中,使用非ASCII字符来显示捐助者的名字也是可以的.
如果使用非ASCII字符,你理所当然应该完全使用一种编码,因为一般不能可靠地混合使用编码.(as one cannot in general mix encodings reliably.)
5.10 引号字符
在C locale下,对于显示给用户的引号字符,GNU程序应该坚持使用无格式的ASCII字符:最好左引号使用0x60(‘`’),右引号使用0x27(‘'’).可以,但是不是必须要在其他locale中使用locale相关的引号.
Gnulib的quote和quotearg模块对其他locale中支持locale相关的引号提供了合理又直接的方式,同时也考虑了其他问题,比如引用一个文件名,但是文件名本身含有引号. 用法细节可以阅读Gnulib文档.
无论那种情况,如果你的程序引号用法和通用的‘`’ 和 ‘'’不一样,那么文档应该清楚地说明它如何引用.如果你的程序的输出结果可能被其他程序处理,这就显得尤其重要了.
这次引号是计算机世界里的一个难点:在拉丁语中根本没有真正的左右引号之分;我们标准化了的‘`’ 在那里是一个重音号.并且拉丁语还通用.(still not universally usable.).
Unicode编码包含所需的无二义的引号,并且它的通用编码UTF-8也与拉丁语向上兼容.然而,Unicode和UTF-8也没有被普遍支持.
5.11 Mmap
不要假定mmap在所有文件上都能或者不能工作. 它在一些文件上可能工作,在一些文件上可能不工作.恰当的方式是,在你想使用mmap的特定文件上尝试使用,如果不行,就回到原来的读写文件的方式.
采取这种措施的原因是GNU内核(HURD)提供了用户可扩展的文件系统,在这个文件系统上,可能有很多不同的"普通文件".它们许多支持mmap,但是也有一下额不支持.程序能够处理所有的这些文件是很重要的.
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表