和记娱乐


网站导航

联系我们

和记娱乐

联系人: 

电 话:021-64057486

公司网址:http://www.happy123456789.com

   址:成都市松江区漕河泾松江新兴产业园区研展路丰产支路55号B座803室

邮编:201165


通信科技

您的当前位置: 和记娱乐主页 > 通信科技 >

干货 让嵌入式C语言更可读、易理解的规范

发布日期:2019-08-30 11:19 来源:和记h88 发布人:和记娱乐 点击:

  技术人员设计程序的首要目的是用于技术人员沟通和交流,其次才是用于机器执行。程序的生命力在于用户使用,程序的成长在于后期的及根据用户需求更新和升级功能。

  如果你的程序只能由你来,当你离开这个程序时,你的程序也和你一起离开了,这将给公司和后来接手的技术人员带来巨大的痛苦和损失。

  为提高产品代码质量,指导仪表嵌入式软件开发人员编写出简洁、可、可靠、可测试、高效、可移植的代码,编写了本规范。

  本规范将分为完整版和精简版,完整版将包括更多的样例、规范的解释以及参考材料(what & why),而精简版将只包含规则部分(what)以便查阅。

  一:为形成统一编程规范,从编码形式角度出发,本规范对标示符命名、格式与排版、注释等方面进行了详细阐述。

  二:为编写出高质量嵌入式软件,从嵌入式软件安全及可靠性出发,本规范对由于C语言标准、C语言本身、C编译器及个人理解导致的潜在进行说明及规避。

  本规范适用于X股份有限公司仪表台秤产品部嵌入式软件的开发,也对其他嵌入式软件开发起一定的指导作用。

  规则:编程时需要遵循的约定,分为强制和(强制是必须遵守的,是一般情况下需要遵守,但没有强制性)。

  Unspecified:未详细说明的行为,这些是必须成功编译的语言结构,但关于结构的行为,编译器的编写者有某些。例如C语言中的“运算次序”问题。这样的问题有 22 个。

  在某种方式上完全相信编译器的行为是不明智的。编译器的行为甚至不会在所有可能的结构中都是一致的。

  Undefined:未定义行为,这些是本质的编程错误,但编译器的编写者不一定为此给出错误信息。相应的例子是无效参数传递给函数,或函数的参数与定义时的参数不匹配。从安全性角度这是特别重要的问题,因为它们代表了那些不一定能被编译器捕捉到的错误。

  Implementation-defined:实现定义的行为,这有些类似于“unspecified ”问题,其主要区别在于编译器要提供一致的行为并记录成文档。换句话说,不同的编译器之间功能可能会有不同,使得代码不具有可移植性,但在任一编译器内,行为应当是良好定义的。

  比如用在一个正整数和一个负整数上的整除运算“/ ”和求模运算符“% ”。存在76个这样的问题。

  从安全性角度,假如编译器完全地记录了它的方法并它的实现,那么它可能不是那样至关重要。尽可能的情况下要避免这些问题。

  声明(declaration):指定了一个变量的标识符,用来描述变量的类型,是类型还是对象,函数等。声明,用于编译器(compiler)识别变量名所引用的实体。以下这些就是声明:

  定义(definition):是对声明的实现或者实例化。连接器(linker)需要它(定义)来引用内存实体。

  <序号>:每条规则都有一个序号,序号是按照章节目录-**的形式,从数字1开始。例如,若在此章节有个规则的话,序号为0.5-1。

  说明:ISO 标准要求在内部标识符之间前31 个字符必须是不同的,外部标识符之间前6 个字符必须是不同的(忽略大小写)以可移植性。我们这里放宽了此要求,要求内部、外部标示符的有效字符不能多于31即可。

  规则1.1-2(强制):具有内部作用域的标识符不应使用与具有外部作用域的标识符相同的名称,在内部作用域里具有内部标示符会隐藏外部标识符。

  说明:外部作用域和内部作用域的定义如下。文件范围内的标识符可以看做是具有最外部(outermost )的作用域;块范围内的标识符看做是具有更内部(more inner)的作用域,连续嵌套的块,其作用域更深入。如果内部作用域标示符和外部作用域标示符同名,内部作用域标示符会覆盖外部作用域标示符,导致程序混乱。

  说明:不管作用域如何,具有静态存储期的标识符都不应在系统内的所有源文件中重用。它包含带有外部链接的对象或函数,及带有静态存储类标识符的任何对象或函数。

  在一个文件中存在一个具有内部链接的标识符,而在另外一个文件中存在着具有外部链接的相同名字的标识符,或者存在两个标示符相同的外部标示符。对用户来说,这有可能导致混淆。

  原则1.1-4(强制):标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生。

  说明:简短的单词可以使用略去‘元音’字母形成缩写,较长的单词可以使用音节首字母单词前几个字母形成缩写,针对大家的单词缩写要统一。对于特定的项目要使用的专有缩写应该注明或者做统一说明。

  参考材料:《代码大全第2版》(Steve McConnell 著 金戈/汤凌/陈硕/张菲 译 电子工业出版社2006年3月)第11章变量命的力量。

  说明:由于不同系统对文件名大小写处理不同,Windows不区分文件名大小写,而Linux区分。所以文件名命名均采用小写字母,多个单词之间可使用”_”分隔符。

  说明:程序里的注释可能会使用中文,GB2312是简体中文编码,大部分的编辑工具和集成IDE都支持GB2312编码,为避免中文乱码,使用GB2312对源码进行编码。若需要转换成其他编码格式,可使用文本编码转换工具进行转换。

  说明:程序一般需要大量更新、修正、工作,且有时需要多人合作。使用版本管理工具可以帮助你提高工作效率。使用“Git”版本管理工具。

  说明:变量名称可以使用名词表述清楚的尽量使用名词,使用名词无法描述清楚时,使用形容词或者描述性的单词+名词的形式。变量一般为实体的属性、状态等信息,使用上述方案一般可以解决变量名的命名问题,如果出现命名很困难或者无法给出合理的命名方式时,问题可能出现在整体设计上,请重新审视设计。

  规则1.3-2(强制):全局变量添加”G_”前缀,全局静态变量添加” S_ ”,局部静态变量添加”s_”前缀。使用大小写混合方式命名,大写字母用于分割不同单词。

  说明:添加前缀的原因有两个。首先,使全局变量变得更醒目,提醒技术开发人员使用这些变量时要小心。其次,添加前缀使全局变量和静态变量变得和其他变量不一致,提醒技术开发人员尽量少用全局变量。

  说明:局部变量全部使用小写字母,和全局变量有明显区分,使读者看到标示符就知道是何种作用域的变量。

  规则1.3-4(强制):定义指针变量*紧挨变量名,全局指针变量使用大写P前缀”P_”,局部指针变量使用小写p前缀”p _”。

  说明:函数的功能是获取、修改实体的属性、状态等,采用“动词+名词”的方式可以满足上述需求,若出现使用此方式命名函数很困难或不能命名的情况,问题可能出现在整体设计上,请重新审视设计方案。

  规则1.4-2(强制):具有外部链接的函数命名使用大小写混合的方式,首字母大写,用于分割不同单词。

  说明:函数具有外部链接属性的含义是函数通过头文件对外声明后,对其他文件或模块来说是可见的。如果一个函数要在其他模块或者文件中使用,需要在头文件中声明该函数。另外,在头文件声明函数,还可以促使编译器检查函数声明和调用的一致性。

  规则1.4-3(强制):具有文件内部链接属性的函数命名使用小写字母,使用’_’分隔符分割不同单词,且使用static关键字函数作用域。

  说明:函数具有内部链接属性的含义是函数只能在模块或文件内部调用,对文件或模块外来说是不可见的。如果一个函数仅在模块内部或者文件内部使用,需要函数使用范围,使用static修饰符修饰函数,使其只具有内部链接属性。

  规则1.4-4(强制):函数参数使用小写字母,各单词之间使用“_”分割,尽量保持参数顺序从左到右为:输入、修改、输出。

  说明:函数参数顺序为需输入参数值(这个值一般不修改,若不需要修改使用const关键字修饰),需修改的参数(这个参数输入后用于提供数据,函数内部可以修改此参数),输出参数(这个参数是函数输出值)。

  规则1.5-1(强制):常量(#define定义的常量、枚举、const定义的常量)的定义使用全大写字母,单词之间加 ’_’分割的命名方式。

  规则1.6-1(强制):新定义类型名的命名应该明确抽象对象的含义,新类型名使用大写字母,单词之间加’_’分割,新类型指针在类型名前增加前缀”P_”。

  规则2.1.1-1(强制):头文件排版内容依次为包含的头文件、宏定义、类型定义、声明变量、声明函数。且各个种类的内容间空三行。

  说明:头文件是模块对外的公用接口。在头文件中定义的宏,可以被其他模块引用。Project中不使用全部变量,若使用则需在头文件里对外声明。模块对外的函数接口在模块头文件里声明。

  规则2.1.2-1(强制):源文件排版内容依次为包含的头文件、宏定义、具有外部链接属性的全局变量定义、模块内部使用的static变量、具有内部链接的函数声明、函数实现代码。且各个种类的内容间空三行。

  说明:模块内部定义的宏,只能在该模块内部使用。只在模块内部使用的函数,需在源码文件中声明,用于促使编译器检查函数声明和调用的一致性。

  说明:当前主流IDE都支持Tab缩进,使用Tab缩进需要打开和设置相关选项。宏定义、编译开关、条件预处理语句可以顶格。

  说明:执行语句必须用缩进风格写,属于if、for、do、while、case、switch、deult、typedef等的下一个缩进级别。一般写if、for、do、while等语句都会有成对出现的{}‟,if、for、do、while等语句后的执行语句增加成对的“{}”; 如果if/else语句块中只有一条语句,也需增加“{}”。

  规则2.1.2-4(强制):进行双目运算、赋值时,操作符之前、之后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。

  (1)比较操作符, 赋值操作符=、 +=,算术操作符+、%,逻辑操作符&&、&,位域操作符<<、^等双目操作符的前后加空格。

  规则2.1.2-5():一行只定义一个变量,一行只书写一条执行语句,多行同类操作时操作符尽量保持对齐。

  原则3.1-1(强制):注释的内容要清楚、明了,含义准确,在代码的功能、意图层次上进行注释。

  说明:注释的目的是让读者快速理解代码的意图。注释不是为了名词解释(what),而是说明用途(why)。

  原则3.1-2(强制):注释应分为两个角度进行,首先是应用角度,主要是告诉使用者如何使用接口(即你提供的函数),其次是实现角度,主要是告诉后期升级、的技术人员实现的原理和细节。

  说明:每一个产品都可以分为三个层次,产品本身是一个层次,这个层次之下的是你使用的更小的组件,这个层次之上的是你为别人提供的服务。你这个产品的存在的价值就在于把最底层的小部件的使用细节隐藏,同时给最上层的用户提供方便、简洁的

  从这个角度来看软件的注释,你应该时刻想着你写的注释是给那一层次的人员看的,如果是用户,那么你应该注重描述如何使用,如果是后期者,那么你应该注重原理和实现细节。

  原则3.1-3(强制):修改代码时,应代码周边的注释,使其代码和注释一致,不再使用的注释应删除。

  说明:注释的目的在于帮助读者快速理解代码使用方法或者实现细节,若注释和代码不一致会起到相反的作用。在修改代码前应该先修改注释。

  说明:当源代码段不需要被编译时,应该使用条件编译来完成(如带有注释的#if或#ifdef 结构)。为这种目的使用注释的开始和结束标记是的,因为C 不支持/**/嵌套的注释,而且已经存在于代码段中的任何注释将影响执行的结果。

  说明:注释格式可被doxygen工具识别,其中@file、@brief、@author等是doxygen工具识别的关键字,注释内容可以为中文。

  规则3.3-1(强制):函数注释分为头文件中函数原型声明时的注释和源文件中函数实现时的注释。头文件中的注释注重函数使用方法和注意事项,源文件中的注释注重函数实现原理和方法。具体格式见实例。

  说明:函数原型声明的注释按照doxygen工具可以识别的格式进行注释,用于doxygen工具生成头文件信息以及函数间的调用关系信息。

  源代码实现主要是注释函数实现原理及修改记录,不需按照doxygen工具要求的注释格式进行注释。

  说明:局部变量,关键语句需要注释,从功能和意图上进行注释,而不是代码的重复。多条注释语句尽量保持对齐,实现美观,整洁。

  项目开发过程一般分为前期开发测试阶段、发布阶段、阶段这三个主要阶段,我们分别制定了命名规范。

  说明:处于新开发、调试阶段的项目,版本号使用“V0.yz” 的形式,比如新开发的项目正处在开发、调试阶段,这时可以使用“ V0.10 ”这样的版本号。

  你认为完成了新的功能模块或整体架构做了很大的修改,可以根据情况增加 Y 或者 Z的值。比如,你开发阶段在“ V0.10 ”基础上新增加了一个功能模块你可以将版本号改为“V0.11”,做了比较大的修改,你可以将版本号定为“V0.20”。

  说明:处于正式发布的项目版本号使用“Vx.y”的形式。比如,你发布了一个正式面向市场的项目,你可以使用“V1.0”作为正式的版本号。在“V1.0”基础上增加功能的正式版本,你可以使用“V1.1”作为下一次正式版本的版本号,在“V1.0”基础上修正了大的BUG或者做了很大的改动,你可以使用“V2.0”作为下一次正式版本号。

  说明:处于阶段的项目版本号使用“Vx.yz”的形式。比如在V1.1的基础上修改了一个功能实现算法以实现高效率,则可以使用V1.11 来表示这是在正式发布版本“V1.1”的基础上进行的一次修正,再次修正可以使用“V1.12”。

  原则5.1-1(强制):头文件用于声明模块对外接口,包括具有外部链接的函数原型声明、全局变量声明、定义的类型声明等。

  说明:头文件是模块(Module)或单元(Unit)的对外接口。头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。内部使用的函数声明不应放在头文件中。 内部使用的宏、枚举、结构定义不应放入头文件中。变量定义不应放在头文件中,应放在.c文件中。

  变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接给外部,应通过函数接口的方式进行对外。即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的。

  规则5.1-2(强制):只能通过包含头文件的方式使用其他.c提供的接口,在.c中通过extern的方式使用外部函数接口、变量。

  说明:多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取头文件内容被包含多于一次的机制。通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。所有头文件都应当使用#define 防止头文件被多重包含,命名格式FILENAME_H_,其中FILENAME 为头文件的名称。

  规则5.2-1(强制):C的宏只能扩展为用大括号括起来的初始化、常量、小括号括起来的表达式、类型限定符、存储类标识符或do-while-zero 结构。

  说明:这些是宏当中所有可允许使用的形式。存储类标识符和类型限定符包括诸如extern、static和const这样的关键字。使用任何其他形式的#define 都可能导致非预期的行为,或者常难懂的代码。

  特别的,宏不能用于定义语句或部分语句,除了do-while 结构。宏也不能重定义语言的语法。

  宏的替换列表中的所有括号,不管哪种形式的 ()、{} 、[]  都应该成对出现。 do-while-zero 结构(见下面实例)是在宏语句体中唯一可接受的具有完整语句的形式。do-while-zero 结构用于封装语句序列并确保其是正确的。

  同样在调用函数时,参数也不要变化,如果某次软件升级将其中一个接口由函数实现转换成宏,那参数数值发生变化的调用将产生非预期效果。

  规则5.3-1(强制):应该使用标明了大小和符号的typedef代替基本数据类型。不应使用基本数值类型char、int、short、long、float和double,而应使用typedef进行类型的定义。

  说明:浮点运算会带来许多问题,一些问题(而不是全部)可以通过适应已定义的标准来克服。其中一个合适的标准是  ANSI/IEEE Std 754 [1] 。

  C 语言给程序员提供了相当大的度并允许不同数值类型可以自动转换。由于某些功能性的原因可以引入显式的强制转换,例如:

  为了代码清晰的目的而插入的强制转换通常是有用的,但如果过多使用就会导致程序的可读性下降。正如下面所描述的,一些隐式转换是可以安全地忽略的,而另一些则不能。

  说明:这条规则主要是要求需要强制转换时,须明确被转换对象的表示范围及转换后的表示范围。转换时尽量保持符号一致,不同符号对象之间不应出现强制转换。向更宽数据范围转换并不能提高数据精确度,并没有实际意义。在程序中尽量规划好变量范围,尽量少使用强制转换。

  当<<操作符用在 smallinteger 类型时会遇到类似的问题,高端数据位被保留下来。

  常量的符号应该明确。符号的一致性是构建良好形式的表达式的重要原则。如果一个是unsigned 类型,为其加上“U”后缀将有助于避免混淆。当用在较大数值上时,后缀也许是多余的(在某种意义上它不会影响常量的类型);然而后缀的存在对代码的清晰性是种有价值的帮助。

  1)        转换发生在对象指针和void 指针之间,而且目标类型承载了源类型的所有类型标识符。

  2)        当空指针常量(void*)被赋值给任何类型的指针或与其做等值比较时,空指针常量被自动为特定的指针类型。

  规则5.3.9-2(强制):对象指针和其他除整型之外的任何类型指针之间、对象指针和其他类型对象的指针之间、对象指针和void指针之间不能进行转换。

  说明:注意,根据ISO C[2] 标准,具有静态存储期的变量缺省地被自动赋予零值,除非经过了显式的初始化。实际中,一些嵌入式没有实现这样的缺省行为。

  静态存储期是所有以static存储类形式声明的变量或具有外部链接的变量的共同属性,自动存储期变量通常不是自动初始化的。

  ISO C[2]要求数组、结构和联合的初始化列表要以一对大括号括起来(尽管不这样做的行为是未定义的)。本规则更进一步地要求,使用附加的大括号来嵌套的结构。它程序员显式地考虑和描述复杂数据类型元素(比如,数组)的初始化次序。

  例如,下面的例子是二维数组初始化的有效(在ISO C [2]中)形式,但第一个与本规则相:

  NULL)。如果选择了这样的初始化方法,那么首元素应该被初始化为0(或NULL),此时不需要使用嵌套的大括号。

  规则5.4-3(强制):在枚举列表中,“= ”不能显式用于除首元素之外的元素上,除非所有的元素都是显式初始化的。

  如果枚举列表的没有显式地初始化,那么C 将为其分配一个从0 开始的整数序列,首元素为0 ,后续元素依次加 1 。

  如上规则允许的,首元素的显式初始化整数的分配从这个给定的值开始。当采用这种方法时,重要的是确保所用初始化值一定要足够小,这样列表中的后续值就不会超出该枚举常量所用的int 存储量。

  列表中所有项目的显式初始化也是允许的,它防止了易产生错误的自动与手动分配的混合。然而,程序员就该担负职责以所有值都处在要求的范围内以及值不是被无意复制的。

  说明:原型的使用使得编译器能够检查函数定义和调用的完整性。如果没有原型,就不会编译器检查出函数调用当中的一定错误(比如,函数体具有不同的参数数目,调用和定义之间参数类型的不匹配)。

  事明,函数接口是相当多问题的肇因,因此本规则是相当重要的。对外部函数来说,我们采用如下方法,在头文件中声明函数(亦即给出其原型),并在所有需要该函数原型的代码文件中包含这个头文件,在实现函数功能的.c文件中也包含具有原型声明的头文件。 为具有内部链接的函数给出其原型也是良好的编程实践。

  规则5.4-6(强制):函数的每个参数类型在声明和定义中必须是等同的,函数的返回类型也该是等同的。

  规则5.4-7(强制):在文件范围内声明和定义的所有对象或函数应该具有内部链接,除非是在需要外部链接的情况下,具有内部链接属性的对象或函数应该使用static关键字修饰。

  说明:如果一个变量只是被同一文件中的函数所使用,那么就用static。类似地,如果一个函数只是在同一文件中的其他地方调用,那么就用 static。

  使用 static存储类标识符将确保标识符只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。具有外部链接属性的对象或函数在相应模块的头文件中声明,在需要使用这些接口的模块中包含此头文件。

  规则5.4-8(强制):当一个数组声明为具有外部链接,它的大小应该显式声明或者通过初始化进行隐式定义。

  尽管可以在数组声明不完善时访问其元素,然而仍然是在数组的大小可以显式确定的情况下,这样做才会更为安全。

  说明:括号的使用除了可以覆盖缺省的运算符优先级以外,还可以用来强调所使用的运算符。使用相当复杂的C 运算符优先级规则很容易引起错误,那么这种方法就可以帮助避免这样的错误,并且可以使得代码更为清晰可读。

  说明:当一个表达式使用了sizeof运算符,并期望计算表达式的值时,表达式是不会被计算的。sizeof只对表达式的类型有用。

  说明:C语言中存在表达式的某些部分不会被计算到,这取决于表达式中的其他部分。逻辑操作符&&或在进行逻辑判断时,若仅判别左操作数就能确定true or lse的情况下,逻辑操作符的右操数将被忽略。

  规则5.5-3():逻辑运算符(&&、   和 ! )的操作数应该是有效的布尔数。有效布尔类型的表达式不能用做非逻辑运算符(&&、   和 ! )的操作数。

  说明:有效布尔类型是表示真、假的一种数据类型,产生布尔类型的可以是比较,逻辑运算,但布尔类型数据只能进行逻辑运算。

  规则5.5-4(强制):位运算符不能用于基本类型(underlying type )是有符号的操作数上。

  说明:位运算(~ 、<<、>>、&、^  和   )对有符号整数通常是无意义的。比如,如果右移运算把符号位移动到数据位上或者左移运算把数据位移动到符号位上,就会产生问题。

  规则5.5-6():在一个表达式中,自增(++)和自减(- - )运算符不应同其他运算符混合在一起。

  规则5.5-7(强制):浮点表达式不能做像‘>’ ‘<’ ‘==’ ‘!=’等 关系运算。

  说明:float、double类型的数据都有一定的精确度,使用不同浮点数表示规范或者不同硬件平台可能导致关系运算的结果不一致。

  规则5.5-8(强制):for语句的三个表达式应该只关注循环控制,for循环中用于计数的变量不应在循环体中修改。

  规则5.5-9(强制):组成switch、while、do...while 或for 结构体的语句应该是复合语句。即使该复合语句只包含一条语句也要扩在{}里。

  规则5.5-11(强制):switch 语句中如果case 分支的内容不为空,那么必须以break 作为结束,最后分支应该是deult分支。

  说明:代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。代码的有效组织包括:逻辑层组织和物理层组织两个方面。逻辑层,主要是把不同功能的函数通过某种联系组织起来,主要关注模块间的接口,也就是模块的架构。

  物理层,无论使用什么样的目录或者名字空间等,需要把函数用一种标准的方法组织起来。例如:设计良好的目录结构、函数名字、文件组织等,这样可以方便查找。

  规则5.6-2(强制):一定要显示声明函数的返回值类型,及所带的参数。如果没有要声明为void。

  说明:有些算法使用分而治之的递归思想,但在嵌入式中栈空间有限,递归本身承载着可用堆栈空间过度的,这能导致严重的错误。除非递归经过了非常严格的控制,否则不可能在执行之前确定什么是最坏情况(worst-case)的堆栈使用。

  规则5.7-1(强制):除了指向同一数组的指针外,不能用指针进行数算,不能进行关系运算。

  局部变量是在栈中分配的,函数返回后占用的内存会,继续使用这样的内存是的。因此,应该避免出现这样的。

  说明:相关的一组信息才是构成一个结构体的基础,结构的定义应该可以明确的描述一个对象,而不是一组相关性不强的数据的集合。设计结构时应力争使结构代表一种现实事务的抽象,而不是同时代表多种。

  结构中的各元素应代表同一事务的不同侧面,而不应把描述没有关系或关系很弱的不同事务的元素放到同一结构中。

  说明:通常 #undef  一个定义在标准库中的宏是件坏事。同样不好的是,#define 一个宏名字,而该名字是C 的保留标识符或者标准库中做为宏、对象或函数名字的C 关键字。

  例如,存在一些特殊的保留字和函数名字,它们的作用为人所熟知,如果对它们重新定义或取消定义就会产生一些未定义的行为。这些名字包括defined、__LINE__、__FILE__、__DATE__ 、__TIME__、__STDC__、errno和assert。

  C 标准库中的许多函数根据ISO [2] 标准 并不需要检查传递给它们的参数的有效性。即使标准要求这样,或者编译器的编写者声明要这么做,也不能会做出充分的检查。因此,程序员应该为所有带有严格输入域的库函数(标准库、第三方库及自己定义的库)提供适当的输入值检查机制。

  toupper 和tolower:当传递给toupper函数的参数不是小写字符时,某些实现能产生并非预期的结果(tolower 函数情况类似)

  应用于大多数负整数的abs 函数给出未定义的行为 在math.h 中,尽管大多数数学库函数定义了它们允许的输入域,但在域发生错误时它们的返回值仍可能随编译器的不同而不同。因此,对这些函数来说,预先检查其输入值的有效性就变得至关重要。

  2.  设计深入函数内部的检查手段。这种方法尤其适应于实验室内开发的库,纵然它也可以用于买进的第三方库(如果第三方库的供应商声明他们已内置了检查的话)。

  3.  产生函数的“封装”(wrapped)版本,在该版本中首先检查输入,然后调用原始的函数。

  注意,在检查函数的浮点参数时(浮点参数在零点上为奇点),适当的做法是执行其是否为零的检查。然而如果当参数趋近于零时,函数值的量级趋近无穷的话,仍然有必要检查其在零点(或其他任何奇点)上的容限,这样可以避免溢出的发生。

      和记娱乐,和记h88,h88平台官网