C 预处理器插入的空格

2024-03-15

假设我们得到以下输入 C 代码:

#define Y 20
#define A(x) (10+x+Y)

A(A(40))

gcc -E像这样的输出(10+(10+40 +20)+20).

gcc -E -traditional-cpp像这样的输出(10+(10+40+20)+20).

为什么默认cpp后面要插入空格40 ?

在哪里可以找到涵盖该逻辑的 cpp 的最详细规范?


C 标准没有指定这种行为,因为预处理阶段的输出只是一个标记和空格流。将令牌流序列化回字符串,这就是gcc -E标准没有要求,甚至没有提及,并且不构成标准指定的翻译过程的一部分。

在第 3 阶段,程序“被分解为预处理标记和空白字符序列”。除了忽略空格的连接运算符和保留空格的字符串化运算符的结果之外,标记也被固定,并且不再需要空格来分隔它们。但是,需要空格才能:

  • 解析预处理器指令
  • 正确处理字符串化运算符

流中的空白元素直到第 7 阶段才会被消除,尽管它们在第 4 阶段结束后不再相关。

Gcc 能够生成对程序员有用的各种信息,但与标准中的任何内容都不对应。例如,翻译的预处理器阶段还可以使用以下之一生成可用于插入 Makefile 的依赖信息:-M选项。或者,可以使用以下命令输出编译代码的人类可读版本-S选项。并且可以使用以下命令输出预处理程序的可编译版本,大致对应于第 4 阶段生成的令牌流-E选项。这些输出格式都不受 C 标准控制,C 标准只关心实际执行程序。

为了生产-E输出时,gcc 必须以不改变程序语义的格式序列化标记和空格流。在某些情况下,如果流中的两个连续标记没有彼此分开,它们将被错误地粘合在一起成为单个标记,因此 gcc 必须采取一些预防措施。它实际上无法将空格插入正在处理的流中,但是当它响应于呈现流时,没有什么可以阻止它添加空格gcc -E.

例如,如果您的示例中的宏调用被修改为

A(A(0x40E))

那么令牌流的天真输出将导致

(10+(10+0x40E+20)+20)

无法编译,因为0x40E+20是单个 pp-number 标记,无法转换为数字标记。之前的空间+防止这种情况发生。

如果您尝试将预处理器实现为某种字符串转换,那么毫无疑问您将在极端情况下遇到严重的问题。正确的实现策略是首先按照标准中的指示进行标记化,然后将第 4 阶段作为标记和空白流上的函数执行。

字符串化是一个特别有趣的情况,其中空格影响语义,它可以用来查看实际的令牌流是什么样子。如果将以下扩展字符串化A(A(40)),您可以看到实际上没有插入空格:

$ gcc -E -x c - <<<'
#define Y 20
#define A(x) (10+x+Y)
#define Q_(x) #x
#define Q(x) Q_(x)         
Q(A(A(40)))'

"(10+(10+40+20)+20)"

字符串化中空格的处理由标准精确指定:(§6.10.3.2,第 2 段,非常感谢 John Bollinger 找到了规范。)

参数的预处理标记之间每次出现的空格 成为字符串文字中的单个空格字符。构成参数的第一个预处理标记之前和最后一个预处理标记之后的空白将被删除。

这是一个更微妙的示例,其中需要额外的空格gcc -E输出,但实际上并未插入到令牌流中(再次通过使用字符串化来生成真正的令牌流来显示。)I(identify) 宏用于允许将两个令牌插入到令牌流中,而无需插入空格;如果你想使用宏来组成参数,这是一个有用的技巧#include指令(不推荐,但可以做到)。

也许这对于您的预处理器来说可能是一个有用的测试用例:

#define Q_(x) #x
#define Q(x) Q_(x)
#define I(x) x
#define C(x,...) x(__VA_ARGS__)
// Uncomment the following line to run the program
//#include <stdio.h>

char*quoted=Q(C(I(int)I(main),void){I(return)I(C(puts,quoted));});
C(I(int)I(main),void){I(return)I(C(puts,quoted));}

