Recommended C Style and Coding Standards中文翻译版

 更新时间:2014年04月28日 11:45:23   作者:  
本文翻译自Recommended C Style and Coding Standards(C语言编码风格和标准),需要的朋友可以参考下


16. 可移植性

    "C语言结合了汇编的强大功能和可移植性" — 无名氏,暗指比尔.萨克。

可移植代码的好处是有目共睹的。这一节将阐述一些编写可移植代码的指导原则。这里"可移植的"是指一个源码文件能够在不同机器上被编译和执行,其 前提仅仅是在不同平台上可能包含不同的头文件,使用不同的编译器开关选项罢了。头文件包含的#define和typedef可能因机器而异。一般 来说,一个新"机器"是指一种不同的硬件,一种不同的操作系统,一个不同的编译器,或者是这些的任意组合。参考1包含了很多关于风格和可移植 性方面的有用信息。下面是一个隐患列表,当你设计可移植代码时应该考虑避免这些隐患:

    * 编写可移植的代码。只有当被证明是必要的情况下才考虑优化的细节。优化后的代码往往是模糊不清、难以理解的。在一台机器上经过优化后的代码,在其他机器上 可能变得更加糟糕。将采用的性能优化手段记录下来并尽可能多地本地化。文档应该解释这些手段的工作原理以及引入它们的原因(例如:"循环执行了无 数次")

    * 要意识到很多东西天生就是不可移植的。比如处理类似程序状态字这样的特定硬件寄存器的代码,以及被设计用于支持某特定硬件部件的代码,诸如汇编器以及 I/O驱动。即使在这种情况下,许多例程和数据仍然可以被设计成机器无关的。

    * 组织源文件时将机器无关与机器相关的代码分别放在不同文件中。之后如果这个程序需要被移植到一个新机器上时,我们就可以很容易判断出来哪些需要被改变。为 一些文件的头文件中机器依赖相关的代码添加注释。

    * 任何"实现相关"的行为都应该作为机器(编译器)依赖对待。假设编译器或硬件以一种十分古怪的方式实现它。

    * 注意机器字长。对象的大小可能不直观,指针大小也不总是与整型大小相同,也不总是彼此大小相同,或者可相互自由转换。下面的表中列举了C语言基本类型在不 同机器和编译器下的大小(以bit为单位)。

复制代码 代码如下:

type    pdp11  VAX/11 68000  Cray-2 Unisys Harris 80386
        series       family          1100   H800
char     8      8      8       8      9     8      8
short    16    16     8/16   64(32)   18    24    8/16
int      16    32     16/32  64(32)   36    24    16/32
long     32    32      32    64       36    48    32
char*    16    32      32    64       72    24    16/32/48
int*     16    32      32    64(24)   72    24    16/32/48
int(*)() 16    32      32    64       576   24    16/32/48

有些机器针对某一类型可能有不止一个大小。其类型大小取决于编译器和不同的编译期标志。下面表展示了大多数系统的"安全"类型大小。无符号与带符 号数具有相同的大小(单位:bit)。

复制代码 代码如下:

Type    Minimum  No Smaller
          # Bits   Than
char       8 
short      16    char
int        16    short
long       32    int
float      24 
double     38    float
any *      14 
char *     15    any *
void *     15    any *

    * void类型可以保证有足够位精度来表示一个指向任意数据对象的指针。void()()类型可以保证表示一个指向任意函数的指针。当你需要通用指针时 可以使用这些类型(在一些旧的编译器里,分别用char和char()()表示)。确保在使用这些指针类型之前将其转换回正确的类型。

    * 即使说一个int和一个char类型大小相同,它们仍可能具有不同的格式。例如,下面例子在一些sizeof(int)等于 sizeof(char)的机器上可能失败。其原因在与free函数期望一个char,但却传入了一个int。

