You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

为何C语言辅助文件无法继承主文件的头文件与宏定义?

拆分K&R《C程序设计语言》逆波兰计算器到多文件的编译问题

我正在研读Kernighan和Ritchie所著的《C程序设计语言》一书,书中76-79页是逆波兰计算器的代码。我尝试将代码拆分到多个文件:主逻辑放在calculator.c,栈操作放在stack.c,获取操作数放在getop.c,输入处理放在getch.c,代码如下。使用GCC命令gcc calculator.c stack.c getop.c getch.c -o calculator编译时出现大量错误,有以下疑问:

  1. calculator.c中已包含#include <stdio.h>,为何其他文件仍需单独包含,它们不能继承吗?
  2. calculator.c中定义了#define NUMBER '0',为何getop.c中提示‘NUMBER未声明’,宏定义也无法继承吗?
  3. 是否有GCC命令行参数可让其他文件复用calculator.c中的头文件与宏定义?还是必须在每个文件中重复包含和定义?专业C开发者通常怎么做?

代码清单

calculator.c

#include <stdio.h>
#include <math.h> // for atof()
#define MAXOP 100 /* max size of operand or operator */
#define NUMBER '0' /* signal that a number was found */
int getop(char []);
void push(double);
double pop(void);
/* reverse Polish calculator */
int main() {
    int type;
    double op2;
    char s[MAXOP];
    while ((type = getop(s)) != EOF) {
        switch (type) {
            case NUMBER:
                push(atof(s));
                break;
            case '+':
                push(pop() + pop());
                break;
            case '-':
                op2 = pop();
                push(pop() - op2);
                break;
            case '/':
                op2 = pop();
                if (op2 != 0.0)
                    push(pop() / op2);
                else
                    printf("error: zero divisor\n");
                break;
            case '\n':
                printf("\t%.8g\n", pop());
                break;
            default:
                printf("error: unknown command %s\n", s);
                break;
        }
    }
    return 0;
}

stack.c

#define MAXVAL 100 /* maximum depth of the val stack */
int sp = 0; /* next free stack position */
double val[MAXVAL]; /* value stack */
/* push: push f onto value stack */
void push (double f) {
    if (sp < MAXVAL)
        val[sp++] = f;
    else
        printf("error: stack full, can't push %g\n", f);
}
/* pop: pop and return top value from stack */
double pop(void) {
    if (sp > 0)
        return val[--sp];
    else {
        printf("error: stack empty\n");
        return 0.0;
    }
}

getop.c

#include <ctype.h>
int getch(void);
void ungetch(int);
/* getop: get next operator or numeric operand */
int getop(char s[]) {
    int i, c;
    while ((s[0] = c = getch()) == ' ' || c == '\t')
        ;
    s[1] = '\0';
    if (!isdigit(c) && c != '.')
        return c; /* not a number */
    i = 0;
    if (isdigit(c)) /* collect integer part */
        while (isdigit(s[++i] = c = getch()))
            ;
    if (c == '.') /* collect fraction part */
        while (isdigit(s[++i] = c = getchar()))
            ;
    s[i] = '\0';
    if (c != EOF)
        ungetch(c);
    return NUMBER;
}

getch.c

#define BUFSIZE 100
char buf[BUFSIZE]; /* buffer for ungetch */
int bufp = 0; /* next free position in buf */
int getch(void) /* get a (possibly pushed back) character */
{
    return (bufp > 0 ? buf[--bufp] : getchar());
}
void ungetch(int c) /* push characters back on input */
{
    if (bufp >= BUFSIZE)
        printf("ungetch: too many characters\n");
    else
        buf[bufp++] = c;
}

问题解答

1. 为什么头文件不能跨文件继承?

这是C语言编译模型的核心特点——每个.c文件都是独立的编译单元。编译器会逐个处理每个源文件,把它们分别编译成.o目标文件,最后才通过链接器把这些目标文件拼在一起。

所以calculator.c里的#include <stdio.h>只对它自己生效,其他文件在编译时根本不知道这个包含操作,自然没法“继承”。比如stack.c里用了printf,但没包含stdio.h,编译器就找不到printf的函数声明,肯定报错。每个需要用到标准库函数(比如printfgetchar)的源文件,都得自己包含对应的头文件。

2. 宏定义为什么也无法跨文件复用?

宏定义和头文件一样,都是编译单元级别的。预处理器只会处理当前正在编译的.c文件里的内容,不会去读取其他.c文件的定义。calculator.c里的#define NUMBER '0'只在编译calculator.c时有效,getop.c作为另一个独立的编译单元,编译时完全看不到这个宏,所以会提示NUMBER未声明。

3. GCC参数能解决吗?专业开发者的做法是什么?

没有任何GCC参数能让一个编译单元直接复用另一个编译单元里的头文件或宏定义——这不符合C语言的编译模型。专业C开发者的标准做法是创建一个公共头文件,把所有需要在多个文件中共享的宏定义、函数声明、类型定义都放在这个头文件里,然后在每个需要的.c文件中包含这个头文件。

具体解决方案:

  1. 创建一个calc.h头文件,用头文件保护避免重复包含:
#ifndef CALC_H
#define CALC_H

#include <stdio.h>
#include <ctype.h>
#include <math.h>

/* 共享的宏定义 */
#define MAXOP 100
#define NUMBER '0'
#define MAXVAL 100
#define BUFSIZE 100

/* 跨文件调用的函数声明 */
int getop(char []);
void push(double);
double pop(void);
int getch(void);
void ungetch(int);

#endif
  1. 修改各个.c文件,替换为包含公共头文件:

    • calculator.c:去掉重复的#include和宏定义,只保留#include "calc.h"
    • stack.c:添加#include "calc.h",去掉重复的#define MAXVAL
    • getop.c:去掉#include <ctype.h>,添加#include "calc.h",同时把小数部分的getchar()改成getch()(修复原代码的小bug)
    • getch.c:添加#include "calc.h",去掉重复的#define BUFSIZE
  2. 编译命令不变:gcc calculator.c stack.c getop.c getch.c -o calculator,现在就能正常编译运行了。


内容的提问来源于stack exchange,提问作者Roger Costello

火山引擎 最新活动