13.6 条件编译
C语言的预处理器可以识别出允许用户选择要编译和要省略的部分程序的命令。这种功能对于许多情况都会有帮助。例如,当编写一个函数时可以加入调试的printf调用,然后只在需要时将这些语句包含在已经编译过的程序中。包含头文件也是另一个需要有条件执行的行为。例如,假设有sp_one和sp_two两个库,它们都使用了第三个库sp的数据类型和运算符。头文件sp_one.h和sp_two.h中都应该包含指令#include"sp.h"。然而,如果想要一个程序使用sp_one和sp_two两个库的功能,就应该包含它们两个的头文件,这样就会导致包含sp.h多次,从而使sp.h中定义的数据类型重复声明。因为C语言禁止这样的重复声明,所以必须防止这种情况的发生。条件编译非常有用的第三种情况是设计用于不同计算机的系统。条件编译允许编译仅适合当前计算机的代码。
图13-9给出了一个递归函数,其中包含了产生跟踪执行的printf调用。这些printf语句的编译依赖于条件值
defined (TRACE)
如果该名称(也就是其中的操作数)已在预处理中定义,那么defined运算符的值为1。这里的定义是在#define指令中或在模拟#define的编译器选项中使用该名称的结果。否则defined运算符的值为0。
在产生了类似图13-9的函数后,只需要在源文件中的函数定义之前包含指令
#define TRACE
就可以“打开”跟踪printf调用的编译。没有必要用TRACE关联一个确切的值。对于所有的预处理指令,要记住条件编译指令的#必须是该行第一个非空字符。defined运算符只在应用于#if和#elif指令时存在。#elif表示else if,在判断多项选择时使用,如图13-10所示。
|
711. /* 712. * Computes an integer quotient (m/n) using subtraction 713. */ |
图13-9 跟踪printf调用的条件编译
|
714. int 715. quotient(int m, int n) 716. { 717. int ans; 718. #if defined (TRACE) 719. printf("Entering quotient with m = %d, n = %d\n", m, n); 720. #endif 721. 722. if (n > m) 723. ans = 0; 724. else 725. ans = 1 + quotient(m - n, n); 726. 727. #if defined (TRACE) 728. printf("Leaving quotient(%d, %d) with result = %d\n", m, n, ans); 729. #endif 730. 731. return (ans); 732. } |
图13-9(续)
|
733. /* 734. * Computes an integer quotient (m/n) using subtraction 735. */ 736. int 737. quotient(int m, int n) 738. { 739. int ans; 740. 741. #if defined (TRACE_VERBOSE) 742. printf("Entering quotient with m = %d, n = %d\n", m, n); 743. #elif defined (TRACE_BRIEF) 744. printf(" => quotient(%d, %d)\n", m, n); 745. #endif 746. 747. if (n > m) 748. ans = 0; 749. else 750. ans = 1 + quotient(m - n, n); 751. 752. #if defined (TRACE_VERBOSE) 753. printf("Leaving quotient(%d, %d) with result = %d\n", m, n, ans); 754. #elif defined (TRACE_BRIEF) 755. printf("quotient(%d, %d) => %d\n", m, n, ans); 756. #endif 757. 758. return (ans); 759. } |
图13-10 跟踪printf调用的条件编译
多个包含文件的一个协调方法在图13-11中给出。每个头文件在构建时就应该避免内容的重复编译,而不管该头文件被包含多少次。头文件的整个内容都包含在一个#if中,这用于测试基于该头文件名的名称是否已经在#define指令中定义了。然后在首次包含该头文件时,它的整个内容就会传递给编译器。由于重要名称的#define在该文件中,所以同一文件的其余#include指令将不为编译器提供代码。
|
760. /* Header file planet.h 761. * 762. * abstract data type planet 763. * 764. * Type planet_t has these components: 765. * name, diameter, moons, orbit_time, rotation_time 766. * 767. * Operators: 768. * print_planet, planet_equal, scan_planet 769. */ 770. 771. #if !defined (PLANET_H_INCL) 772. #define PLANET_H_INCL 773. 774. #define PLANET_STRSIZ 10 775. 776. typedef struct { /* planet structure */ 777. char name[PLANET_STRSIZ]; 778. double diameter; /* equatorial diameter in km */ 779. int moons; /* number of moons */ 780. double orbit_time , /* years to orbit sun once */ 781. rotation_time; /* hours to complete one revolution on axis */ 782. } planet_t; 783. 784. /* 785. * Displays with labels all components of a planet_t structure 786. */ 787. extern void 788. print_planet(planet_t pl); /* input - one planet structure */ 789. 790. /* 791. * Determines whether or not the components of planet_1 and planet_2 792. * match 793. */ 794. extern int 795. planet_equal(planet_t planet_1, /* input - planets to */ 796. planet_t planet_2); /* compare */ 797. 798. /* 799. * Fills a type planet_t structure with input data. Integer returned as 800. * function result is success/failure/EOF indicator. 801. * 1 => successful input of planet 802. * 0 => error encountered 803. * EOF => insufficient data before end of file 804. 805. * In case of error or EOF, value of type planet_t output argument is 806. * undefined. 807. 808. */ 809. extern int 810. scan_planet(planet_t *plnp); /* output- address of planet_t structure to 811. fill */ 812. 813. #endif |
图13-11 防止自身多重包含的头文件
C语言的#if和#elif指令可以由#else指令进行补充,从而使可选编译结构的整个范围都成为可能。还有一个#undef指令,它可以取消一个特定名称的预处理定义。
练习
自测
1. 使用条件编译选择一个合适的printf调用。假定在一个UNIX操作系统上,名称UNIX会在C预处理器中定义;在VMS操作系统上,会定义名称VMS。在UNIX上的期望消息是
Enter <ctrl-d> to quit.
在VMS上的期望信息是
Enter <ctrl-z> to quit.
2. 查看图13-11给出的头文件。描述在下列情况下会发生什么事情?(a)当预处理器首次遇到#include"planet.h"指令时;(b)当预处理器第二次遇到#include“planet.h”指令时。