复制代码 代码如下:

    int *p = (int *) malloc (sizeof(int));
   free (p);

    * 注意,一个对象的大小不能保证这个对象的精度。Cray-2可能使用64位来存储一个整型,但一个长整型转换为一个整型并且再转换回长整型后可能会被截断 为32位。

    * 整型常量0可以强制转型为任何指针类型。转换后的指针称为对应那个类型的空指针,并且与那个类型的其他指针不同。空指针比较总是与常量0相当。空指针不应 该与一个值为0的变量比较。空指针不总是使用全0的位模式表示。两个不同类型的空指针有些时候可能不同。某个类型的空指针被强制转换为另外一个类 型的指针,其结果是该指针转换为第二个类型的空指针。

    * 对于ANSI编译器,当两个类型相同的指针访问同一块存储区时,则它们比较是相等的。当一个非0整型常量被转换为指针类型时,它们可能与其他指针相等。对 于非ANSI编译器,访问同一块存储区的两个指针比较可能并不相同。例如,下面两个指针比较可能相等或不相等,并且他们可能或可能没有访问同一块 存储区域。

复制代码 代码如下:

    ((int *) 2 )
    ((int *) 3 )

如果你需要'magic'指针而不是NULL,要么分配一些内存,要么将指针视为机器相关的。

复制代码 代码如下:

extern int x_int_dummy;    /* in x.c */
#define X_FAIL    (NULL)
#define X_BUSY    (&x_int_dummy)
#define X_FAIL    (NULL)
#define X_BUSY    MD_PTR1  /* MD_PTR1 from "machdep.h" */

    * 浮点数字既包含精度也包含范围。这些都是数据对象大小无关的。但是,一个32位浮点数在不同机器上溢出时的值有所不同。同时,4.9乘以5.1在不同的机 器上可能产生两个不同的数字。在圆整(rounding)和截断方面的差异将给出特别不同的答案。

    * 在一些机器上,一个双精度浮点数在精度或范围方面可能比一个单精度浮点数还要低。

    * 在一些机器上,double值的前半部分可能是一个具有相同值的float类型。千万不要依赖于此。

    * 提防带符号字符。例如,在某些VAX系统上,用在表达式中的字符是符号扩展的,但在其他一些机器上并非如此。对有符号和无符号有依赖的代码是不可移植的。 例如,如果假设c是正值,arrayc在c为有符号且为负值时将无法正常工作。如果你一定要假设signed或unsigned字符的话,请 用SIGNED或UNSIGNED为其加上注释。无符号字符的行为可由unsigned char保证。

    * 避免对ASCII做假设。如果你必须假设,那么请将其记录下来并本地化。请记住字符很可能用不止8位表示。

    * 大多数机器采用2的补码表示数,但我们在代码中不应该利用这一特点。使用等价移位操作替代算术运算的优化尤其值得怀疑。如果必须这么做,那么机器相关的代 码应该用#ifdef定义,或者操作应该在#ifdef宏判定下执行。你应该衡量一下使用这种难以理解的代码所节省的时间与做代码移植时找bug 所花费的时间相比孰多孰少。

    * 一般情况下,如果字长或值范围非常重要,应该使用typedef定义具有特定大小的类型。大型程序应该具有一个统一的头文件用于提供通用的、大小 (size)敏感的类型的typedef定义,这样更加便于修改以及在紧急修复时查找大小敏感的代码。无符号类型比有符号整型更加编译器无关。如 果既可以用16bit也可以用32bit标识一个简单for循环的计数器,我们应该使用int。因为对于当前机器来说,通过整型可以获取更高效 (自然)的存储单元。

    * 数据对齐也很重要。例如,在不同的机器上,一个四字节的整型数的可能以任意地址作为起始地址,也可能只允许以偶数地址作为起始地址,或者只能以4的整数倍 的地址作为起始地址。因此,一个特定的结构体的各个元素在不同的机器上的偏移量有不同,即使给定的这些元素在所有机器上的大小相同。事实上,一个 包含一个32位指针和一个8位字符的结构提在三个不同的机器上可能有三个不同的大小。作为一个推论,对象指针可能无法自由互换;通过一个指向起始 地址为奇数地址长度为4个字节的指针保存一个整型数有时可以正常工作,但有时则会导致产生core,有些时候静悄悄地失败了(在这个过程中会破坏 其他数据)。在那些不按字节寻址的机器上,字符指针更是"事故高发地区"。对齐考虑以及加载器的特殊性使得很容易轻率地认为两个连续声明的变量在 内存中也是连在一起的,或者某个类型的变量已经被适当对齐并可以用作其他类型变量使用了。

    * 在一些机器上,诸如VAX(小端),一个字的字节随着地址的增加,其重要性提高;而另外一些机器上,诸如68000(大端),随着地址的增加,其重要性降 低。字或更大数据对象(诸如一个双精度字)的字节顺序可能并不相同。因此,任何依赖对象内从左到右方向位模式的代码都值得特别细致的审查。只有当 结构体中两个不同的位字段不被连接以及不被当作一个单元时,这些位字段才具备可移植性。事实上,连接任意两个变量都是不可移植的行为。

    * 结构体中有一些未使用的空洞。猜想联合体用于类型欺骗。尤其是,一个值不应该在存储时使用一个类型,而在读取时使用另外一种类型。对联合体来说,一个显式 的标签(tag)字段可能会很有用。

    * 不同的编译器在返回结构体时使用不同的约定。这就会导致代码在接受从不同编译器编译的库代码中返回的结构体值时会出现错误。结构体指针不是问题。

    * 不要假设参数传递机制。特别是指针大小以及参数求值顺序,大小等。例如,下面的代码就不具备可移植性。

