当前位置: C语言 -- C标准库头文件

C标准库头文件

C语言标准库包含31个标准头文件,具体如下:

<assert.h> 诊断头文件。
<complex.h> 复数算术头文件。
<ctype.h> 字符处理头文件。
<errno.h> 错误头文件。
<fenv.h> 浮点环境头文件。
<float.h> 有关浮点类型特性的头文件。
<inttypes.h> 整数类型格式转换头文件。
<iso646.h> 拼写替换头文件。
<limits.h> 整数类型特性头文件。
<locale.h> 本地化头文件。
<math.h> 数学计算头文件。
<setjmp.h> 非本地跳转头文件。
<signal.h> 信号处理头文件。
<stdalign.h> 对齐头文件。
<stdarg.h> 可变参数头文件。
<stdatomic.h> 原子操作头文件。
<stdbit.h> 位与字节工具头文件。
<stdbool.h> 有关布尔类型和值的头文件。
<stdckdint.h> 检查过的整数算术运算头文件。
<stddef.h> 常用定义头文件。
<stdint.h> 整数类型头文件。
<stdio.h> 输入输出头文件。
<stdlib.h> 通用工具头文件。
<stdnoreturn.h> 不返回头文件。
<string.h> 字符串处理头文件。
<tgmath.h> 泛型数学头文件。
<threads.h> 线程头文件。
<time.h> 日期和时间头文件。
<uchar.h> 统一码工具头文件。
<wchar.h> 扩展的多字节和宽字符工具头文件。
<wctype.h> 宽字符分类和映射工具头文件。

(注:<complex.h>、<stdatomic>和<threads.h>是可选特性,实现可以支持;但ISO/IEC 9899:2024标准不要求实现一定支持。)


一、概述

库函数在头文件中声明,其内容通过#include预处理指令提供。头文件不一定是源文件,并且头文件名中使用< >括起来的部分也不一定是有效的源文件名。头文件声明了相关函数以及使用这些函数所需的类型和宏。

如果实现定义了__STDC_IEC_60559_BFP__、__STDC_IEC_559__或者__STDC_IEC_60559_DFP__,实现应符合ISO/IEC 9899:2024标准附录F中的规定;如果实现定义了__STDC_IEC_60559_COMPLEX__或者__STDC_IEC_559_COMPLEX__,实现应符合ISO/IEC 9899:2024标准附录G中的规定;如果实现定义了__STDC_IEC_60559_TYPES__,实现应符合ISO/IEC 9899:2024标准附录H中的规定;如果实现定义了__STDC_LIB_EXT1__,实现应符合ISO/IEC 9899:2024标准附录K中的规定。这些附录视为已合并到ISO C语言标准的标准库的子条款中。除非另有明确说明,否则在ISO C语言标准的标准库、附录H以及附录K中描述的类型声明不包含类型限定符。

如果实现不支持十进制浮点类型(decimal floating types),那么实现可能也不支持专门为这些类型设计的接口或者接口特性。


如果一个文件同上述某个标准头文件同名,但不是作为实现的一部分提供;并且该文件位于搜索包含源文件的标准位置中,其行为是未定义的。

标准头文件可以按任意顺序包含。在指定范围内每个标准头文件可以包含多次,效果与包含一次是相同的;但<assert.h>头文件例外,包含<assert.h>头文件的效果取决于宏NDEBUG的定义。

如果要使用标准头文件,标准头文件应包含在任何外部声明或者定义之外;并且应先包含标准头文件,再使用该标准头文件中声明的类型、函数、对象和定义的宏。如果一个标识符在多个标准头文件中声明或者定义,第二个及随后的相关头文件可能包含在对该标识符的初始引用之后。包含头文件前或者扩展头文件中定义的宏时,程序中不能存在与当前定义关键词相同的宏。


某些标准头文件定义或者声明了在本文档先前版本中未出现的标识符。为使实现和用户适应这种情况,还定义了用于特性测试的版本宏,其形式为__STDC_VERSION_XXXX_H__,其中XXXX是对应头文件的大写拼写格式。这些版本宏展开后呈现为附录M子条款中描述的值之一,例如:202311L。

ISO C语言标准的标准库的子条款、附录F、附录G、附录H以及附录K中定义的对象式宏必要时应展开为完全由括号保护的代码,使其在任何表达式中像单个标识符一样分组。库函数的任何声明都应具有外部链接。


二、保留标识符

每个标准库头文件都应声明或者定义其对应章节列出的所有标识符,并且可以选择性地声明或者定义未来其对应章节列出的标识符、永久保留用作某种用途的标识符或者具有文件作用域的标识符。

-- 所有由实现通过外部定义方式提供的潜在保留标识符均保留用于某种用途。实现不得提供潜在保留标识符的外部定义,除非该标识符保留用于具有外部链接的用途(所有库函数都具有外部链接。)。当包含关联头文件时,所有其它由实现提供的潜在保留标识符(包括以宏形式提供的标识符。)均保留用于某种用途。其它任何潜在保留标识符均不保留(当实现开始使用某个潜在保留标识符或者未来标准将某个潜在保留标识符保留时,该标识符成为保留标识符;但其它情况下,程序员仍然可以使用该标识符。)。