这是 gcc -E 的输出(只是最后的好东西):

$ gcc -E squish.c | tail -n2
char*quoted="intmain(void){returnputs(quoted);}";
int main(void){return puts(quoted);}

在从阶段 4 传出的令牌流中,令牌int and main不被空格分隔(也不是return and puts)。字符串化清楚地表明了这一点,其中没有空格分隔标记。但是,即使显式传递,程序也可以正常编译和执行gcc -E:

$ gcc -E squish.c | gcc -x c - && ./a.out 
intmain(void){returnputs(quoted);}

并编译输出gcc -E.


不同的编译器和同一编译器的不同版本可能会产生预处理程序的不同序列化。所以我认为你不会找到任何可以通过逐个字符比较来测试的算法-E给定编译器的输出。

最简单的序列化算法是无条件输出两个连续标记之间的空格。显然,这会输出不必要的空格,但它永远不会在语法上改变程序。

我认为最小空间算法是在令牌中最后一个字符的末尾记录 DFA 状态,以便以后如果存在从第一个令牌末尾的状态开始的转换,则可以在两个连续令牌之间输出空格在以下标记的第一个字符上。 (将 DFA 状态保留为令牌的一部分与将令牌类型保留为令牌的一部分并没有本质上的不同,因为您可以从 DFA 状态的简单查找中派生出令牌类型。)该算法不会在后面插入空格40在你原来的测试用例中,但它会在后面插入一个空格0x40E。所以它不是您的 gcc 版本使用的算法。

如果使用上述算法,则需要重新扫描由令牌串联创建的令牌。但是,无论如何这是必要的,因为如果串联结果不是有效的预处理标记,则需要标记错误。

如果您不想记录状态(尽管,正如我所说,这样做基本上没有任何成本)并且您不想通过在输出令牌时重新扫描令牌来重新生成状态(这也非常便宜) ),您可以预先计算一个由标记类型和后续字符键入的二维布尔数组。计算本质上与上面相同:对于返回特定令牌类型的每个接受 DFA 状态,在数组中输入该令牌类型的真值以及任何从 DFA 状态转换的字符。然后,您可以查找令牌的令牌类型和后续令牌的第一个字符,以查看是否需要空格。该算法不会产生最小间距的输出:例如,它会在40在你的例子中,因为40 is a pp-number对于某些人来说这是可能的pp-number可以扩展为+(即使你不能延长40以这种方式)。所以 gcc 可能使用该算法的某个版本。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C 预处理器插入的空格 的相关文章

