用C语言编写可移植程序的注意事项
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
未确定和未定义的行为 以下是几个未确定和未定义的行为的例子。 1. 函数调用中,函数指针和自变量的执行次序。 译注:例如在以下的函数调用中 (*pf[f1()]) (f2(), f3() + f4()) f1(), f2(), f3()和 f4()可能以任意的次序被执行。
(摘录自 https://www.securecoding.cert.org/confluence/display/seccode/EXP10-C.+Do+not +depend+on+the+order+of+evaluation+of+subexpressions+or+the+order+in+which+sid e+effects+take+place)
#ifdef <platform-specific-symbol> #pragma ...
#endif 4. 连接符号有两种方式。一种是老式的 K&R 方式,它的实现原理是预处理器会去掉/**/这样的注释。 显然,如果输出中有空格的话,这样就不管用了。ANSI C 标准定义了操作符##来表示相邻字符串的连接。 因此像下面这样将两种形式都包含在一个头文件中是很有用的。
* 在一个结构体或共用体内最多 127 个成员。 * 一个函数调用最多拥有 31 个参数。这在使用参数个数不定的函数的时候会带来麻烦。因此,最好在 设计函数时将参数的个数限制在合理的范围之内,或者使用诸如数组之类的可变接口。 不幸的是这些限制中的其中一些可能会迫使程序员以一种不优雅的方式书写代码。如果标准中的这些限 制符合“好”的编程实践的话,我们当然是遵守为好。 然而在某些情况下,我们只能以不优雅的方式书写代码,因为这些限制可能会中断那些生成 C 代码的 程序,例如编译器的编译器和许多 C++编译器。
#ifdef __STDC__ # define GLUE(a,b) a##b #else # define GLUE(a,b) a/**/b #endif 如果有需要的话,我们也可以仿照 GLUE 宏,定义多个参数的宏。 5. 一些预处理器对引号中的符号做替换,但另一些却不是这样。因此这是一种内在的不可移植。标准 不允许这样做,并提供了相应的机制来达到相同的效果。以下的代码既可以在遵循 ANSI 标准的预处理器 上工作,又可以在那些对引号中的符号做替换预处理器上工作。 #ifdef __STDC__
标准化的影响 所有的标准都有好的一面和坏的一面,这是由它的本质决定的。我们将在下文着重探讨这一点。 美国国家标准委员会(ANSI) 即将完成 C 语言标准的制定。标准主要规定了语言的语法和语义,并定
义了一个最小环境(包括一些头文件的名字和内容和一些运行时库函数的定义)。 可以从以下地址获得 ANSI C 标准的副本:
预处理器 预处理器在以下的情况下可能会有不同的行为。 1. -I 命令项的解释在不同系统中是不一样,而且标准也没有涉及这一点。例如,在使用-I..的情况下,
#include "diBiblioteka Baidu/file.h"语句,在绝大多数的类 Unix 环境中,预处理器会在../dir 下寻找 file.h,但在 VM S 下,却只在./dir 下寻找 file.h。
POSIX
POSIX 工作组 P1003.1 的目标是为 UNIX 定义一个通用接口。尽管 ANSI C 标准的确定义了一些头 文件的内容和一些库函数的行为,但这并不足以定义一个有实用价值的环境。而这就是 P1003.1 的任务。
我们不知道 P1003.1 是如何处理本文所列出的这些问题,因为目前我们还缺乏这方面的文档。希望这 些在将来的文档中能得到解决。
特定的硬件和软件环境)的程序员,特别是那些打算编写跨主机的大系统的程序员。 如果你已经做过一些移植工作的话,你或许会觉得这些内容没什么用。 我们建议结合着文献[Can89]阅读本文。强烈推荐订阅 comp.lang.c 新闻组。[Hor90, Koe89]
免责声明:这里所示的代码片段只是为了使程序移植性“更”好,也就是说这些代码可能会在某些编译器或 环境下失败。
o 浮点计算 o 异常 * VMS o 文件描述符 o 杂谈 * 一般方针 o 机器体系结构, 类型兼容, 指针等 o 编译器的差异 o 文件 o 杂谈 * 致谢 * 版权声明 * 参考文献 * 关于本文 * 译者后记
前言 在正文开始之前,先说一下本文针对的读者人群。本文主要针对那些从未把程序移植到其他平台(包括
事实上,我们可以使用超出最大数目限制的字符来命名标识符,不过编译器会忽略超出的那部分字符。 也就是说,如果我们用 35 个字符来命名变量,而那个编译器最多只能处理 31 个字符的变量名的话,那 么多出的那 4 个字符就会被编译器忽略,只有前面的 31 个字符有效。(摘录自 http://www.jb51.ne t/article/7209.htm)
# define MAKESTRING(s) # s #else # define MAKESTRING(s) "s" #endif 有许多优秀公开有用的预处理器遵循 ANSI C 标准。例如 MIT X 联盟随着 X Window 系统发布的预 处理器。 注意一下#pragma 指示符,它能够改变程序的语义,但有些特殊的编译器可能不认识这些语义。很显 然,如果程序的行为依赖于这些语义的正确解释的话,为了使程序可移植,所有的目标平台都必须认识它。 最后,我们还要知道标准中已经包含了#error 指示符的确切语义。缩进#error,因为有些老的预处理 器不认识它。
用 C 语言编写可移植程序的注意事项
1990 年 6 月,第五版
A. Dolenc、A. Lemmke、D. Keppel 著 antkillerfarm 译
内容提要
* 前言 * 介绍 * 标准化的影响
o ANSI C + 移植限制 + 未确定和未定义的行为
o POSIX * 预处理器 * 语言
o 语法 o 语义 * Unix 系列: System V 和 BSD * 头文件 o ctype.h o fcntl.h and sys/file.h o errno.h o math.h o strings.h vs. string.h o time.h and types.h o varargs.h vs. stdarg.h * 运行时库 * 编译器限制 * 使用浮点数 o 机器常量 o 浮点参数
2. 我们不认为下面的语句能在所有的预处理器上执行。 #define D define #D this that
标准不允许这样的语法。(参见[X3J88]3.8.3 §20) 3. 指示符在所有的预处理器中都是差不多的,只是有些预处理器可能不支持在#if 指示符中的#prag ma 指示符。 可以缩进#pragma 指示符,使之在老的预处理器上也没有问题。进一步的说,你最好像下面这样用# ifdef 将#pragma 括起来以区分特定的平台。
2. 当操作符#和##连在一起时,预处理器对它们宏替换的执行次序。 3. 浮点类型的表示。 4. 使用在当前范围不可见的标识符。(译注:原文为 An identifier is used that is not visible i n the current scope,我也不清楚其具体所指为何。) 5. 一个指针被转换为除了整数和指针类型之外的类型。 这些例子还有很多。标准没有明确定义这些行为的主要原因之一是为了允许 C 环境的实现者更有效率 的使用它。
然而,我们还必须保证程序的行为并不依赖于这些关键字。
语义 语法没有解释的问题,因为它可以被准确定义。然而,编程语言通常是用自然语言描述的,例如英语,
而这会导致同样的文字会有不同的解释。 很明显的 C 语言的定义不该有歧义,否则的话,标准也就没有存在的必要了。[KR78]尽管标准已经很
以下是我们认为最重要的限制。我们首先列出与预处理器相关的。 * 最多 8 层嵌套的条件包含。(译注:例如#ifdef...#endif 等。) * 最多 8 层嵌套的#included 语句。 * 在一个完整的表达式中最多 32 层括号表达式。这通常发生在使用宏的时候。 * 同时最多 1024 个宏。这在某个文件包含了太多的头文件时可能发生。 * 一个源代码行最多 509 个字符。如果对预处理之后的行做这样的严格限制的话,又会因为一般的宏 通常是展开在一行,而影响到宏的最大尺寸的定义。标准在这一点上并不明确,因此在绝大多数的实现中, 这个限制是针对于宏展开之前的行。 * 一个外部标识符最多 6 个字符。通常这个规定是由链接器,而不是编译器限制的。 译注:C89 规定,编译器至少应该能够处理 31 个字符(包括 31)以内的内部标识符(internal i dentifier);而对于外部标识符(external identifier),编译器至少应该能够处理 6 个字符(包括 6)
American National Standards Institute Sales Department 1430 Broadway New York, NY 10018 电话 (212) 642-4900 传真 (212) 302-1286
ANSI C 移植限制
我们首先关注一下标准状态的环境限制。这些限制是移植的下限,它意味着超过这些限制的在某个编译 器上正确的程序,可能不能被另一个正确(符合标准)的编译器编译。
介绍 本文的目的是总结几位资深程序员在各种平台移植 C 语言程序的经验。 为了保证本文内容的合理性,我们限定这些程序必须运行在类 UNIX 操作系统上,且有基本的类 UNI
X 环境。唯一的例外是,我们还会讨论 VMS 操作系统。 我们可以从那些已有的跨平台程序中获得一些有用的信息。例如 Free Software Foundation 和 MI
语言
语法 标准定义的语法是 K&R 定义的一个超集。它意味着严格按照之前的标准的程序在 ANSI C 兼容的编译
器下是没有问题的。标准主要扩展了以下语法: 1. 包含了关键字 const 和 volatile。 2. 用省略号...表示可变数量的参数。 3. 函数原型。 4. 用 Trigraph 表示特定的宽字符串。(译注:Trigraph 的含义参见 http://en.wikipedia.org/wiki
以内的外部标识符。所谓标识符,是指我们为变量(variable)、宏(macro),或者函数(function) 等等取的名字。例如 int num; 这个语句中的 num 就是一个标识符。
最新的 C99 标准规定,编译器至少应该能够处理 63 个字符(包括 63)以内的内部标识符;编译器 至少应该能够处理 31 个字符(包括 31)以内的外部标识符。
本文件可以通过匿名的 FTP 在 sauna.hut.fi [130.233.251.253]的~ftp/pub/CompSciLab/doc 文件夹下获得。文件 portableC.tex、portableC.bib 和 portableC.ps.Z 分别是本文的 LaTeX、BibT eX 和 compressed PostScript 版本。
/C_trigraph) 我们鼓励使用保留字 const 和 volatile,因为它们有助于代码的书写。如果代码要在那些不兼容的编译
器上编译的话,将下面的片段添加到头文件中,将是很有用的。 #ifndef __STDC__ # define const # define volatile #endif
T X Consortium 开发的那些公用软件。
我们讨论可移植性主要关注以下两点: 语言。包括预处理器、语法和语义。 环境。包括头文件和运行时库的位置和内容。 我们还将讨论语言和环境的标准化,并特别关注浮点数的表示与计算,特定编译器的限制,以及 VMS。 我们主要关注于 boiler-plate 问题。即将成为标准的文献[X3J88]所提到的 twisted code 问题以及 系统编程问题在本文中不会过多涉及。
(摘录自 https://www.securecoding.cert.org/confluence/display/seccode/EXP10-C.+Do+not +depend+on+the+order+of+evaluation+of+subexpressions+or+the+order+in+which+sid e+effects+take+place)
#ifdef <platform-specific-symbol> #pragma ...
#endif 4. 连接符号有两种方式。一种是老式的 K&R 方式,它的实现原理是预处理器会去掉/**/这样的注释。 显然,如果输出中有空格的话,这样就不管用了。ANSI C 标准定义了操作符##来表示相邻字符串的连接。 因此像下面这样将两种形式都包含在一个头文件中是很有用的。
* 在一个结构体或共用体内最多 127 个成员。 * 一个函数调用最多拥有 31 个参数。这在使用参数个数不定的函数的时候会带来麻烦。因此,最好在 设计函数时将参数的个数限制在合理的范围之内,或者使用诸如数组之类的可变接口。 不幸的是这些限制中的其中一些可能会迫使程序员以一种不优雅的方式书写代码。如果标准中的这些限 制符合“好”的编程实践的话,我们当然是遵守为好。 然而在某些情况下,我们只能以不优雅的方式书写代码,因为这些限制可能会中断那些生成 C 代码的 程序,例如编译器的编译器和许多 C++编译器。
#ifdef __STDC__ # define GLUE(a,b) a##b #else # define GLUE(a,b) a/**/b #endif 如果有需要的话,我们也可以仿照 GLUE 宏,定义多个参数的宏。 5. 一些预处理器对引号中的符号做替换,但另一些却不是这样。因此这是一种内在的不可移植。标准 不允许这样做,并提供了相应的机制来达到相同的效果。以下的代码既可以在遵循 ANSI 标准的预处理器 上工作,又可以在那些对引号中的符号做替换预处理器上工作。 #ifdef __STDC__
标准化的影响 所有的标准都有好的一面和坏的一面,这是由它的本质决定的。我们将在下文着重探讨这一点。 美国国家标准委员会(ANSI) 即将完成 C 语言标准的制定。标准主要规定了语言的语法和语义,并定
义了一个最小环境(包括一些头文件的名字和内容和一些运行时库函数的定义)。 可以从以下地址获得 ANSI C 标准的副本:
预处理器 预处理器在以下的情况下可能会有不同的行为。 1. -I 命令项的解释在不同系统中是不一样,而且标准也没有涉及这一点。例如,在使用-I..的情况下,
#include "diBiblioteka Baidu/file.h"语句,在绝大多数的类 Unix 环境中,预处理器会在../dir 下寻找 file.h,但在 VM S 下,却只在./dir 下寻找 file.h。
POSIX
POSIX 工作组 P1003.1 的目标是为 UNIX 定义一个通用接口。尽管 ANSI C 标准的确定义了一些头 文件的内容和一些库函数的行为,但这并不足以定义一个有实用价值的环境。而这就是 P1003.1 的任务。
我们不知道 P1003.1 是如何处理本文所列出的这些问题,因为目前我们还缺乏这方面的文档。希望这 些在将来的文档中能得到解决。
特定的硬件和软件环境)的程序员,特别是那些打算编写跨主机的大系统的程序员。 如果你已经做过一些移植工作的话,你或许会觉得这些内容没什么用。 我们建议结合着文献[Can89]阅读本文。强烈推荐订阅 comp.lang.c 新闻组。[Hor90, Koe89]
免责声明:这里所示的代码片段只是为了使程序移植性“更”好,也就是说这些代码可能会在某些编译器或 环境下失败。
o 浮点计算 o 异常 * VMS o 文件描述符 o 杂谈 * 一般方针 o 机器体系结构, 类型兼容, 指针等 o 编译器的差异 o 文件 o 杂谈 * 致谢 * 版权声明 * 参考文献 * 关于本文 * 译者后记
前言 在正文开始之前,先说一下本文针对的读者人群。本文主要针对那些从未把程序移植到其他平台(包括
事实上,我们可以使用超出最大数目限制的字符来命名标识符,不过编译器会忽略超出的那部分字符。 也就是说,如果我们用 35 个字符来命名变量,而那个编译器最多只能处理 31 个字符的变量名的话,那 么多出的那 4 个字符就会被编译器忽略,只有前面的 31 个字符有效。(摘录自 http://www.jb51.ne t/article/7209.htm)
# define MAKESTRING(s) # s #else # define MAKESTRING(s) "s" #endif 有许多优秀公开有用的预处理器遵循 ANSI C 标准。例如 MIT X 联盟随着 X Window 系统发布的预 处理器。 注意一下#pragma 指示符,它能够改变程序的语义,但有些特殊的编译器可能不认识这些语义。很显 然,如果程序的行为依赖于这些语义的正确解释的话,为了使程序可移植,所有的目标平台都必须认识它。 最后,我们还要知道标准中已经包含了#error 指示符的确切语义。缩进#error,因为有些老的预处理 器不认识它。
用 C 语言编写可移植程序的注意事项
1990 年 6 月,第五版
A. Dolenc、A. Lemmke、D. Keppel 著 antkillerfarm 译
内容提要
* 前言 * 介绍 * 标准化的影响
o ANSI C + 移植限制 + 未确定和未定义的行为
o POSIX * 预处理器 * 语言
o 语法 o 语义 * Unix 系列: System V 和 BSD * 头文件 o ctype.h o fcntl.h and sys/file.h o errno.h o math.h o strings.h vs. string.h o time.h and types.h o varargs.h vs. stdarg.h * 运行时库 * 编译器限制 * 使用浮点数 o 机器常量 o 浮点参数
2. 我们不认为下面的语句能在所有的预处理器上执行。 #define D define #D this that
标准不允许这样的语法。(参见[X3J88]3.8.3 §20) 3. 指示符在所有的预处理器中都是差不多的,只是有些预处理器可能不支持在#if 指示符中的#prag ma 指示符。 可以缩进#pragma 指示符,使之在老的预处理器上也没有问题。进一步的说,你最好像下面这样用# ifdef 将#pragma 括起来以区分特定的平台。
2. 当操作符#和##连在一起时,预处理器对它们宏替换的执行次序。 3. 浮点类型的表示。 4. 使用在当前范围不可见的标识符。(译注:原文为 An identifier is used that is not visible i n the current scope,我也不清楚其具体所指为何。) 5. 一个指针被转换为除了整数和指针类型之外的类型。 这些例子还有很多。标准没有明确定义这些行为的主要原因之一是为了允许 C 环境的实现者更有效率 的使用它。
然而,我们还必须保证程序的行为并不依赖于这些关键字。
语义 语法没有解释的问题,因为它可以被准确定义。然而,编程语言通常是用自然语言描述的,例如英语,
而这会导致同样的文字会有不同的解释。 很明显的 C 语言的定义不该有歧义,否则的话,标准也就没有存在的必要了。[KR78]尽管标准已经很
以下是我们认为最重要的限制。我们首先列出与预处理器相关的。 * 最多 8 层嵌套的条件包含。(译注:例如#ifdef...#endif 等。) * 最多 8 层嵌套的#included 语句。 * 在一个完整的表达式中最多 32 层括号表达式。这通常发生在使用宏的时候。 * 同时最多 1024 个宏。这在某个文件包含了太多的头文件时可能发生。 * 一个源代码行最多 509 个字符。如果对预处理之后的行做这样的严格限制的话,又会因为一般的宏 通常是展开在一行,而影响到宏的最大尺寸的定义。标准在这一点上并不明确,因此在绝大多数的实现中, 这个限制是针对于宏展开之前的行。 * 一个外部标识符最多 6 个字符。通常这个规定是由链接器,而不是编译器限制的。 译注:C89 规定,编译器至少应该能够处理 31 个字符(包括 31)以内的内部标识符(internal i dentifier);而对于外部标识符(external identifier),编译器至少应该能够处理 6 个字符(包括 6)
American National Standards Institute Sales Department 1430 Broadway New York, NY 10018 电话 (212) 642-4900 传真 (212) 302-1286
ANSI C 移植限制
我们首先关注一下标准状态的环境限制。这些限制是移植的下限,它意味着超过这些限制的在某个编译 器上正确的程序,可能不能被另一个正确(符合标准)的编译器编译。
介绍 本文的目的是总结几位资深程序员在各种平台移植 C 语言程序的经验。 为了保证本文内容的合理性,我们限定这些程序必须运行在类 UNIX 操作系统上,且有基本的类 UNI
X 环境。唯一的例外是,我们还会讨论 VMS 操作系统。 我们可以从那些已有的跨平台程序中获得一些有用的信息。例如 Free Software Foundation 和 MI
语言
语法 标准定义的语法是 K&R 定义的一个超集。它意味着严格按照之前的标准的程序在 ANSI C 兼容的编译
器下是没有问题的。标准主要扩展了以下语法: 1. 包含了关键字 const 和 volatile。 2. 用省略号...表示可变数量的参数。 3. 函数原型。 4. 用 Trigraph 表示特定的宽字符串。(译注:Trigraph 的含义参见 http://en.wikipedia.org/wiki
以内的外部标识符。所谓标识符,是指我们为变量(variable)、宏(macro),或者函数(function) 等等取的名字。例如 int num; 这个语句中的 num 就是一个标识符。
最新的 C99 标准规定,编译器至少应该能够处理 63 个字符(包括 63)以内的内部标识符;编译器 至少应该能够处理 31 个字符(包括 31)以内的外部标识符。
本文件可以通过匿名的 FTP 在 sauna.hut.fi [130.233.251.253]的~ftp/pub/CompSciLab/doc 文件夹下获得。文件 portableC.tex、portableC.bib 和 portableC.ps.Z 分别是本文的 LaTeX、BibT eX 和 compressed PostScript 版本。
/C_trigraph) 我们鼓励使用保留字 const 和 volatile,因为它们有助于代码的书写。如果代码要在那些不兼容的编译
器上编译的话,将下面的片段添加到头文件中,将是很有用的。 #ifndef __STDC__ # define const # define volatile #endif
T X Consortium 开发的那些公用软件。
我们讨论可移植性主要关注以下两点: 语言。包括预处理器、语法和语义。 环境。包括头文件和运行时库的位置和内容。 我们还将讨论语言和环境的标准化,并特别关注浮点数的表示与计算,特定编译器的限制,以及 VMS。 我们主要关注于 boiler-plate 问题。即将成为标准的文献[X3J88]所提到的 twisted code 问题以及 系统编程问题在本文中不会过多涉及。