当前位置: C语言 -- 基础 -- 环境

环境(一)


严格符合ISO/IEC C语言标准的程序只能使用ISO/IEC 9899标准文档中规定的语言和库的功能,并且程序不能生成任何依赖未指定行为、未定义行为或者实现定义行为的输出,也不能超出任何实现最低限制。


存在两种形式符合ISO/IEC C语言标准的实现:宿主实现(hosted implementation)和独立实现(freestanding implementation)。宿主实现接受严格符合ISO/IEC C语言标准的程序。独立实现接受严格符合ISO/IEC C语言标准的程序;但程序使用的库仅限于<float.h><iso646.h><limits.h><stdalign.h><stdarg.h><stdbit.h><stdbool.h><stddef.h><stdint.h><stdnoreturn.h>头文件。

独立实现接受严格符合ISO/IEC C语言标准的程序还包括以下两种情况:

-- 使用<string.h>头文件指定的功能,但不包括以下函数:strcollstrdupstrerrorstrndupstrtokstrxfrm

-- 使用<stdlib.h>头文件中的memalignment函数。


在不改变严格符合ISO/IEC C语言标准程序行为的前提下,符合ISO/IEC C语言标准的实现可以存在扩展(包括额外的库函数);这意味着除ISO/IEC C语言标准明确保留的标识符外,实现不保留其它标识符。

定义了宏__STDC_IEC_60559_BFP__或者宏__STDC_IEC_60559_DFP__的符合ISO/IEC C语言标准的独立实现所接受的严格符合ISO/IEC C语言标准的程序,可以使用<fenv.h><math.h>头文件以及<stdlib.h>头文件中strto*浮点数值转换函数,前提是程序未将编译提示FENV_ACCESS的状态设置为on

宿主实现中包含<stdlib.h>头文件保留的所有标识符,在独立实现中包含<stdlib.h>头文件时也会保留。


符合ISO/IEC C语言标准的程序是符合ISO/IEC C语言标准的实现可接受的程序。严格符合ISO/IEC C语言标准的程序旨在符合ISO/IEC C语言标准的实现之间实现最大的可移植性;实现程序取决于实现的不可移植特性。

实现应对实现定义行为、特定语言环境行为以及所有扩展进行定义。


保存C程序文本的单元称为源文件(source files)或者预处理文件(preprocessing files)。从源文件到实现程序功能需要经过编译、执行两个步骤,这两个步骤是在两个数据处理环境中完成的,即编译环境(translation environment)和执行环境(execution environment)。实现在编译环境中编译C源文件;执行环境中执行C程序。执行环境可进一步细分为独立环境(freestanding environment)和宿主环境(hosted environment)。

上述环境的关系如下图所示:

C源文件    编译    编译环境    执行    执行环境    { 独立环境     宿主环境


一、编译环境

源文件以及通过预处理指令#include包含的所有头文件和源文件称为预处理编译单元(preprocessing translation unit)。预处理后,预处理编译单元称为编译单元(translation unit)。先前编译过的编译单元可以单独保存或者保存在库中。程序的独立编译单元通过调用具有外部链接的函数、操作具有外部链接的对象或者操作数据文件来进行通信。C程序不需要同时编译所有单元,编译单元可以单独编译,然后链接生成可执行程序。


C程序的编译可分为以下8个步骤(:实现可能会将多个步骤合并成1个步骤;但实现应表现为出现了8个这样的步骤。源文件、编译单元和编译过的编译单元可以以文件形式存储,也可以通过或者在实现定义的介质中存储。这些实体和外部表示之间也不需要一一对应。这里描述只是概念性的,并未指定任何特定的实现。):

步骤1

如有必要,物理源文件中多字节字符以实现定义的方式映射到源字符集(使用换行符替换行尾指示符)。

ISO/IEC 9899:2024标准已弃用三字符序列(trigraph sequences)。对于符合ISO/IEC 9899:2024标准之前的ISO/IEC 9899标准的编译,此步骤还包括将三字符序列使用对应的单字符内部表示形式替换。)


步骤2

如果反斜杠字符(\)后紧跟换行符,将删除反斜杠字符;将物理源代码行(physical source lines)拼接成逻辑源代码行(logical source lines),例如:

1 
2 
printf("Great works are performed not by strength, \
but by perseverance.");

这里两个物理源代码行会拼接成一个逻辑源代码行。

只有物理源代码行中最后一个反斜杠字符才能成为此类拼接的一部分。非空源文件应以换行符结束;在进行任何此类拼接前,该换行符不能紧跟在反斜杠字符之后。


步骤3

源文件分解成预处理标记和空格字符序列(包括注释);其中预处理标记(preprocessing tokens)包括:头文件名标识符预处理数、字符常量、字符串字面量标点符号、不属于上述字符的通用字符名、不属于上述字符的非空格字符。标准空格字符包括:空格符、换页符、换行符、回车符、水平制表符、垂直制表符。

将源文件分解成预处理标记时,预处理标记应为构成预处理标记的最长字符序列,例如:

A+++++B

将解释为++ ++ + B,而不是++ + ++ B;尽管前者是无效的C程序代码,而后者是有效的C程序代码。

源文件不能以部分预处理标记或者部分注释结束。每个注释将使用一个空格符替换;换行符将保留;除换行符外的空格字符构成的非空序列是保留,还是使用一个空格符替换,将由实现定义。


步骤4

进行预处理:执行预处理指令,扩展宏,并执行_Pragma一元运算符表达式。如果通过标记串联得到匹配通用字符名语法的字符序列,其行为是未定义的。使用#include预处理指令包含的头文件和源文件按步骤1至步骤4递归处理。该步骤结束后,将删除所有的预处理指令。


步骤5

字符常量和字符串字面量中的每个源字符集成员和转义序列将转换成对应的执行字符集成员;如果不存在对应的执行字符集成员,将转换成实现定义的除空字符(空宽字符)外的成员;但实现可以将相同的不存在对应执行字符集成员的源字符转换成不同的执行字符集成员。


步骤6

串联相邻的字符串字面量标记。


步骤7

进行编译:分隔标记的空格字符不再重要;每个预处理标记都转换成标记;对结果标记进行句法和语义分析,将其作为编译单元进行编译。


步骤8

进行链接:所有外部对象和函数引用都已解析。链接库组件,以满足不在当前编译单元中定义的函数和对象的外部引用。所有此类编译器输出都收集到程序映像(program image)中,该程序映像包含在执行环境中执行程序所需的信息。


8个步骤是从C源代码到可执行文件的过程;在前4个步骤中,C预处理器为编译器准备代码。


如果预处理编译单元或者编译单元包含任何违反语法规则或者限制的行为,即使该行为被明确指定为未定义行为或者实现定义行为,遵守ISO/IEC 9899:2024标准的实现应至少生成一条诊断信息。其它情况下不需要生成诊断信息。ISO/IEC 9899:2024标准鼓励实现确定每种违规行为的性质,并在可能的情况下将其本地化。