随机推荐

  • WPF 切角元素

    我正在尝试在 WPF 中创建类似于下图的内容 该控件被设计为我的应用程序中所有内容的基本视图 并将位于带有背景 可能是某种渐变 的 Window 控件内 要求如下 三边圆角 左上 左下 右下 剪掉右上角的选项卡查看角 剪切区域 后面的背景透
  • 将可变上下文传递到回调中

    我正在尝试用 Rust 构建一个简单的 UI 但部分可以在 Lua 中编写脚本 使用 rust lua53 并且在找到一种让 Lua 组件访问 Lua 状态的好方法时遇到问题 这个问题 示例比我想要的要长一点 抱歉 UI 的核心是Widge
  • Direct2D 仅在 C++ Builder 中部分链接

    我有一个 C Builder Rad Studio Berlin 项目设置来使用 Direct2d 画布绘图与 TDirect2DCanvas 配合得很好 这表明 Direct2D 链接正确 一切都渲染得很顺利 但是 我需要使用矩阵 当我尝
  • gitlab-ctl 重新配置:无法确定节点名称

    我确实在 Ubuntu 16 04 4 LTS 上设置了一个新的 GitLab 实例 安装包进展顺利 GitLab 似乎已启动并运行 然后我开始配置实例并设置 SMTP etc gitlab gitlab rb 后来我跑了sudo gitl
  • 从多个 jpg 图像创建 Dicom

    我已经成功地用一张图像构建了 dicom 但我找不到添加更多图像的方法 我认为问题可能出在我的像素阵列中 任何人都可以帮我纠正它吗 Populate required values for file meta information met
  • 如何动态设置谷歌地图自定义缩放级别?

    我正在使用谷歌地图 根据要求 我需要设置取决于我的搜索查询的不同缩放级别 如果国家 地区地图上有多个位置 则地图应聚焦该国家 地区 其他情况是 如果城市中标记了不同的位置 则地图应集中到城市级别 var geoCoder new GClie
  • “#!/bin/env”是什么意思(位于 node.js 脚本的顶部)?

    我发现一些 Node js 项目的顶部有这个app js 如在这个开放轮班计划 https github com openshift openshift mongo node express example blob master serv
  • WinDBG - 查找实际的(非托管)异常

    我试图在托管 非托管混合代码中找到实际的异常 问题是我有一个 Net 类 它捕获所有未处理的异常 然后创建一个转储 因此当我查看转储时 存在混合的托管 非托管代码 并且我无法真正获取实际的非托管异常 更糟糕的是 Net 似乎有自己的例外 所
  • 如何在 ACSL 中编写“is power of 2”谓词?

    我尝试编写一个 ACSL 谓词来查看整数是否是 2 的幂 如下所示 predicate positive power of 2 integer i i gt 0 i 1 i 1 0 positive power of 2 i gt gt 1
  • OpenFileIDialog C# 中的 URL 作为文件名

    在我的 C win 表单中 我使用OpenFileDialog供用户选择要保存的文件 当用户指定一个 url 文件时 例如http www xyz com qdms MyFile PDFOpenFileDialog 下载文件并给出下载的文件
  • OPENCV 链接错误 - Win32 和 VS2012

    我已经构建了 openCV 3 0 0 alpha 和 beta 版本 但每次我运行我的项目时 我都会收到此错误 仅适用于 imread 功能 error LNK2019 unresolved external symbol class c
  • 如何设置Redis最大内存?

    我发现配置在this http redis io topics config 它只是说使用指定配置的命令 redis server
  • 如何从受密码保护的站点更新 Eclipse 插件?

    我在 这个网站 http javaforge com project HGE http javaforge com project HGE 我需要 注册 才能下载 Mercurial 的 Eclipse 插件 我注册了 但似乎什么也没发生
  • Wix:如何警告用户而不是使用属性终止安装?

    我正在 x64 计算机上搜索 Microsoft Access 数据库引擎的注册表项 这是我的代码
  • 将请求的文件从 API 传输到 API:NestJS(HttpService: Axios) 到 Python(flask)

    我正在尝试将文件从 NestJS API 传输到 Python Flask API 此过程将由 Nest API 上的 POST 请求 FormData 文件 触发 然后 Nest api 应该将文件发送到 Python api NestJ
  • MongoEngine - 通过 id 从 ListField 中提取引用

    我想删除一些引用ListField ReferenceField 仅基于其价值 我将有关图像的信息存储在以下模型中 class ImageUrl Document src UrlField counter IntField deleted
  • torchvision和tensorflow-gpu导入错误

    运行这个 import torchvision import tensorflow 产生错误 SystemError google protobuf pyext descriptor cc 354 内部函数的参数错误 但是 交换导入的顺序不
  • Tomcat不返回图片资源

    我正在使用 eclipse 并使用 eclipse 中的 tomcat6 运行我的 jsp servlet 我的 servlet 为我创建了一个图像 并将其存储在我的 webapps 的目录中 但是 当我尝试从 JSP 访问此图像时 它返回
  • React中输入的屏蔽卡号

    我正在学习 React 并希望输入有两个约束 16个数字 每四个后面加一个空格 import React Component from react export default class CardNumberInput extends C
  • C 预处理器插入的空格

    假设我们得到以下输入 C 代码 define Y 20 define A x 10 x Y A A 40 gcc E像这样的输出 10 10 40 20 20 gcc E traditional cpp像这样的输出 10 10 40 20