复制代码 代码如下:

        c = foo (getchar(), getchar());

    char
    foo (c1, c2, c3)
    char c1, c2, c3;
    {
        char bar = *(&c1 + 1);
        return (bar);            /* often won't return c2 */
    }

    * 上面的例子有诸多问题。栈可能向上增长,也可能向下增长(事实上,甚至都不需要一个栈)。参数在传入时可能被扩大,例如一个char可能以int型被传 入。参数可能以从左到右,从右到左,或以任意顺序压入栈,或直接放在寄存器中(根本无需压栈)。参数求值的顺序也可能与压栈的次序有所不同。一个 编译器可能使用多种(不兼容的)调用约定。

    * 在某些机器上,空字符指针((char *)0)常被当作指向空字符串的指针对待。不要依赖于此。

   *  不要修改字符串常量。下面就是一个臭名昭著的例子

复制代码 代码如下:

    s = "/dev/tty??";
  strcpy (&s[8], ttychars);

    * 地址空间可能有空洞。简单计算一个数组中未分配空间的元素(在数组实际存储区域之前或之后)的地址可能会导致程序崩溃。如果这个地址被用于比较,有时程序 可以运行,但会破坏数据,报错,或陷入死循环。在ANSI C中,指向一个对象数组的指针指向数组结尾后的第一个元素是合法的,这在一些老编译器上通常是安全的。不过这个"在外边"不可以被解引用。

    * 只有==和!=比较可用于某给定类型的所有指针。当两个指针指向同一个数组内的元素(或数组后第一个元素)时,使用<<、<=、& amp; gt;或>=对两个指针进行比较是可移植的。同样,仅仅对指向同一个数组内的元素(或数组后第一个元素)的两个指针使用算术操作符才是可移 植的。

    * 字长(word size)也影响移位和掩码。下面代码在一些68000机器上只会将一个整型数的最右三个位清0,而在其他机器上它还会将高地址的两个字节清零。x &= 0177770 使用 x &= ~07可以在所有机器上正常工作。位字段(bitfield)没有这些问题。

    * 表达式内的副作用可能导致代码语义是编译器相关的,因为在大多数情况下C语言的求值顺序是没有显式定义的。下面是一个臭名昭著的例子:

复制代码 代码如下:

    a[i] = b[i++];

    在上面的例子中,我们只知道b的下标值没有被增加。a的下标i值可能是自增后的值也可能是自增前的值。