-- 除非另有明确说明,否则当包含关联头文件时,ISO标准库中的宏名(包括未来ISO标准库中的宏名。)将按规定保留使用。

#include <float.h>

//宏FLT_RADIX是<float.h>头文件中定义的宏。
//在包含<float.h>头文件的情况下,再定义宏FLT_RADIX非法。
//未包含<float.h>头文件的情况下,定义宏FLT_RADIX合法。
#define FLT_RADIX 8

-- ISO标准库(包括未来ISO标准库)中所有具有外部链接的标识符以及errno始终保留用作具有外部链接的标识符。

(注:ISO/IEC 9899:2024标准中具有外部链接的保留标识符包括:math_errhandling、setjmp、va_copy和va_end。)


-- 当包含关联头文件时,ISO标准库(包括未来ISO标准库)中具有文件作用域的标识符均保留用作宏名,并作为同一命名空间内具有文件作用域的标识符。


三、库函数的使用

除非另有明确说明,否则库函数的使用遵循以下规则:

-- 如果函数实参具有无效值(例如:实参值超出函数定义的值域范围、实参指针指向程序地址空间以外、当对应形参没有const类型限定符限定时实参指针指向不可修改存储区域等。),或者对于可变参数函数,实参类型经过默认参数提升后与函数定义的类型不匹配,其行为是未定义的。

double d = sqrt(-1.0);  //域错误,未定义行为。

-- 如果函数实参是数组,传递给函数的指针应确保:所有地址计算以及对象访问操作都是有效的(如果指针指向数组首元素时,所有地址计算以及对象访问操作都是有效的。)。

(注:所有地址计算以及对象访问操作都有效包括:传递大小为0、指向数组末尾后一个位置的有效指针,或者使用大小为0的有效指针。指针有效是指指针指向的地址合法;大小为0是指指针指向的地址不可访问。)

int func(int n, const int *p)
{
  int sum = 0;

  for(int i=0; i<n; i++)
    sum += p[i];

  return sum;
}
...
const int arr[3] = {3, 6, 9};
const int *ptr = arr;
func(3, ptr);

-- 头文件中声明的任何函数,实现都可以在该头文件中定义额外的函数式宏;但可以给函数名外加括号来抑制函数的宏定义,因为宏名后存在左括号是函数式宏的标志。即使存在函数式宏定义,ISO标准允许获取库函数的地址(这意味着:即使实现为函数提供了宏定义,实现也必须为库函数提供实际函数定义。)。使用#undef预处理指令可以删除函数式宏定义,这样可以确保函数调用时引用真正的函数。

putchar('a');   //函数调用可能是函数引用,也可能是函数式宏展开。
(putchar)('A'); //函数调用是函数引用,一定不是函数式宏展开。

#undef putchar
putchar('b');	//函数调用是函数引用,一定不是函数式宏展开。

-- 调用以宏形式实现的库函数时,宏应展开为确保每个实参仅被评估一次的代码,并在必要时使用括号实现完全保护,这样可以安全地使用任一表达式作为实参。

(注:此类宏有时可能不包含对应函数调用具有的序列点(sequence points)。)


-- ISO标准库中的函数式宏可在任何可调用具有兼容返回类型的函数的位置进行表达式调用。


-- 所有展开为整数常量表达式的对象式宏应适用于条件表达式包含预处理指令。

#ifndef __STDC_NO_COMPLEX__
  #include <complex.h>
#endif

如果库函数的声明无需引用任何在头文件中定义的类型,那么允许在不包含其关联头文件的情况下声明使用该函数。库函数返回前存在一个序列点。标准库函数不保证可重入,并且可能修改具有静态存储期限或者线程存储期限的对象;因此信号处理程序通常不能调用标准库函数。


除非另有明确说明,库函数按以下方式防止数据竞争(data races):库函数不得直接或者间接访问当前线程外的线程可访问对象,除非这些对象可以通过函数实参直接或者间接访问。库函数不得直接或者间接修改当前线程外的线程可访问对象,除非这些对象可以通过函数实参(const类型限定符限定的函数实参除外。)直接或者间接访问。实现可以在线程间共享其内部对象,前提是这些对象对用户不可见,并且受到数据竞争的保护。

(注:这意味着:实现不能在未进行同步的情况下将静态对象用于内部目的,因为即使程序未在线程间显式共享对象的情况下,此类操作也可能引发数据竞争。memcpy函数的实现不能在复制超过目标对象指定长度的字节后恢复原始值,因为如果程序在线程间共享这些字节,此类操作可能引发数据竞争。)


除非另有明确说明,否则库函数仅在当前线程内执行所有操作,如果这些操作存在对用户可见的影响。这允许实现在没有可见副作用的情况下进行并行化操作(parallelize operations)。



主要参考资料:

1、ISO/IEC 9899:2024

2、ISO/IEC 9899:2018