为什么要重写预处理器?
最近,我们在上发表了一篇博文 C++一致性完成。 正如在博客文章中提到的,MSVC中的预处理器目前正在进行大修。我们这样做是为了提高它的语言一致性,解决一些长期存在的由于设计原因而难以修复的错误,并提高它的可用性和诊断能力。除此之外,标准中还有一些地方未定义或未指定预处理器行为,我们的传统行为与其他主要编译器不同。在某些情况下,我们希望向生态系统靠拢,使跨平台库更容易进入MSVC。
如果您使用或维护的旧库依赖于MSVC预处理器的不一致的传统行为,那么您不必担心这些破坏性的更改,因为我们仍然支持这种行为。更新的预处理器当前处于开关/exp下erimental:preprocessor until 它已完全实现并准备好生产,此时它将移动到a/Zc:switch,默认情况下在/permission-mode下启用。
如何使用它?
传统预处理器的行为将得到维护,并继续作为编译器的默认行为。通过使用/exp可以启用一致的预处理器erimental:preprocessor switch 在命令行上,从 Visual Studio 2017 15.8预览版3发行版 .
我们在编译器中引入了一个新的预定义宏,名为“_ MSVCU传统 表示正在使用传统的预处理器。此宏是无条件设置的,与调用哪个预处理器无关。它的值对于传统的预处理器是“1”,对于一致的实验预处理器是“0”。
#如果 定义 (u MSVCu TRADITIONAL)和&u MSVCu TRADITIONAL //使用传统预处理器的逻辑 #其他 //使用跨平台兼容预处理器的逻辑 #结束
行为改变是什么?
第一个实验版本的重点是实现一致的宏扩展,以最大限度地利用MSVC编译器的新库。下面列出了在用实际项目测试更新的预处理器时遇到的一些更常见的破坏性更改。
行为1[宏注释]
传统的预处理器是基于字符缓冲区而不是预处理器令牌。这允许不寻常的行为,如预处理器注释技巧,在一致的预处理器下不起作用:
#如果 消失 #定义 U型/##/ #其他 #定义 U型 内景 #结束
//当类型转换为注释时,myVal将消失 //要使符合标准,请用适当的///#if/#endif包装以下行 U型 米瓦尔;
行为2
传统的预处理器错误地将字符串前缀组合到#运算符的结果中:
#定义 调试u信息 (值) L“调试前缀:” L#val公司 // ^ // 此前缀
常数 世界卫生组织 *信息= 调试u信息 (你好世界);
在这种情况下 L 前缀是不必要的,因为相邻的字符串文字在宏展开后仍然会合并。向后兼容的解决方案是将定义更改为:
#定义 调试u信息 (值) L“调试前缀:” #瓦尔 // ^ // 没有前缀
在将参数“字符串化”为宽字符串文字的方便宏中也存在此问题:
//传统的预处理器创建一个宽字符串文本标记 #定义 字符串 (str)左#str
//潜在修复: //使用L“”和#str的字符串串联来添加前缀 //这是因为相邻的字符串文字是在宏展开后合并的 #定义 字符串1 (str公司) “我” #str公司
//在#str通过额外的宏扩展字符串化后添加前缀 #定义 宽 (str)左##str #定义 字符串2 (str公司) 宽 (#str)
//使用串联运算符##组合标记。 //##和#的操作顺序未指定,尽管所有编译器 //在本例中,我选中了在###之前执行#运算符。 #定义 字符串3 (str)左####str
行为3[关于无效##的警告]
当 ## 运算符不会产生单个有效的预处理令牌,行为未定义。传统的预处理器将无声地无法组合令牌。新的预处理器将匹配大多数其他编译器的行为并发出诊断代码。
//###是不必要的,不会产生单个预处理令牌。 #定义 添加标准 (x) 标准::##x
//声明std::字符串 添加标准 (字符串)s;
行为4[可变宏中的逗号省略]
考虑以下示例:
无效 功能( 内景 , 内景 = 2, 内景 = 3); //此宏替换列表有一个逗号,后跟uu VAu参数__ #定义 功能 (a,…)函数 内景 主() { //以下宏替换为: //函数(10,20,30) 功能 (10, 20, 30);
//一个合格的预处理器将 将以下宏替换为: 函数(1,); //这将导致语法错误。 功能 (1, ); }
所有主要的编译器都有一个预处理器扩展来帮助解决这个问题。传统的MSVC预处理器总是在空之前删除逗号 __瓦乌阿格斯__ 替代品。在更新的预处理器中,我们决定更密切地关注其他流行的跨平台编译器的行为。要删除逗号,变量参数必须丢失(不仅仅是空的),并且必须用 ## 接线员。
#定义 功能2 (a,…)函数 内景 主() { //正在调用的宏中缺少变量参数 //逗号将被删除并替换为: //函数(1) 功能2 (1);
//variadic参数为空,但没有丢失(请注意 //参数列表中的逗号)。逗号不会被删除 //当宏被替换时。 //函数(1,) 功能2 (1, ); }
在即将到来的C++ 2A标准中,通过添加 __选择__ ,尚未实施。
行为5[宏参数是“解包的”]
在传统的预处理器中,如果一个宏将它的一个参数转发给另一个相关的宏,那么这个参数在被替换时不会被“解包”。通常这种优化不会引起注意,但会导致异常行为:
//用第一个参数和其余参数创建一个字符串。 #定义 两根线 (第一,…)#第一,##########VA#ARGS__ #定义 A ( … ) 两根线 (uu VAu ARGSuuuu)
常数 烧焦 *c[2]={ A (1, 2) }; //一致预处理器结果: //const char c[2]={“1”,“2”}; //传统的预处理器结果,所有参数都在第一个字符串中: //const char c[2]={“1,2”,};
扩展A()时,传统的预处理器会转发打包在中的所有参数 __瓦乌阿格斯__ 第一个论点 两根线 ,这就留下了 两根线 空的。这会导致#first的结果是“1,2”,而不仅仅是“1”。如果你紧跟其后,那么你可能会想知道这项研究的结果是什么# __瓦乌阿格斯__ 在传统的预处理器扩展中:如果variadic参数为空,则应生成空字符串文本“”。由于另一个问题,没有生成空字符串文本标记。
行为6[重新扫描宏的替换列表]
替换宏后,将重新扫描生成的标记,以查找需要替换的其他宏标识符。传统预处理器用于执行重新扫描的算法与基于实际代码的本例所示不一致:
#定义 猫 (a,b)a##b #定义 回声 (…)uuu VAu ARGS__
//IMPL1和IMPL2是实现细节 #定义 执行1 (前缀,值)做一件事(前缀,值) #定义 执行2 (前缀,值)做第二件事(前缀,值) //宏根据传递给宏开关的值选择展开行为 #定义 做什么 (宏U开关,b) 猫 (IMPL,宏U开关) 回声 (( “你好” ,b))
做什么 (1, “世界” ); //传统预处理器: //做一件事(“你好”,“世界”); //一致预处理器: //IMPL1(“你好”,“世界”);
尽管这个例子有点做作,但在测试预处理器对真实代码的更改时,我们已经遇到过这个问题几次了。为了看看发生了什么,我们可以从 做什么 :
做什么 (1, “世界” ) — > 猫 (实施例1) 回声 (( “你好” , “世界” ))
第二,CAT扩展:
猫 (IMPL,1)–>IMPL # #1->执行1
使代币处于这种状态:
执行1 回声 (( “你好” , “世界” ))
预处理器查找类似宏标识符IMPL1的函数,但它后面没有“(”,因此它不被视为类似宏调用的函数。它将转到以下标记,并找到正在调用的宏ECHO之类的函数:
回声 (( “你好” , “世界” ))– > ( “你好” , “世界” )
IMPL1不再考虑扩展,因此扩展的全部结果是:
执行1 ( “你好” , “世界” );
通过添加另一层间接寻址,可以修改宏,使其在实验预处理器和传统预处理器下的行为相同:
#定义 猫 (a,b)a##b #定义 回声 (…)uuu VAu ARGS__
//IMPL1和IMPL2是宏实现的详细信息 #定义 执行1 (前缀,值)做一件事(前缀,值) #定义 执行2 (前缀,值)做第二件事(前缀,值)
#定义 呼叫 (宏名,args)宏名args #定义 做什么事 (甲、乙) 呼叫 ( 猫 (IMPL,a), 回声 (( “你好” ,b)))
做什么事 (1, “世界” ); //宏扩展为: //做一件事(“你好”,“世界”);
下一步是什么…
预处理器大修尚未完成;我们将继续在实验模式下进行更改,并修复早期采用者的错误。
- 一些预处理器指令逻辑需要完成,而不是回到传统的行为
- 支持u Pragma
- C++ 20的特点
- 其他诊断改进
- 用于控制/E和/P下输出的新开关
-
推进阻塞错误
- 预处理器常量表达式中的逻辑运算符在新的预处理器中没有完全实现等等 #如果 新的预处理器可以回到传统的预处理器。只有在扩展与传统预处理器不兼容的宏时,这一点才明显,在构建boost预处理器插槽时可能会出现这种情况。
最后
我们很乐意为您下载 Visual Studio 2017 15.8版预览版 并尝试所有新的实验特性。 一如既往,我们欢迎您的反馈。我们可以通过下面的评论或电子邮件联系我们( visualcpp@microsoft.com ). 如果您在Visual Studio 2017中遇到MSVC的其他问题,请联系我们 帮助>报告产品中的问题 ,或通过 开发者社区 . 把你的建议告诉我们 用户语音 . 你也可以在Twitter上找到我们( @视觉 )还有Facebook( msftvisualcpp软件 ).
谢谢您,
菲尔·克里斯滕森,乌尔齐伊·卢夫桑巴特