复制代码 代码如下:

    struct bar_t { struct bar_t *next; } bar;
    bar->next = bar = tmp;

在第二个例子中,bar->next的地址很可能在bar被赋值之前被计算使用。

复制代码 代码如下:

bar = bar->next = tmp;

第三个例子中,bar可能在bar->next之前被赋值。虽然这可能有悖于"赋值从右到左处理"的规则,但这确是一个合法的解析。考虑下 面的例子:

复制代码 代码如下:

long i;
short a[N];
i = old
i = a[i] = new;

赋给i的值必须是一个按照从右到左的处理顺序进行赋值处理后的值。但是i可能在ai被赋值前而被赋值为"(long) (short)new"。不同编译器作法不同。

    * 质疑代码中出现的数值(“魔数”)。

    * 避免使用预处理器技巧。一些诸如使用/ /粘和字符串以及依赖参数字符串展开的宏会破坏代码可靠性。

复制代码 代码如下:

    #define FOO(string)    (printf("string = %s",(string)))
    …
  FOO(filename);

只是在有些时候会扩展为

复制代码 代码如下:

 (printf("filename = %s",(filename)))

小心。诡异的预处理器在一些机器上可能导致宏异常中断。下面是一个宏的两种不同实现版本:

复制代码 代码如下:

  #define LOOKUP(chr)    (a['c'+(chr)])    /* Works as intended. */
  #define LOOKUP(c)    (a['c'+(c)])        /* Sometimes breaks. */

第二个版本的LOOKUP可能以两种不同的方式扩展,并且会导致代码异常中断。

    * 熟悉现有的库函数和定义(但不用太熟悉。与其外部接口相反,库基础设施的内部细节常会改变并且没有警告,这些细节常常也是不可移植的)。你不应该再自己重 新编写字符串比较例程、终端控制例程或为系统结构编写你自己的定义。自己动手实现既浪费你的时间,又使得你的代码可读性变差,因为另外一个读者需 要知道你是否在新的实现中做了什么特殊的事情,并尝试证实它们的存在。同时这样做会使得你无法充分利用一些辅助的微代码或其他有助于提高系统例程 性能的方法。更进一步,它将是一个bug的高产源头。如果可能的话,要知道公共库之间的差异(如ANSI、POSIX等等)。

    * 如果lint可用,请使用lint。这个工具对于查找代码中机器相关的构造、其他不一致性以及顺利通过编译器检查的程序bug时具有很高价值。如果你的编 译器具备打开警告的开关,请打开它。

    * 质疑在代码块内部的与代码块外部switch或goto有关联的标签(Label)。

    无论类型在哪里,参数都应该被转换为适当的类型。当NULL用在没有原型的函数调用时,请对NULL进行转换。不要让函数调用成为类型欺骗发生的地方。C 语言的类型提升规则很是让人费解,所以尽量小心。例如,如果一个函数接受一个32位长的长整型做为参数,但实际传入的却是一个16位长的整型数, 函数栈可能会无法对齐,这个值也可能会被错误提升。

    * 在混用有符号和无符号值的算术计算时请使用显式类型转换

    * 应该谨慎使用跨程序的goto、longjmp。很多实现"忘记"恢复寄存器中的值了。尽可能将关键的值声明为volatile,或将它们注释为 VOLATILE。

    * 一些链接器将名字转换为小写,并且一些链接器只识别前六个字母作为唯一标识。在这些系统上程序可能会悄悄地中断运行。

    * 当心编译器扩展。如果使用了编译器扩展,请将他们视为机器依赖并用文档记录下来。

    * 通常程序无法在数据段执行代码或者无法将数据写入代码段。即使程序可以这么做,也无法保证这么做是可靠的。

17. 标准C

