什么时候在C中发生宏替换

时间:2014-05-20 06:04:02

标签: c compiler-construction macros c-preprocessor

我正在阅读这本书" 编译器:原理,技术和工具(第2版)"作者:Alfred V. Aho。本书(示例1.7)中有一个示例,要求在 C 中分析以下宏定义中x的范围:

#define a (x+1)

从这个例子中,

  

我们无法静态解析x,即根据程序文本。

     

实际上,为了解释x,我们必须使用通常的动态范围   规则。我们检查当前活动的所有函数调用,以及   我们采用最近调用的函数,它具有x的声明。   对于此声明,使用x表示。

我读这个很困惑 - 据我所知,在编译开始之前,宏替换发生在预处理阶段。但是,如果我做对了,那本书就说它会在程序执行时发生。有人可以澄清一下吗?

3 个答案:

答案 0 :(得分:2)

宏本身没有范围概念,至少与C语言没有相同的意义。在a之后(在可能的#define之前)符号#undef出现在源中的任何地方,它都会被(x + 1)替换。

但是文本讨论了x的范围,宏观替代中的符号。这是由通常的C规则解释的。如果替换x的范围中没有符号a,则这是编译错误。

宏不是独立的。它使用宏外部的符号,某种全局变量(如果愿意),但其含义将根据调用宏的源文本中的位置而改变。我认为引用的文字想要说的是我们无法知道宏a做什么,除非我们知道它被引发的位置。

答案 1 :(得分:1)

  

我读这个很困惑 - 据我所知,在编译开始之前,宏替换发生在预处理阶段。

是的,这就是编译器的工作方式。

  

但是,如果我做对了,那本书说这是在程序执行时发生的。有人可以澄清一下吗?

在不参考本书的情况下,除了将源代码翻译成目标代码(a.k.a.编译)之外,还有其他形式的程序分析。 C编译器在编译之前替换宏,从而丢失有关原始宏的信息,因为该信息对于翻译过程的其余部分并不重要。宏中x范围的问题永远不会出现,因此编译器可能会忽略该问题。

但是,调试器通常会实现与源代码的更紧密集成。可以想象一个调试器在指向子程序时指向子程序(我已经在嵌入式工具链中看到了这个特性),并且还指出了生成表达式的宏(这是我从未见过的,但它可以想象) 。或者,某些调试器允许您指向任何标识符并查看其值。然后,指向宏定义将需要解析宏中使用的标识符,如Aho等人在那里讨论的那样。

答案 2 :(得分:0)

如果没有从书中看到更多的背景,很难确定,但我认为这段话至少不清楚,而且可能不正确。关于宏定义如何工作基本上是正确的,而不是如何解析名称x

#define a (x+1)

C编译过程中,在编译过程的早期,在N1570 5.1.1.2中指定的翻译阶段4中扩展了C宏。直到阶段7)才能解析变量名称。

因此名称x对于编译器来说将是有意义的,而不是在定义宏的位置,而是在源代码中使用宏a的位置。 a宏的两种不同用法可以引用名为x的变量的两种不同声明。

  

我们无法静态解析x,即根据程序文本。

我们无法在宏定义时解决它。

  

实际上,为了解释x,我们必须使用通常的动态范围   规则。我们检查当前活动的所有函数调用,以及   我们采用最近调用的函数,它具有x的声明。   对于此声明,使用x表示。

对于C来说这是不正确的。当编译器看到对x的引用时,它必须确定它引用的声明(或者如果没有这样的声明则发出诊断)。该确定取决于当前活动的函数调用,这只能在运行时确定。 C是静态范围,这意味着x的适当声明可以通过检查程序文本来完全确定。

在编译时,编译器将检查当前块的符号表条目,然后检查封闭块,然后是当前函数(x可能是参数的名称),然后是文件范围。

有些语言使用动态作用域,其中名称引用的声明取决于当前的运行时调用堆栈。 C不是其中之一。

以下是Perl中动态范围的示例(请注意,这被认为是风格不佳):

#!/usr/bin/perl

use strict;
use warnings;

no strict "vars";

sub inner {
    print "    name=\"$name\"\n";
}

sub outer1 {
    local($name) = "outer1";
    print "outer1 calling inner\n";
    inner();
}

sub outer2 {
    local($name) = "outer2";
    print "outer2 calling inner\n";
    inner();
}

outer1();
outer2();

输出结果为:

outer1 calling inner
    name="outer1"
outer2 calling inner
    name="outer2"

C中的类似程序无效,因为name的声明在函数inner中不会静态