-
xzsdn_08



- 注册时间 01-04-2009
- 江苏徐州
- 发帖总数 641
|
- 20.1 怎样从键盘直接读入字符而不用等 RETURN 键?怎样防止字符输入时的回显?
- 20.2 怎样知道有未读的字符, 如果有, 有多少?如果没有字符, 怎样使读入不阻断?
- 20.3 怎样显示一个百分比或 ``转动的短棒" 的进展表示器?
- 20.4 怎样清屏?怎样输出彩色文本?怎样移动光标到指定位置?
- 20.5 怎样读入方向键, 功能键?
- 20.6 怎样读入鼠标输入?
- 20.7 怎样做串口 (``comm") 的输入输出?
- 20.8 怎样直接输出到打印机?
- 20.9 怎样发送控制终端或其它设备的逃逸指令序列?
- 20.10 怎样直接访问输入输出板?
- 20.11 怎样做图形?
- 20.12 怎样显示 GIF 和 JPEG 图象?
- 20.13 怎样检验一个文件是否存在?
- 20.14 怎样在读入文件前, 知道文件大小?
- 20.15 怎样得到文件的修改日期和时间?
- 20.16 怎样缩短一个文件而不用清除或重写?
- 20.17 怎样在文件中插入或删除一行 (或记录)?
- 20.18 怎样从一个打开的流或文件描述符得到文件名?
- 20.19 怎样删除一个文件?
- 20.20 怎样复制一个文件?
- 20.21 为什么用了详尽的路径还不能打开文件? fopen("c:\ newdir \file.dat", "r") 返回错误。
- 20.22 fopen() 不让我打开文件: "$HOME/.profile" 和 "~/ .myrcfile"。
- 20.23 怎样制止 MS-DOS 下令人担忧的 ``Abort, Retry, Ignore?" 信息?
- 20.24 遇到 ``Too many open files (打开文件太多)" 的错误, 怎样增加同时打开文件的允许数目?
- 20.25 怎样在 C 中读入目录?
- 20.26 怎样找出系统还有多少内存可用?
- 20.27 怎样分配大于 64K 的数组或结构?
- 20.28 错误信息 ``DGROUP data allocation exceeds 64K (DGROUP 数据分配内存超过 64K)" 说明什么?我应该怎么做?我以为使用了大内存模型, 那我就可以使用多于 64K 的数据!
- 20.29 怎样访问位于某的特定地址的内存 (内存映射的设备或图显内存)?
- 20.30 怎样在一个 C 程序中调用另一个程序 (独立可执行的程序, 或系统命令)?
- 20.31 怎样调用另一个程序或命令, 同时收集它的输出?
- 20.32 怎样才能发现程序自己的执行文件的全路径?
- 20.33 怎样找出和执行文件在同一目录的配置文件?
- 20.34 一个进程如何改变它的调用者的环境变量?
- 20.35 怎样读入一个对象文件并跳跃到其中的地址?
- 20.36 怎样实现精度小于秒的延时或记录用户回应的时间?
- 20.37 怎样抓获或忽略像 control-C 这样的键盘中断?
- 20.38 怎样很好地处理浮点异常?
- 20.39 怎样使用 socket? 网络化? 写客户/服务器程序?
- 20.40 怎样调用 BIOS 函数?写 ISR?创建 TSR?
- 20.41 编译程序, 编译器出示 ``union REGS" 未定义错误信息, 连接器出示 ``int86()" 的未定义错误信息。
- 20.42 什么是 ``near" 和 ``far" 指针?
- 20.43 我不能使用这些非标准、依赖系统的函数, 程序需要兼容 ANSI!
20.1 怎样从键盘直接读入字符而不用等 RETURN 键?怎样防止字符输入时的回显? 唉, 在 C 里没有一个标准且可移植的方法。在标准中跟本就没有提及屏幕和键盘的概念, 只有基于字符 ``流" 的简单输入输出。
在某个级别, 与键盘的交互输入一般上都是由系统取得一行的输入才提供给需要的程序。这给操作系统提供了一个加入行编辑的机会 (退格、删除、消除等), 使得系统地操作具一致性, 而不用每一个程序自己建立。当用户对输入满意, 并键入 RETURN (或等价的键)后, 输入行才被提供给需要的程序。即使程序中用了读入单个字符的函数 (例如 getchar() 等), 第一次调用就会等到完成了一整行的输入才会返回。这时, 可能有许多字符提供给了程序, 以后的许多调用 (象 getchar() 的函数) 都会马上返回。
当程序想在一个字符输入时马上读入, 所用的方式途径就采决于行处理在输入流中的位置, 以及如何使之失效。在一些系统下 (例如 MS-DOS, VMS 的某些模态), 程序可以使用一套不同或修改过的操作系统函数来扰过行输入模态。在另外一些系统下 (例如 Unix, VMS 的另一些模态), 操作系统中负责串行输入的部分 (通常称为 ``终端驱动") 必须设置为行输入关闭的模态, 这样, 所有以后调用的常用输入函数 (例如 read(), getchar() 等) 就会立即返回输入的字符。最后, 少数的系统 (特别是那些老旧的批处理大型主机) 使用外围处理器进行输入, 只有行处理模式。
因此, 当你需要用到单字符输入时 (关闭键盘回显也是类似的问题), 你需要用一个针对所用系统的特定方法, 假如系统提供的话。新闻组 comp.lang.c 讨论的问题基本上都是 C 语言中有明确支持的, 一般上你会从针对个别系统的新闻组以及相对应的常用问题集中得到更好的解答, 例如 comp.unix.questions 或 comp.os.msdos.programmer。另外要注意, 有些解答即使是对相似系统的变种也不尽相同, 例如 Unix 的不同变种。同时也要记住, 当回答一些针对特定系统的问题时, 你的答案在你的系统上可以工作并不代表可以在所有人的系统上都工作。
然而, 这类问题被经常的问起, 这里提供一个对于通常情况的简略回答。
某些版本的 curses 函数库包含了 cbreak(), noecho() 和 getch() 函数, 这些函数可以做到你所需的。如果你只是想要读入一个简短的口令而不想回显的话, 可以试试 getpass()。在 Unix 系统下, 可以用 ioctl() 来控制终端驱动的模式, ``传统"系统下有 CBREAK 和 RAW 模式, System V 或 POSIX 系统下有 ICANON, c_cc[VMIN] 和 c_cc[VTIME] 模式, 而 ECHO 模式在所有系统中都有。必要时, 用函数 system() 和 stty 命令。更多的信息可以查看所用的系统, 传统系统下, 查看 <sgtty.h> 和 tty(4), System V 下, 查看 <termio.h> 和 termio(4), POSIX 下, 查看 <termios.h> 和 termios(4)。在 MS-DOS 系统下, 用函数 getch() 或 getche(), 或者相对应的 BIOS 中断。在 VMS 下, 使用屏幕管理例程 (SMG$), 或 curses 函数库, 或者低层 $QIO 的 IO$_READVBLK 函数, 以及 IO$M_NOECHO 等其它函数。也可以通过设置 VMS 的终端驱动, 在单字符输入或 ``通过" 模式间切换。 如果是其它操作系统, 你就要靠自己了。
另外需要说明一点, 简单的使用 setbuf() 或 setvbuf() 来设置 sdtin 为无缓冲, 通常并不能切换到单字符输入模式。
如果你在试图写一个可移植的程序, 一个比较好的方法是自己定义三套函数: 1) 设置终端驱动或输入系统进入单字符输入模式, (如果有必要的话), 2) 取得字符, 3) 程序使用结束后的终端驱动复原。理想上, 也许有一天, 这样的一组函数可以成为标准的一部分。本常用问题集的扩充版 (参见问题 20.36) 含有一套适用于几个流行系统的函数。
参见问题 19.2
参考资料: [PCS, Sec. 10 pp. 128-9, Sec. 10.1 pp. 130-1]; [POSIX, Sec. 7]。
20.2 怎样知道有未读的字符, 如果有, 有多少?如果没有字符, 怎样使读入不阻断? 这个问题也是完全和操作系统有关。某些版本的 curses 函数库有 nodelay() 的函数。根据所用系统的不同, 也许你可以使用 ``不阻断输入输出 (nonblocking I/O)", 或者系统函数 select 或 poll, 或者用 ioctl 的 FIONREAD, c_cc[VTIME], kbhit(), rdchk(), open() 或 fcntl() 的参数 O_NDELAY。参见问题 19.1。
20.3 怎样显示一个百分比或 ``转动的短棒" 的进展表示器? 这个简单的事情, 你可以做到相当的可移植。输出字符 '\r' 通常可以得到一个回车而没有换行, 这样你就可以复写当前行。字符 '\b' 代表退格, 通常会使光标左移一格。记住要调用 fflush()。
参考资料: [ISO, Sec. 5.2.2]。
20.4 怎样清屏?怎样输出彩色文本?怎样移动光标到指定位置? 这些功能跟你所用的终端类型 (或显示器) 有关。你需要使用 termcap, term-info 或 curses 类的函数库, 或者系统提供的特殊函数。在 MS-DOS 系统下, 有两个函数可以使用 clrscr() 和 gotoxy()。
有一个不彻底的可移植的清屏方法: 输出卷纸字符 ('\f'), 可以清除一部分的显示。还有个更加可移植的办法 (尽管很简陋), 输出足够多的换行使当前屏幕清空。最后一个方法: 使用 system() 函数 (参见问题 19.30) 来调用操作系统的清屏指令。
参考资料 [PCS, Sec. 5.1.4 pp. 54-60, Sec. 5.1.5 pp. 60-62]。
20.5 怎样读入方向键, 功能键? terminfo, 某些版本的 termcap, 以及某些版本的 curses 函数库有对这些非 ASCII 键的支持。典型的, 一个特殊键会发送一个多字符序列 (通常以 ESC ['\033'] 字符开头)。分析这个多字符序列比较麻烦。如果你首先调用了 keypad(), curses 会帮你做分析。
在 MS-DOS 下, 如果你在读入键盘输入时, 收到一个值为 0 的字符 (不是字符 '0'), 这就标志着下一个读入的值代表一个特殊键。有关键盘的编码可参见任何 DOS 的编程指南。简单的说明: 上、下、左、右键的编码是 72, 80, 75, 77, 功能键从 59 到 68。
参考资料: [PCS, Sec. 5.1.4 pp. 56-7]。
20.6 怎样读入鼠标输入? 请查阅你的系统文档, 或者在特定系统的新闻组寻问, 请先查看其组的 FAQ。鼠标的处理在 X Windown 系统, MD-DOS, Macintosh 下是完全不同的, 也许每个系统都不一样。
参考资料 [PCS, Sec. 5.5 pp. 78-80]。
20.7 怎样做串口 (``comm") 的输入输出? 这也是跟所用系统有关的。在 Unix 下, 你通常打开、读入、写出在 /dev 下的一个设备文件, 使用终端驱动提供的工具来调整设备的特性。(参见问题 19.1 和 19.2)。在 MS-DOS 下, 你可一使用预定义的 stdaux 流, 或特殊文件 COM1, 或基础的 BIOS 中断, 又或者你需要更好的性能, 使用任何一个中断驱动的串口输入输出包。许多网友推荐 Joe Campbell 的书《C 程序员串口通讯指南》 (``C Programmer's Guide to Serial Communications")。
20.8 怎样直接输出到打印机? Unix 系统下, 使用 popen() (参见问题 19.31) 来把输出写到 lp 或 lpr 中, 或者打开特殊文件 /dev/lp。 MS-DOS 下, 写向预定义的 stdprn 流 (非标准), 或者打开特殊文件 PRN 或 LPT1。在某些情况下, 另一个方法 (也许是唯一的方法) 是用窗口管理者的屏幕截图功能, 然后打印得到的图形。
参考资料: [PCS, Sec. 5.3 pp. 72-74]。
20.9 怎样发送控制终端或其它设备的逃逸指令序列? 如果你能够找到发送字符到设备的方法 (参见问题 19.8), 发送逃逸指令序列是件很容易的事。 在 ASCII 代码中, 逃逸字符的代码是 033 (十进制 27), 以下代码就可以发送逃逸序列 ESC[J:
fprintf(ofd, "\033[J");
20.10 怎样直接访问输入输出板? 通常, 有两种办法: 使用特定的系统函数, 例如 inport 和 outport (如果设备由输入输出接口访问), 或者使用人为的指针变量来访问内存映射的输入输出 (memory-mapped I/O) 设备。参见问题 19.29。
20.11 怎样做图形?
从前, Unix 下有一套相当不错且小巧的设备独立的绘制函数 (plot(3) 和 plot(5))。由 Robert Maier 写的 GNU libplot 函数库保持了同样的精神, 支持许多现代的绘制设备 (http://www.gnu.org/software/plotutils/plotutils.html)。
OpenGL 是一个现代的平台独立的制图函数库, 它也支持三维制图和动画。其它有关的制图标准有 GKS 和 PHIGS。
如果你在 MS-DOS 下编程, 你大概需要用到符合 VESA 或 BGI 标准的函数库。
如果你要和某一个特定的制图仪打交道, 通常发送适当的逃逸序列就可以绘图了, 参见问题 19.9. 销售商可能会提供一个可以从 C 调用的函数库, 或者你也许可以在网上找到。
如果你需要在某个特定的视窗环境下编程 (Macintosh, X Window, Microsoft Windows), 你使用其提供的工具; 参阅相关的文档、新闻组或 FAQ。
参考资料: [PCS, Sec. 5.4 pp. 75-77]。
20.12 怎样显示 GIF 和 JPEG 图象? 这跟你用的显示环境有关, 有可能环境已经提供了这些函数。 http://www.ijg.org/files/ 有个可供参考的 JPEG 软件。
20.13 怎样检验一个文件是否存在? 要做到可靠而可移植的检验出乎意料的困难。如果从你检验到你打开文件前, 这个文件被 (别的进程) 生成或删除了, 所做的任何检验都会失效。
三个可能用作检验的函数是 stat(), access() 和 fopen()。当使用 fopen() 作近似检验时, 用只读打开, 然后马上关闭, 但是失败并不代表不存在。这里, 只有 fopen() 据有广泛的可移植性, 如果系统提供 access, 而程序用了 Unix 的 UID 设置特性, 要特别小心使用。
不要去预测像打开文件这类操作是否成功, 通常直接尝试再查验返回值会更好, 如果失败了再申诉。当然, 如果你要避免复写已存在的文件, 这个方法并不适用, 除非打开文件有象 O_EXCL 的参数, 那就可以做到你所要的效果。
参考资料: [PCS, Sec. 12 pp. 189,213]; [POSIX, ec. 5.3.1, Sec. 5.6.2, Sec. 5.6.3.]。
20.14 怎样在读入文件前, 知道文件大小?
如果文件大小指的是你从 C 程序中可以读进的字符数量, 要得到这个精确的数字可能困难或不可能。
Unix 系统函数 stat() 会给出准确的答案。有些系统提供了类似 Unix 的 stat() 函数, 但只返回一个近似值。你可以调用 fseek() 搜索到文件尾, 再调用 ftell(), 或者调用 fstat(), 然而这些方法都有同样的问题: fstat() 不可移植, 通常返回和 stat() 一样的值; ftell() 并不保证可以返回字符计数, 除非是用于二进制文件, 但是, 严格来讲, 二进制文件并不一定支持 fseek 搜索到 SEEK_END。某些系统提供 filesize() 或 filelength() 的函数, 但是它们明显的不可移植。
你是否确定你必须预先知道文件大小?作为一个 C 程序最准确的方法就是打开文件并读入, 也许可以重新整理代码, 使其边读入边计算文件大小。
参考资料: [ISO, Sec. 7.9.9.4]; [H&S, Sec. 15.5.1]; [PCS, Sec. 12 p. 213]; [POSIX, Sec. 5.6.2]。
20.15 怎样得到文件的修改日期和时间?
Unix 和 POSIX 函数是 stat(), 某些其它系统也提供。参见问题 19.14。
20.16 怎样缩短一个文件而不用清除或重写?
BSD 系统提供函数 ftruncate(), 某些其它系统提供 chsize(), 还有少数系统提供用于 fcntl 的参数 F_FREESP。 MS-DOS 下, 某些时候你可以用 write(fd, , 0)。然而, 没有一个可移植的方法, 也没有办法删除在文件开头的数据块。参见问题 19.17。
20.17 怎样在文件中插入或删除一行 (或记录)?
如果你不能重写文件, 你大概做不了。通常的做法就是重写文件。也许你可以用简单的标志记录删除来取代真的删除, 这样可以避免重写。另外一个可能性, 是使用数据库来取代平坦文件。参见问题 12.26 和 19.16。
20.18 怎样从一个打开的流或文件描述符得到文件名? 通常情况下, 这是没办法解决的。在 Unix 系统下, 理论上需要搜索整个磁盘 , 也许还牵扯到特殊许可的问题, 如果文件描述符是连接到管道 (pipe) 或者指向一个已被删除的文件 (如果文件有多个连接, 也许会返回误导的信息), 那搜索也会失败。最好的方法就是你在打开文件时, 自己记住 (或许用一个 fopen() 的包裹函数)。
20.19 怎样删除一个文件?
标准 C 库函数是 remove()。这是本章里少有的几个与系统无关的问题。在一些 ANSI 之前的老 Unix 系统, remove() 可能不存在, 那么你可以试试 unlink()。
参考资料 [K&R2, Sec. B1.1 p. 242; ISO Sec. 7.9.4.1]; [H&S, Sec. 15.15 p. 382]; [PCS, Sec. 12 pp. 208,220-221]; [POSIX, Sec. 5.5.1, Sec. 8.2.4]。
20.20 怎样复制一个文件? 可以用函数 system() 调用你所用操作系统的文件复制工具, 参见问题 19.30。或者打开源文件和目标文件 (用 fopen() 或一些低层的打开文件的系统函数), 读入字符或数据段, 再写出到目标文件中。
参考资料: [K&R2, Sec. 1, Sec. 7]。
20.21 为什么用了详尽的路径还不能打开文件? fopen("c:\ newdir \file.dat", "r") 返回错误。 你实际请求的文件名内含有字符 \n 和 \f, 可能并不存在, 也不是你希望的。
在字符常量和字符串中, 反斜杠 \ 是逃逸字符, 它赋予后面紧跟的字符特殊意义。为了正确的把反斜杠传递给 fopen() (或其它函数), 必须成双的使用, 这样第一个反斜杠引述了第二个:
fopen("c:\\newdir\\file.dat", "r")
另一个选择, 在 MS-DOS 下, 正斜杠也被接受为路径分隔符, 所以也可以这样用:
fopen("c:/newdir/file.dat", "r")
注意, 顺便提一下, 用于预处理 #include 指令的头文件名不是字符串文字, 所以不必担心反斜杠的问题。
20.22 fopen() 不让我打开文件: "$HOME/.profile" 和 "~/ .myrcfile"。
至少在 Unix 系统下, 像 $HOME 这样的环境变量和家目录的表示符 ~ 是由 shell 来展开的。不存在一个调用 fopen() 时的自动扩展机制。
20.23 怎样制止 MS-DOS 下令人担忧的 ``Abort, Retry, Ignore?" 信息? 除了其它的事, 你需要截取 DOS 的重要错误中断, 中断 24H。详情请参阅 comp.os.msdos.programmer 的 FAQ。
20.24 遇到 ``Too many open files (打开文件太多)" 的错误, 怎样增加同时打开文件的允许数目?
通常有至少两个资源限制了同时打开文件的数目: 操作系统可用的低层 ``文件说明符" 或 ``文件句柄" 的数目; 和标准 stdio 函数库可用的 FILE 结构数目。两个条件必须符合。在 MS-DOS 下, 可以通过设置 CONFIG.SYS, 可以控制系统文件 handle 的数目。一些编译器附有增加 stdio 的 FILE 结构数目的指令 (也许是一两个源文件)。
20.25 怎样在 C 中读入目录?
试试能否使用 opendir() 和 readdir() 函数, 它们是 POSIX 标准的一部分, 大多数 Unix 变体都支持。MS-DOS, VMS 和其它系统下也有这些函数的实现。MS-DOS 还有 FINDFIRST 和 FINDNEXT 函数, 它们做的事基本一样, MS Windows 有 FindFirstFile 和 FindNextFile。 readdir() 只返回文件名, 如果你需要该文件更多的信息, 试用 stat()。如果想匹配文件名和通配符式样, 参见问题 13.5。
参考资料: cite[Sec. 8.6 pp. 179-184]kr2; [PCS, Sec. 13 pp. 230-1]; [POSIX, Sec. 5.1]; [Schumacher, ed., Sec. 8]。
20.26 怎样找出系统还有多少内存可用?
你所用的系统可能会提供一个例程返回你所需的信息, 但是这跟系统相当有关。
20.27 怎样分配大于 64K 的数组或结构? 一台合理的电脑应该可以让你透明地访问所有的有效内存。如果, 你很不幸, 你可能需要重新考虑程序使用内存的方式, 或者用各种针对系统的技巧。
64K 仍然是一块相当大的内存。不管你的电脑有多少内存, 分配这么一大段连续的内存是个不小的要求。标准 C 不保证一个单独的对象可以大于 32K, 或者 C99 的 64K。通常, 设计数据结构时的一个好的思想, 是使它不要求所有的内存都连续。对于动态分配的多维数组, 你可以使用指针的指针, 在问题 6.13 中有举例说明。你可以用链接或结构指针数组来代替一个大的结构数组。
如果你使用的是 PC 兼容机 (基于 8086) 系统, 遇到了 64K 或 640K的限制, 可以考虑使用 ``huge" (巨大) 内存模型, 或者扩展或延伸内存, 或 malloc 的变体函数 halloc() 和 farmalloc(), 或者用一个 32 比特的 ``平直" 编译器 (例如 djgpp, 参见问题 18.3), 或某种 DOS 扩充器, 或换一个操作系统。
参考资料: [ISO, Sec. 5.2.4.1]; [C9X, Sec. 5.2.4.1]。
20.28 错误信息 ``DGROUP data allocation exceeds 64K (DGROUP 数据分配内存超过 64K)" 说明什么?我应该怎么做?我以为使用了大内存模型, 那我就可以使用多于 64K 的数据! 即使使用了大内存模型, MS-DOS 的编译器明显地把某些数据 (字符串, 已初始化的全局或静态变量) 都放在了一个缺省的数据段, 而这个数据段溢出了。要么减少全局数据, 或者已经限制在一个合理的范围 (而引起问题的是由于字符串的数目), 你可以告诉编译器对这么大的数据不要使用缺省数据段。某些编译器只把 ``小" 数据放在缺省数据段, 也提供了设置 ``小" 的临界值的方法, 例如, Microsoft 的编译器可以用参数 /Gt。
20.29 怎样访问位于某的特定地址的内存 (内存映射的设备或图显内存)? 设置一个适当类型的指针去正确的值, 使用明示的类型重制, 以保证编译器知道这个不可移植转换是你的意图:
unsigned int *magicloc = (unsigned int *)0x12345678;
那么, *magiloc 就指向你所要的地址。如果地址是个内存映射设备的寄存器, 你大概需要使用限定词 volatile。MS-DOS 下, 在和段、偏移量打交道时, 你会发现像 MK_FP 这类宏非常好用。
参考资料: [K&R1, Sec. A14.4 p. 210]; [K&R2, Sec. A6.6 p. 199]; [ISO, Sec. 6.3.4;]; [Rationale, Sec. 3.3.4]; [H&S, Sec. 6.2.7 pp. 171-2]。
20.30 怎样在一个 C 程序中调用另一个程序 (独立可执行的程序, 或系统命令)? 使用库函数 system(), 它的功能正是你所要的。注意, 系统返回的值最多是命令的退出状态值 (但这并不是一定的), 通常和命令的输出无关。还要注意, system() 只接受一个单独的字符串参数来表述调用程序。如果你要建立复杂的命令行, 可以使用 sprintf()。
跟据你使用的系统, 也许你还可以使用系统函数, 例如 exec 或 spawn (或 execl, execv, spawnl, spawnv 等)。
参见问题 19.31。
参考资料: [K&R1, Sec. 7.9 p. 157]; [K&R2, Sec. 7.8.4 p. 167, Sec. B6 p. 253]; [ISO, Sec. 7.10.4.5]; [H&S, Sec. 19.2 p. 407]; [PCS, Sec. 11 p. 179]。
--------------------------------------------------------------------------------
20.31 怎样调用另一个程序或命令, 同时收集它的输出? Unix 和其它一些系统提供了 popen() 函数, 它在联通运行命令的进程管道设置了 stdio 流, 所以输出可以被读取 (或提供输入)。记住, 结束使用后, 要调用函数 pclose()。
如果你不能使用 popen(), 你应该可以调用 system(), 并输出到一个你可以打开读取的文件。
如果你使用 Unix, 觉得 popen() 不够用, 你可以学习用 pipe(), dup(), fork() 和 exec()。
顺便提一下, freopen() 可能并不工作。
参考资料: [PCS, Sec. 11 p. 169]。
--------------------------------------------------------------------------------
20.32 怎样才能发现程序自己的执行文件的全路径? arg[0] 也许含有全部或部分路径, 或者什么也没有。如果 arg[0] 中的路径不全, 你也许可以重复命令语言注释器的路径搜索逻辑。但是, 没有保证的解决方法。
参考资料: [K&R1, Sec. 5.11 p. 111]; [K&R2, Sec. 5.10 p. 115]; [ISO, Sec. 5.1.2.2.1]; [H&S, Sec. 20.1 p. 416.]。
--------------------------------------------------------------------------------
20.33 怎样找出和执行文件在同一目录的配置文件? 这非常困难。参见问题 19.32。即使你可以找出一个可行的方法, 你可能会考虑通过环境变量或别的方法使程序的辅助 (函数库) 目录可配置。如果程序会被数个人使用, 例如在多用户系统中, 那允许配置文件的变量存放变得特别重要。
--------------------------------------------------------------------------------
20.34 一个进程如何改变它的调用者的环境变量? 这有可能完全做不到。不同的系统使用不同的方法来实现像 Unix 系统的全局名字/值功能。环境是否可以被运行的进程有效的改变, 以及如果可以, 又怎样去做, 这些都依赖于系统。 在 Unix 下, 一个进程可以改变自己的环境 (某些系统为此提供了 setenv() 或 putenv() 函数), 被改变的环境通常会被传给子进程, 但是这些改变不会传递到父进程。在 MS-DOS 下, 总环境是可以操作的, 但是这需要晦涩难解的技巧。参见 MS-DOS 的 FAQ。
--------------------------------------------------------------------------------
20.35 怎样读入一个对象文件并跳跃到其中的地址? 你需要一个动态的连接程序或载入程序。也许可以 malloc 一段内存, 再读入对象文件, 但是你需要知道极多的有关对象文件格式、地址变换等知识。在 BSD Unix 下, 你可以使用 system() 和 ld -A 来实现连接。许多 SunOS 和 System V 的版本有 -ldl 函数库, 允许动态载入对象文件。在 VMS 下, 使用 LIB$FIND_IMAGE_SYMBOL。 GNU 有个 dld 的包可以用。参见问题 15.12。
--------------------------------------------------------------------------------
20.36 怎样实现精度小于秒的延时或记录用户回应的时间? 很不幸, 这没有可移植解决方法。下面是一些你可以在你的系统中寻找的函数: clock(), delay(), ftime(), getimeofday(), msleep(), nap(), napms(), nanaosleep(), setitimer(), sleep(), Sleep(), times() 和 usleep。至少在 Unix 系统下, 函数 wait() 不是你想要的。函数 select() 和 poll() (如果存在) 可以用来实现简单的延时。在 MS-DOS 下, 可以重新对系统计时器和计时器中断编程。 这些函数中, 只有 clock() 在 ANSI 标准中。两次调用 clock() 之间的差分就是执行所用的时间, 如果 CLOCKS_PER_SEC 的值大于 1, 你可以得到精确度小于秒的计时。但是, clock() 返回的是执行程序使用的处理器的时间, 在多任务系统下, 有可能和真实的时间相差很多。
如果你需要实现一个延时, 而你只有报告时间的函数可用, 你可以实现一个繁忙等待。但是这只是在单用户, 单任务系统下可选, 因为这个方法对于其它进程极不友好。在多任务系统下, 确保你调用函数, 让你的进程在这段时间进入休眠状态。可用函数 sleep(), select() 或 poll() 和 alarm() 或 setitimer()实现。
对于非常短暂的延时, 使用一个空循环颇据有诱惑力:
long int i; for (i = 0; i < 1000000; ++i) ;
但是请尽量抵制这个诱惑!因为, 经过你仔细计算的延时循环可能在下个月因为更快的处理器出现而不能正常工作。更糟糕的是, 一个聪明的编译器可能注意到这个循环什么也没做, 而把它完全优化掉。 参考资料: [H&S, Sec. 18.1 pp. 398-9]; [PCS, Sec. 12 pp. 197-8,215-6]; [POSIX, Sec. 4.5.2]。
--------------------------------------------------------------------------------
20.37 怎样抓获或忽略像 control-C 这样的键盘中断? 基本步骤是调用 signal(): #include <signal.h> singal(SIGINT, SIG_IGN);
就可以忽略中断信号, 或者: extern void func(int); signal(SIGINT, func);
使程序在收到中断信号时, 调用函数 func()。 在多任务系统下 (例如 Unix), 最好使用更加深入的技巧:
extern void func(int); if(signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, func);
这个测试和额外的调用可以保证前台的键盘中断不会因疏忽而中断了在后台运行的进程, 在所有的系统中都用这种调用 signal 的方法并不会带来负作用。 在某些系统中, 键盘中断处理也是终端输入系统模式的功能, 参见问题 19.1。在某些系统中, 程序只有在读入输入时, 才查看键盘中断, 因此键盘中断处理就依赖于调用的输入例程 (以及输入例程是否有效)。在 MS-DOS 下, 可以使用 setcbrk() 或 ctrlbrk()。
参考资料: [ISO, Secs. 7.7,7.7.1]; [H&S, Sec. 19.6 pp. 411-3]; [PCS, Sec. 12 pp. 210-2]; [POSIX, Secs. 3.3.1,3.3.4]。
--------------------------------------------------------------------------------
20.38 怎样很好地处理浮点异常? 在许多系统中, 你可以定义一个 matherr() 的函数, 当出现某些浮点错误时 (例如 <math> 中的数学例程), 它就会被调用。你也可以使用 signal() 函数 (参见问题 19.37) 截取 SIGFPE 信号。参见问题 14.9。 参考资料: [Rationale, Sec. 4.5.1]。
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
20.40 怎样调用 BIOS 函数?写 ISR?创建 TSR? 这些都是针对某一特定系统的问题 (最可能的是运行 MS-DOS 的 PC 兼容机)。在针对系统的新闻组 comp.os.msdos.programmer 或它的 FAQ 里你会取得更好的信息; 另一个很好的资源是 Ralf Brown 的中断列表。
-------------------------------------------------------------------------------- 20.41 编译程序, 编译器出示 ``union REGS" 未定义错误信息, 连接器出示 ``int86()" 的未定义错误信息。 这些都跟 MS-DOS 的中断编程有关。它们在其它系统中不存在。
-------------------------------------------------------------------------------- 20.42 什么是 ``near" 和 ``far" 指针? 如今, 它们差不多被废弃了; 它们肯定于特定系统有关。如果你真的要知道, 参阅针对 DOS 或 Windows 的编程参考资料。
-------------------------------------------------------------------------------- 20.43 我不能使用这些非标准、依赖系统的函数, 程序需要兼容 ANSI! 你很不走运。要么你误解了要求, 要么这不可能做到。 ANSI/ISO C 标准没有定义做这些事的方法; 它是个语言的标准, 不是操作系统的标准。国际标准 POSIX (IEEE 1003.1, ISO/IEC 9945-1) 倒是定义了许多这方面的方法, 而许多系统 (不只是 Unix) 都有兼容 POSIX 的编程接口。 可以做到, 也是可取的做法是使程序的大部分兼容 ANSI, 将依赖系统的功能集中到少数的例程和文件中。这些例程或文件可以大量使用 #ifdef 或针对每一个移植的系统重写。
--------------------------------------------------------------------------------
|
|