现代C编译器支持一些或全部的ANSI提议的标准C。无论何时可能的话,尽量用标准C编写和运行程序,并且使用诸如函数原型,常量存储以及 volatile(易失性)存储等特性。标准C通过给优化器提供有有效的信息以提升程序的性能。标准C通过保证所有编译器接受同样的输入语言以及提供相关 机制隐藏机器相关内容或对于那些机器相关代码提供警告的方式提升代码的可移植性。

17.1 兼容性

编写很容易移植到老编译器上的代码。例如,有条件地在global.h中定义一些新(标准中的)关键字,比如const和volatile。标准编译器预 定义了预处理器符号STDC(见脚注8)。void类型很难简单地处理正确,因为很多老编译器只理解void,但不认识void。最简单的方法就是定义一 个新类型VOIDP(与机器和编译器相关),通常在老编译器下该类型被定义为char*。

复制代码 代码如下:

#if __STDC__
    typedef void *voidp;
#   define COMPILER_SELECTED
#endif
#ifdef A_TARGET
#    define const
#    define volatile
#    define void int
    typedef char *voidp;
#    define COMPILER_SELECTED
#endif
#ifdef …
    …
#endif
#ifdef COMPILER_SELECTED
#    undef COMPILER_SELECTED
#else
    { NO TARGET SELECTED! }
#endif

注意在ANSI C中,#必须是同一行中预处理器指示符的第一个非空白字符。在一些老编译器中,它必须是同一行中的第一个字符。

当一个静态函数具有前置声明时,前置声明必须包含存储修饰符。在一些老编译器中,这个修饰符必须是"extern"。对于ANSI编译器,这个存储修饰符 必须为static,但全局函数依然必须声明为extern。因此,静态函数的前置声明应该使用一个#define,例如FWD_STATIC,并通 过#ifdef适当定义。

一个"#ifdef NAME"应该要么以"#endif"结尾,要么以"#endif / NAME /结尾,不应该用"#endif NAME"结尾。对于短小的#ifdef不应该使用注释,因为通过代码我们可以明确其含义。

ANSI的三字符组可能导致内容包含??的字符串的程序神秘的中断。

17.2 格式化

ANSI C的代码风格与常规C一样,但有两点意外:存储修饰符(storage qualifiers)和参数列表。

由于const和volatile的绑定规则很奇怪,因此每个const或volatile对象都应该单独声明。

复制代码 代码如下:

int const *s;        /* YES */
int const *s, *t;    /* NO */

具备原型的函数将参数声明和定义归并在一个参数列表中了。应该在函数的注释中提供各个参数的注释。

复制代码 代码如下:

/*
 * `bp': boat trying to get in.
 * `stall': a list of stalls, never NULL.
 * returns stall number, 0 => no room.
 */
