2012年(10)
分类:
2012-08-13 20:42:41
各种实现定义了很多魔数和常量。其中有许多使用临时特定的技术(ad hoc technique)被硬编码在程序里。随着各种标准化的努力,有更多的可移植的方法可以用来决定这些魔数和由实现定义的限量,极大地提高了我们软件的可移植性。
总共需要两种类型的限量:
1、编译期限量(比如short型的最大值);
2、运行期限量(比如文件名的最大字符数);
编译期限量可以被定义在头文件里,任何程序都可以在编译期引入,但是运行期限量需要进程调用函数来获得限量的值。
此外,有一些限量在一些实现上是固定的,它们能静态地定义在一个头文件里,然而在其它实现上是变化的,从而需要一个运行期的函数调用。这种类型的限量的例
子有文件名的最大字符数。在SVR4前,历史上系统V只允许一个文件名里有14个字符,而BSD的后裔把这个数值增大到了255。大多数UHIX系统实现
最近都支持多个文件系统类型,而且每种类型都有它自己的限量。这种情况下,一个文件名的运行期限量取决于它的文件系统。比如在root文件系统可以有14
个字符的限量,而在另一个系统可以有255个字符的限量。
为了解决这样的问题,有三种类型的限量被提供:
1、编译期限量(头文件);
2、与文件和目录无关的运行期限量(sysconf函数);
3、与文件和目录有关的运行期限量(pathconf和fpathconf函数)。
如果一个特定的运行期限量在一个系统实现上是不变的,则它可以在头文件里静态定义。如果不定义在头文件里,则应用程序必须调用三个conf函数中的一个来得到运行期的值。
ISO C限量
所有在ISO
C里定义的限量都是编译期限量。下表给出定义在
在 |
|||
名称 |
描述 |
可接受的最小值 |
典型值 |
CHAR_BIT | char里的位数 | 8 | 8 |
CHAR_MAX | char的最大值 | 稍后讨论 | 127 |
CHAR_MIN | char的最小值 | 稍后讨论 | -128 |
SCHAR_MAX | signed char的最大值 | 127 | 127 |
SCHAR_MIN | signed char的最小值 | -127 | -128 |
UCHAR_MAX | unsigned char的最大值 | 255 | 255 |
INT_MAX | int的最大值 | 32,767 | 2,147,483,647 |
INT_MIN | int的最小值 | -32,767 | -2,147,483,648 |
UINT_MAX | unsigned int的最大值 | 65,535 | 4,294,967,295 |
SHRT_MIN | short的最小值 | -32,767 | -32,768 |
SHRT_MAX | short的最大值 | 32,767 | 32,767 |
USHRT_MAX | unsigned short的最大值 | 65,535 | 65,535 |
LONG_MAX | long的最大值 | 2,147,438,647 | 2,147,483,647 |
LOGN_MIN | long的最小值 | -2,147,483,647 | -2,147,483,648 |
ULONG_MAX | unsigned long的最大值 | 4,294,967,295 | 4,294,967,295 |
LLONG_MAX | long long的最大值 | 9,223,372,036,854,775,807 | 9,223,372,036,854,775,807 |
LLONG_MIN | long long的最小值 | -9,223,372,036,854,775,807 | -9,223,372,036,854,775,808 |
ULLONG_MAX | unsigned long long的最大值 | 18,446,744,073,709,551,615 | 18,446,744,073,709,551,615 |
MB_LEN_MAX | 多字节字符常量的最大字节数 | 1 | 16 |
浮点数据类型定义在
另一个我们将碰到的ISO
C常量是FOPEN_MAX,它表示一个系统实现可以保证的同时打开的标准I/O流的最小数量。这个值定义在
ISO C在
下表我们给出本文讨论的四个系统上FOPEN_MAX和TMP_MAX的值:
限量 | FreeBSD 5.2.1 | Linux 2.4.22 | Mac OS X 10.3 | Solaris 9 |
FOPEN_MAX | 20 | 16 | 20 | 20 |
TMP_MAX | 308,915,776 | 238,328 | 308,915,776 | 17,576 |
ISO C还定义了常量FILENAME_MAX,但是我们应避免使用它,因为由于历史原因有些操作系统把它设得太小而不适合使用。
POSIX限量
POSIX.1定义了许多处理操作系统实现的限量的常量。不幸的是,这是一个更令人困惑特性。尽量POSIX.1定义了许多限量和常量,但我们仅仅关心影响基本POSIX.1接口的那些。这些限量和常量被分为五个各类:
1、不变的最小值:下表中的19个常量;
2、不变的值:SSIZE_MAX;
3、运行期可增长的值:CHARCLASS_NAME_MAX、COLL_WEIGHTS_MAX、LINE_MAX、NGROUPS_MAX、RE_DUP_MAX;
4、运行期可变化的值,可能不定:ARG_MAX、CHILD_MAX、HOST_NAME_MAX、LOGIN_NAME_MAX、OPEN_MAX、
PAGESIZE、RE_DUP_MAX、STREAM_MAX、SYMLOOP_MAX、TTY_NAME_MAX和TZNAME_MAX;
5、路径名变量的值,可能不定:FILESIZEBITS、LINK_MAX、MAX_CANON、MAX_INPUT、NAME_MAX、PATH_MAX、PIPE_BUF和SYMLINK_MAX。
在 |
||
名称 | 描述:...的可接受的最小值 | 值 |
_POSIX_ARG_MAX | exec函数参数的长度 | 4,096 |
_POSIX_CHILD_MAX | 每个真实用户ID的子进程数 | 25 |
_POSIX_HOST_NAME_MAX | gethostname返回的主机名的最大长度 | 255 |
_POSIX_LINK_MAX | 一个文件的链接数 | 8 |
_POSIX_LOGIN_NAME_MAX | 登录名的最大长度 | 9 |
_POSIX_MAX_CANON | 终端最简洁的(canonical)输入队列的字节数 | 255 |
_POSIX_MAX_INPUT | 终端输入队列的可用空格 | 255 |
_POSIX_NAME_MAX | 文件名的字节数,不包括终止字符null | 14 |
_POSIX_NGROUPS_MAX | 一个进程的同步的补充组ID的数量 | 8 |
_POSIX_OPEN_MAX | 一个进程打开的文件数 | 20 |
_POSIX_PATH_MAX | 路径名的字节数,包括终止符null | 256 |
_POSIX_PIP_BUF | 可被原子写入管道的字节数 | 512 |
_POSIX_RE_DUP_MAX | 当使用间隔标记“\{m,n\}”时,被regexec和regcomp函数认可的基本正则表达式的重复出现次数 | 255 |
_POSIX_SSIZE_MAX | 可以存入ssize_t对象的值 | 32,767 |
_POSIX_STREAM_MAX | 一个进程能同时打开的标准I/O流的数量 | 8 |
_POSIX_SYMLINK_MAX | 符号链接的字节数 | 255 |
_POSIX_SYMLOOP_MAX | 路径名解析时可以转换的符号链接数 | 8 |
_POSIX_TTY_NAME_MAX | 终端设备名的长度,包括终止符null | 9 |
_POSIX_TZNAME_MAX | 时区名的字节数 | 6 |
在这44个限量和常量中,有些可能定义在
19个不变最小值定义在上表中。这些值不会因为系统不同而有所区别。它们为那些特性定义了最具有约束性的值。遵守POSIX.1的系统实现必须提供至少那
么大的值,这也是为什么它们被称为最小值的原因,尽管它们名字里都含有“MAX”。还有,为了确保可移植性,一个严格遵守标准的应用程序一定不能要求一个
更大的值。我们在本文会讨论所有这些常量。
一个严格遵守POSIX标准(srictly-conforming POSIX)的应用程序与一个仅遵守POSIX标准(merely POSIX
conforming)的应用程序不同。一个严格遵守的应用程序只使用IEEE标准1003.1-2001的接口。一个严格遵守的应用程序是一个
POSIX程序,而且不信赖于任何无定义的行为、不使用任何逐步废弃的接口、以及不使用比上表中的最小值更大的值。
不幸的是,不变最小值里有一些太小了,而不适合实际使用。比如,多数UNIX系统为每个进程打开远大于20个的文件。同
样,_POSIX_PATH_MAX的255的限制也太小,路径名会超过这个限制。这意味着我们不能使用_POSIX_OPEN_MAX和
_POSIX_PATH_MAX作为编译期的数组大小。
上表中的19个不变最小值中,每一个都有一个相应实现,名字为去掉_POSIX_前缀的常量名。没有_POSIX前绷的名字说明这是个真实系统实现上支持
的值。这19个实现值是早先我们列出的第2~5项:不变值、运行期可增长的值、运行期不变值和路径名变量值。问题是这19个实现值并不保证一定定义
在
比如,一个特定的值可能没有包含在这个头文件里,如果它的进程的真实值依赖于系统里的内存总量。如果值没有在头文件里定义,我们不能在编译期里把它作为数
组边界。如以,
POSIX.1决定为我们提供三个运行期函数:sysconf、pathconf和fpathconf,用来得到在运行时的真实实现值。尽管如此,仍然有
个问题,POSIX.1定义的值里面有些被定义为“可能不确定的”,即理论上是“无限”的。这意味着这个值没有实际的上限。比如,在Linux上,可以被
readv和writev使用的iovec结构的数量只限于系统的内存总量。为此,IOV_MAX在Linux被认为是“不确定”的,我们在讲述后面的运
行期限量时再来讨论这个问题。
XSI 限量
XSI同样定义了处理实现限量的常数,包括:
1、不变最小量:下表中的10个常量;
2、数字限量:LONG_BIT和WORD_BIT;
3、运行期不变值,可能不确定:ATEXIT_MAX、IOV_MAX和PAGE_SIZE。
|
|||
名称 |
描述 |
最小可接受值 |
典型值 |
NL_ARGMAX | printf和scanf调用数字的最大值 | 9 | 9 |
NL_LANGMAX | LANG环境变量的最大字节数 | 14 | 14 |
NL_MSGMAX | 最大消息数量 | 32,767 | 32,767 |
NL_NMAX | 多对一映射字符的最大字节数 | 未定义 | 1 |
NL_SETMAX | 最大集合数量 | 255 | 255 |
NL_TEXTMAX | 消息字符串的最大字节数 | _POSIX2_LINE_MAX | 2,048 |
NZERO | 默认的进程优先级 | 20 | 20 |
_XOPEN_IOV_MAX | readv和writev使用的iovec结构的最大数量 | 16 | 16 |
_XOPEN_NAME_MAX | 文件名的最大字节数 | 255 | 255 |
_XOPEN_PATH_MAX | 路径名的最大字节数 | 1,024 | 1,024 |
函数synconf、pathconf和fpathconf
我们已经列出一个系统实现必须支持的各种最小值,但是我们怎么知道一个特定系统真正支持了那些限量呢?如我们早先所述,这些限量中有些可以在编译期可用, 而一些只有在运行期得到。我们也提到过有些限量在某个系统上是固定的,而其它可能是变化的,因为它们与文件或目录相关。运行期限量可以通过调用以下三个函 数来得到:
#include
long sysconf(int name);
long pathconf(const char* pathname, int name);
long fpathconf(int filedes, int name);
这三个函数成功都返回一个相应的值,而失败则返回-1。
最后两个函数的区别在于一个接受路径名作为参数,而另一个接受文件描述符作为参数。
下表列出了sysconf得到系统限量所用的名字参数,都以_SC_开头:
sysconf的名字参数以及相应的限量 | ||
---|---|---|
限量名 |
描述 |
名字参数 |
AGR_MAX | exec函数参数的最大长度,以字节为单位 | _SC_ARG_MAX |
ATEXIT_MAX | 可以被atexit函数注册的函数的最大数量 | _SC_ATEXIT_MAX |
CHILD_MAX | 一个真实用户ID可以拥有的最大进程数 | _SC_CHILD_MAX |
clock ticks/second | 每秒钟的时钟周期 | _SC_CLK_TCK |
COLL_WEIGHTS_MAX | 在本地定义文件里可以赋值给LC_COLLATE命令关键字的重量的最大数量 | _SC_SOLL_WEIGHTS_MAX |
HOST_NAME_MAX | gethostname返回的主机名的最大长度 | _SC_HOST_NAME_MAX |
IOV_MAX | readv和writev使用的iovec结构的最大数量 | _SC_IOV_MAX |
LINE_MAX | 实用工具的输入行的最大长度 | _SC_LINE_MAX |
LOGIN_NAME_MAX | 登录名的最大长度 | _SC_LOGIN_NAME_MAX |
NGROUPS_MAX | 每个进程的同步补充组ID数 | _SC_NGROUPS_MAX |
OPEN_MAX | 一个进程打开文件的最大数量 | _SC_OPEN_MAX |
PAGESIZE | 系统内存页的字节数 | _SC_PAGESIZE |
PAGE_SIZE | 系统内存页的字节数 | _SC_PAGE_SIZE |
RE_DUP_MAX | regexec和regcomp函数使用间隔符\{m,n\}可以识别的基本表达式的重复次复 | _SC_RE_DUP_MAX |
STREAM_MAX | 在任何时候一个进程的标准流的最大数量;如果有定义,这个值必须与FOPEN_MAX一样 | _SC_STREAM_MAX |
SYMLOOP_MAX | 路径名解析时可以处理的符号链接数 | _SC_SYMLOOP_MAX |
TTY_NAME_MAX | 终端设备名的长度,包括终止符null | _SC_TTY_NAME_MAX |
TZNAME_MAX | 时区名的最大字节数 | _SC_TZNAME_MAX |
下表列出了pathconf和fpathconf函数使用的名字参数,都以_PC_开头:
pathconf和fpathconf的名字参数以及相应限量 | ||
限量名 | 描述 | 名字参数 |
FILESIZEBITS | 为了表示在指定目录下允许的普通文件大小的最大值,所使用的有符号整型值所需的最小比特位数 | _PC_FILESIZEBITS |
LINK_MAX | 一个文件链接数的最大值 | _PC_LINK_MAX |
MAX_CONON | 一个终端最简洁的输入队列的最大字节数 | _PC_MAX_CONON |
MAX_INPUT | 一个终端的输入队列可用空间的字节数 | _PC_MAX_INPUT |
NAME_MAX | 文件名的最大字节数,不包括终止符null | _PC_NAME_MAX |
PATH_MAX | 相对路径名的最大字节数,包括终止符null | _PC_PATH_MAX |
PIPE_BUF | 可以原子写入管道的最大字节数 | _PC_PIP_BUF |
SYMLINK_MAX | 一个符号链接的字节数 | _PC_SYMLINK_MAX |
下面再深入讨论下这三个函数的不同的返回值:
1、如果名字参数不正确,这三个函数都会返回-1并设置errno值为EINVAL。
2、一些名字参数可以返回一个变量的值,或者表明这个值是不确定的。通过返回-1但不改变erron的值来表示一个不确定的值;
3、_SC_CLK_TC的返回值是每秒钟的时钟周期,用来与times函数的返回值共同使用。
在传递路径名给pathconf和域参数给fpathconf时有些约束。如果这些约束中任意一个没有满足,则结果无定义:
1、_PC_MAX_CONON和_PC_MAX_INPUT的对应的文件并须是一个终端文件;
2、_PC_LINK_MAX的对应的文件既可以是文件也可以是目录。如果相应文件是一个目录,则返回值对应于目录本身,而不是目录下的文件;
3、_PC_FILESIZEBITS和_PC_NAME_MAX的相应文件必须是目录。返回值对应于这个目录下的文件;
4、_PC_PATH_MAX相应的文件必须是目录。当这个目录是工作目录时,返回值是相对路径名的最大长度。(不幸的是,这不是我们想知道的绝对路径的真实长度,稍后再来讨论这个问题。)
5、_PC_PIPE_BUF的相应文件必须是个管道,FIFO或者目录。前两种情况下返回值为相应管道或FIFO的限量。最后一种情况的返回值为指定目录下创建的任一FIFO的限量。
6、_PC_SYMLINK_MAX的相应文件必须是一个目录。返回值为目录里的符号链接可以包含的字符串的最大长度。
不确定的运行期限量(Indeterminate Runtime Limits)
我们之前提到了有运行期变量可以是不确定的。问题在于如果这些限量没有在
路径名(Pathname)
许多程序需要为路径名分配内存。典型地,内存在编译期分配,而一些魔数(没有一个是正确的值)被用来作为数组尺寸:256、512、1024或都标准
I/O常量BUFSIZ。定义在
POSIX.1试图用PATH_MAX来解决这个问题,但如果这个值是不确定的,那我们仍然不走运。本文使用下面这个函数来动态地为路径名分配内存:
SUSv3之前的标准并没有清楚说明PATH_MAX最末尾是否包括一个null的字节。如果操作系统实现信赖于这些早期的标准,则我们需要加一个字节的内存,这样会更安全些。
处理不确定结果情况的正确方法取决于分配的空间怎样被使用。如果分配的内存是为了函数调用getcwd--比如:返回当前工作目录的绝对路径名--而且如
果分配的空间太小的话,那errno会返回一个错误ERANGE。我们可以接着调用realloc来增加这块分配的空间并再次尝试。我们可以持续这样做,
直到getcwd成功为止。
打开文件的最大数量(Maximum Number of Open Files)
一个后台进程(daemon process)--运行在后台,不与任何终端关联的进程--的一个普遍代码流程是关闭所有打开的文件。一些程序用以下代码,设想常量NOFILE定义在
其它一些程序使用一些版本的
我们本希望用POSIX.1的值OPEN_MAX来决定这个值,但如果这个值是不确定的,我们仍然会有问题。如果我们把代码写成下面的模样,而同时OPEN_MAX是不确定的,那会造成死循环,因为sysconf会返回-1:
我们最好的解决方案是仅仅关闭所有不超过一个任意上限(比如256)的描述符。和我们的pathname例子一样,这个并不保证可以在所有情况下都工作,但这是我们能做的最好的事情。看如下代码:
一些系统实现会返回LONG_MAX当作限值,来有效的表示没有限制。Linux的ATEXIT_MAX就是其一。这不是个好方法,因为它可能会导致程序很坏的行为。
比如,我们可以用Bourne-again
shell的内置ulimit命令来改变我们进程可以同时打开的文件数的最大值。如果这个限制要设置成无限制的话,通常需要一个特殊(超级用户)的权限。
但一旦它被设置为无限的话,sysconf会返回LONG_MAX作为OPEN_MAX的值。程序信赖于这个值作为关闭的文件描述符的上限,而尝试关闭
2,147,483,647个文件描述符是非常浪费时间的,而它们之中大多数都没有被使用。
支持UNIX单一规范里的XSI扩展的系统会提供getrlimit函数。它能用来返回一个进程能打开的描述符的最大数量。利用这个函数,我们可以确保我们的进程没有打开超过预设的上限数的文件,从而避免上述问题。
OPEN_MAX被POSIX称为运行期不变量,意味着它的值不能在一个进程的生命周期内改变。但在支持XSI扩展的系统上我们可以调用
setrlimit函数在为一个运行着的进程改变这个值。(这个值也可以在C
shell上用limit命令改变,或在Bourne、Bourne-again和Knon
shell上用ulimit函数。)如果我们的系统支持这个功能,我们可以改变上述代码:每次调用open_max函数时,都调用sysconf,而不仅
仅在第一次调用时。