int
enter_pier (boat_t const *bp, stall_t *stall)
{
    …

17.3 原型

应该使用函数原型使得代码更加健壮并且运行时性能更好。不幸地是原型的声明

复制代码 代码如下:

extern void bork (char c);

与定义不兼容。

复制代码 代码如下:

void
bork (c)
char c;
 …

原型中c应该以机器上最自然的类型传入,很可能是一个字节。而非原型化(向后兼容)的定义暗示c总是以一个整型传入。如果一个函数具有可类型提升的参数, 那么调用者和被调用者必须以相等地方式编译。要么都必须使用函数原型,要么都不使用原型。如果在程序设计时参数就是可以提升类型的,那么问题就可以被避 免,例如bork可以定义成接受一个整型参数。

如果定义也是原型化的,上面的声明将工作正常。

复制代码 代码如下:

void
bork (char c)
{
    …

不幸地是,原型化的语法将导致非ANSI编译器拒绝这个程序。

但我们可以很容易地通过编写外部声明来同时适应原型与老编译器。

复制代码 代码如下:

#if __STDC__
#    define PROTO(x) x
#else
#    define PROTO(x) ()
#endif

extern char **ncopies PROTO((char *s, short times));

注意PROTO必须使用双层括号。

最后,最好只使用一种风格编写代码(例如,使用原型)。当需要非原型化的版本时,可使用一个自动转换工具生成。

17.4 Pragmas

Pragmas用于以一种可控的方式引入机器相关的代码。很显然,pragma应该被视为机器相关的。不幸地是,ANSI pragmas的语法使得我们无法将其隔离到机器相关的头文件中了。

Pragmas分为两类。优化相关的可以被安全地忽略。而那些影响系统行为(需要pragmas)的Pragmas则不能忽略。需要的pragmas应该结合#ifdef使用,这样如果一个pragma都没有选到,编译过程将退出。

两个编译器可能通过两个不同的方式使用同一个给定的pragma。例如,一个编译器可能使用haggis发出一个优化信号。而另一个可能使用它暗示一个特 定语句,一旦执行到此,程序应该退出。不过,一旦使用了pragma,它们必须总是被机器相关的#ifdef包围。对于非ANSI编译器,Pragmas 必须总是被#ifdef。确保对#pragma的#进行缩进,否则一些较老的预处理器处理它时会挂起。

复制代码 代码如下:

#if defined(__STDC__) && defined(USE_HAGGIS_PRAGMA)
    #pragma (HAGGIS)
#endif

    "ANSI标准中描述的'#pragma'命令具有任意实现定义的影响。在GNU C预处理中,'#pragma'首先尝试运行游戏'rogue';如果失败,它将尝试运行游戏'hack';如果失败,它将尝试运行GNU Emacs显示汉诺塔;如果失败,它将报告一个致命错误。无论如何,预处理将不再继续。"

    — GNU CC 1.34 C预处理手册。

18. 特殊考虑

这节包含一些杂项:‘做'与'不做'。

    * 不要通过宏替换来改变语法。这将导致程序对于所有人都是难以理解的,除了那个肇事者。

    * 不要在需要离散值的地方使用浮点变量。使用一个浮点数作为循环计数器无疑是搬起石头砸自己的脚。总是用<=或>=测试浮点数,对它们永远不要 用精确比较(==或!=)。

    * 编译器也有bug。常见且高发的问题包括结构体赋值和位字段。你无法泛泛的预测一个编译器都有哪些bug。但你可以在程序中避免使用那些已知的在所有编译 器上都存在问题的结构。你无法让你写的任何代码都是有用的,你可能仍然会遇到bug,并且在这期间编译器很可能会被修复。因此,只有当你被强制使 用某个特定的充斥bug的编译器时,你才应该"围绕"着编译器bug写代码。

    * 不要依赖自动代码美化工具。良好代码风格的主要受益者就是代码的编写者,并且尤其在手写算法或伪代码的早期设计阶段。自动代码美化工具只应该用在那些已经 完成、语法正确并且此后不能满足当空白和缩进被更为关注的要求时。伴随着对细致程序员的细节的关注,对于那些将函数或文件布局解释清楚的工作,程 序员们会做得更好(换句话说,一些视觉布局是由意图而不是语法决定的,美化工具无法了解到程序员的思想)。粗心的程序员应该学习成为一个细致的程 序员,而不是依赖美化工具让代码可读性更好。

    * 意外地遗漏逻辑比较表达式中的第二个=是一个常犯的问题。使用显式测试。避免对赋值使用隐式测试。

复制代码 代码如下:

abool = bbool;
if (abool) { …

当嵌入的赋值表达式使用时,确保测试是显式的,这样后续它就无法被"修复"了。

复制代码 代码如下:

while ((abool = bbool) != FALSE) { …
while (abool = bbool) { …    /* VALUSED */
while (abool = bbool, abool) { …

    显式地注释那些在正常控制流之外被修改的变量,或其他可能在维护过程中中断的代码。

    现代编译器会自动将变量放到寄存器中。对于你认为最关键的变量慎用寄存器。在极端情况下,用寄存器标记2-4个最为关键的值,并且将剩余的标记为 REGISTER。后者在那些具有较多寄存器的机器上可以#define为寄存器。

19. Lint

Lint是一个C程序检查工具,用于检查C语言源码文件,探测和报告诸如类型不兼容、函数定义与调用不一致以及潜在的bug等情况。强烈建议在所 有程序上使用lint工具,并且期望大多数工程将lint作为官方验收程序的一部分。

应该注意的是使用lint的最好方法不是将lint作为官方验收之前的一道必须跨过的栅栏,而是作为一个在代码发生添加或变更之后使用的工具。 Lint可以发现一些隐藏的bug并且可以在问题发生前保证程序的可移植性。lint产生的许多信息确实暗示了一些事情是错误的。一个有意思的故 事是关于一个漏掉了fprintf的一个参数的程序:

复制代码 代码如下:

fprintf ("Usage: foo -bar <file>\n");

作者从未有过一个问题。但每当一个正常用户在命令行上犯错,这个程序就会产生一个core。许多版本的lint工具都能发现这个问题。

大多lint选项都值得我们学习。一些选项可能在合法的代码上给出警告,但它们也会捕捉到许多把事情搞遭的代码。注意'–p'只能为库的一个子 集检查函数调用和类型的一致性,因此程序为了最大化的覆盖检查,应该同时进行带–p和不带–p的lint检查。

Lint也可以识别代码里的一些特殊注释。这些注释可以强制让lint在发现问题时关闭警告输出,还可以作为一些特殊代码的文档。

20. Make

另外一个非常有用的工具是make。在开发过程中,make只会重新编译那些上次make后发生了改变的模块。它也可以用于自动化其他任务。一些 常见的约定包括:

复制代码 代码如下:

all
     执行所有二进制文件的构建过程

clean
     删除所有中间文件

debug
     构建一个测试用二进制文件a.out或debug

depend
     制作可传递的依赖关系

install
     安装二进制文件,库等

deinstall
     取消安装

mkcat
     安装手册

lint
    运行lint工具

print/list
    制作一个所有源文件的拷贝

shar
    为所有源文件制作一个shar文件

spotless
     执行make clean,并将源码存入版本控制工具。注意:不会删除Makefile,即便它是一个源文件。

source
     撤销spotless所做的事情。

tags
     运行ctags(建议使用-t标志)

rdist
     分发源码到其他主机

file.c
     从版本控制系统中检出这个文件


除此之外,通过命令行也可以定义Makefile使用的值(如"CFLAGS")或源码中使用的值(如"DEBUG")。

21. 工程相关的标准

除了这里提到内容外,每个独立的工程都期望能建立附加标准。下面是每个工程程序管理组需要考虑的问题中的一部分:

    * 哪些额外的命名约定需要遵守?尤其是,那些用于全局数据的功能归类以及结构体或联合体成员名字的系统化的前缀约定非常有用。

    * 什么样的头文件组织适合于工程特定的数据体系结构?

    * 应该建立什么样的规程来审核lint警告?需要确立一个与lint选项一致的宽容度,保证lint不会针对一些不重要的问题给出警告,但同时保证真正的bug或不一致问题不被隐藏。

    * 如果一个工程建立了自己的档案库,它应该计划向系统管理员提供一个lint库文件。这个lint库文件允许lint工具检查对库函数的兼容性使用。

    * 需要使用哪种版本控制工具?

22. 结论

这里描述了一套C语言编程风格的标准。其中最重要的几点是:

    * 合理使用空白和注释,使得我们通过代码布局就可以清楚地看出程序的结构。使用简单表达式、语句和函数,使他们可以很容易地被理解。

    * 记住,在将来某个时候你或其他人很可能会被要求修改代码或让代码运行在一台不同的机器上。精心编写代码,使得其可以移植到尚不确定的机器上。局部化你的优化,因为这些优化经常让人困惑,并且对于该优化措施是否适合其他机器我们持悲观态度。

    * 许多风格选择是主观武断的。保持代码风格一致比遵循这些绝对的风格规则更重要(尤其是与组织内部标准保持一致)。混用风格比任何一种糟糕的风格都更加糟糕。

无论采用哪种标准,如果认为该标准有用就必须遵循它。如果你觉得遵循某条标准时有困难,不要仅仅忽略它们,而是在和你当地的大师或组织内的有经验的程序员讨论后再做决定。

23. 参考资料

B.A. Tague, C Language Portability, Sept 22, 1977. This document issued by department 8234 contains three memos by R.C. Haight, A.L. Glasser, and T.L. Lyon dealing with style and portability.
S.C. Johnson, Lint, a C Program Checker, Unix Supplementary Documents, November 1986.
R.W. Mitze, The 3B/PDP-11 Swabbing Problem, Memorandum for File, 1273-770907.01MF, September 14, 1977.
R.A. Elliott and D.C. Pfeffer, 3B Processor Common Diagnostic Standards- Version 1, Memorandum for File, 5514-780330.01MF, March 30, 1978.
R.W. Mitze, An Overview of C Compilation of Unix User Processes on the 3B, Memorandum for File, 5521-780329.02MF, March 29, 1978.
B.W. Kernighan and D.M. Ritchie, The C Programming Language, Prentice Hall 1978, Second Ed. 1988, ISBN 0-13-110362-8.
S.I. Feldman, Make -- A Program for Maintaining Computer Programs, UNIXSupplementary Documents, November 1986.
Ian Darwin and Geoff Collyer, Can't Happen or / NOTREACHED / or Real Programs Dump Core, USENIX Association Winter Conference, Dallas 1985 Proceedings.
Brian W. Kernighan and P. J. Plauger The Elements of Programming Style. McGraw-Hill, 1974, Second Ed. 1978, ISBN 0-07-034-207-5.
J. E. Lapin Portable C and UNIX System Programming, Prentice Hall 1987, ISBN 0-13-686494-5.
Ian F. Darwin, Checking C Programs with lint, O'Reilly & Associates, 1989. ISBN 0-937175-30-7.
Andrew R. Koenig, C Traps and Pitfalls, Addison-Wesley, 1989. ISBN 0-201-17928-8.

相关文章

  • C++实现LeetCode(73.矩阵赋零)

    C++实现LeetCode(73.矩阵赋零)

    这篇文章主要介绍了C++实现LeetCode(73.矩阵赋零),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C中qsort快速排序使用实例

    C中qsort快速排序使用实例

    在学习C++ STL的sort函数,发现C中也存在一个qsort快速排序,要好好学习下C的库函数啊
    2014-01-01
  • C语言完数的实现示例

    C语言完数的实现示例

    C语言中的完数指的是一个正整数,本文主要介绍了C语言完数,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-05-05
  • C++ Assert()断言机制原理以及使用方法

    C++ Assert()断言机制原理以及使用方法

    下面小编就为大家带来一篇C++ Assert()断言机制原理以及使用方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-01-01
  • C语言中对文件最基本的读取和写入函数

    C语言中对文件最基本的读取和写入函数

    这篇文章主要介绍了C语言中对文件最基本的读取和写入函数,是C语言入门学习中的基础知识,需要的朋友可以参考下
    2015-08-08
  • C++如何保存bmp图片

    C++如何保存bmp图片

    这篇文章主要介绍了C++如何保存bmp图片问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • C++中套接字库sockpp的使用详解

    C++中套接字库sockpp的使用详解

    sockpp是一个开源、简单、现代的C++套接字库,这篇文章主要为大家详细介绍一下套接字库sockpp的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
    2023-11-11
  • C++ Boost Heap使用实例详解

    C++ Boost Heap使用实例详解

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • C++ Date类的具体使用(构建,重载等)

    C++ Date类的具体使用(构建,重载等)

    本文主要介绍了C++ Date类的具体使用(构建,重载等),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • c语言颜色代码详解

    c语言颜色代码详解

    在本篇文章里小编给大家整理的是关于c语言颜色代码的知识点内容,需要的朋友们可以参考下。
    2020-02-02

最新评论