【问题标题】:Minimum Guaranteed Constant Folding in CC中的最小保证常数折叠
【发布时间】:2016-04-08 16:30:25
【问题描述】:

问题

我很好奇是否可以保证在 C 中进行常量折叠。

我看过的地方

This link 在我不知道其声誉的网站上发表了一条不经意的评论:

所有 C 编译器都可以折叠在宏扩展后出现的整数常量表达式(ANSI C 要求)。

但我没有看到任何东西,例如C 编程语言,第二版(我认为已经彻底更新以说明 ANSI C 的所有细节)。但是在检查索引以获取相关单词的引用后,我没有发现任何可以保证这一点的东西。尤其是第 38 页和第 209 页非常接近,因为他们说任何 可以在编译时计算 的表达式都可以在可以使用常量的地方使用(如果我们足够迂腐的话,可能会有一些限制) ,并且它说这样的表达式“可能”在编译时被评估,而不是“will”/(一些同义词)。

我搜索了this C89 final draft。单词“folding”和“folded”没有产生值的结果,搜索“constant expression”产生了63个匹配,其中我检查了大约一半。感兴趣的主要部分似乎与本书基本相同(它使用“can”而不是“may”,但在这种情况下这些是同义词)。

这两个似乎在逻辑上强烈暗示每个 ANSI C 编译器都必须具有基本的常量折叠功能,但与此同时,似乎没有任何硬性禁止将常量表达式编译成代码在运行时计算表达式(这样的实现仍然受益于常量表达式,因为编译器可以生成计算一次然后假设值不会改变的代码,并且这样的实现可能会受到给定底层架构的限制 - 例如一些 RISC 架构必须使用两条指令来初始化某些可能的值,或者从内存位置加载它们。

我还简单地搜索了this C99 final draft,但“折叠”产生了一个没有价值的结果,而“折叠”和“常量”各有一百多个匹配项,我目前无法分配时间来抓取。

动机

我编写这些宏是为了在一些位旋转的代码中更清晰地表达语义/意图:

#define UCHAR_LOW_N_BITS_m(n) (unsigned char )(UCHAR_MAX >> (CHAR_BIT - (n)))
#define UCHAR_NTH_BIT_m(n) (unsigned char )(1 << (n))

..其中n 始终是整数文字。我想得到安慰,听到一个令人放心的声音说“没关系,使用的每个远程重要的 C 编译器都会为你折叠这些常量”。 (P.S. 我问了一个单独的问题,关于 UCHAR_NTH_BIT_m 是否应该像 bits start from the 0th or 1st bit 一样行事,希望在正确的地方。)

是的,底部的可以变成单独的宏,例如#define UCHAR_1ST_BIT_m (unsigned char )1#define UCHAR_3RD_BIT_m (unsigned char )4 或者我碰巧在代码中需要的许多代码 - 虽然我不确定其中哪个更好,但这可能是一个有争议的问题,因为如果我想成为一个好的迂腐语言 -律师类型的 C 程序员,我无法完全避免排名靠前的程序员(必须确保代码在那些 DSP/嵌入式和古老的大型机 C 实现上做正确的事情)。

【问题讨论】:

  • 这取决于编译器开发人员。 “Can”是美国的“may”,意思是你被允许但你不必这样做。是的,任何好的编译器都可以做到。
  • 我不认为“can”的用法仅限于美式英语。
  • @KlasLindbäck:我感谢您的澄清,但我知道:这就是为什么我说“似乎基本相同” - 因为我明白虽然有措辞不同,但它的意思基本相同事物。我会编辑它以减少进一步误解的机会。
  • 那么,您使用全大写符号(C 中明确表示这是宏或预处理器符号),加上小写的_m 后缀表示该符号是宏?好的。
  • “C 编程语言,第二版(我认为它已经彻底更新以考虑 ANSI C 的所有细节)”不是。与 C90 兼容的第二次更新是半心半意的。当然,C90 标准已经过时了。

标签: c language-lawyer constant-expression constantfolding


【解决方案1】:

在 C 标准中,编译时的术语是翻译时间,编译时发生的事件可以描述为翻译期间。您可以在标准中搜索这些术语。

本标准中与您的主题最接近的是第 6.6 节(C11)中常量表达式的定义。概述是:

常量表达式可以在翻译期间而不是运行时进行计算,因此可以在常量可能存在的任何地方使用。

请注意,“可以”并不意味着“必须”。我们希望高质量的编译器在编译时评估常量表达式,尽管这不是绝对要求。

第 6.6 节太大,无法在此处粘贴整个内容,但通过查阅 C 标准(或 N1570 等草案),您可以了解哪些表达式被定义为 常量表达式,它会合理地假设您的编译器在编译时评估它们。

如果n &gt;= 0 &amp;&amp; n &lt; CHAR_BIT * sizeof(int) - 1n 是一个整数常量表达式,那么(unsigned char )(1 &lt;&lt; (n)) 是一个整数常量表达式,因为:它的操作数是整数常量,它不使用其中一个常量表达式中禁止使用的运算符(例如++),并且允许转换,因为它将整数类型转换为另一种整数类型。

如果n 超出此范围,则表达式不是常量表达式,并导致未定义的行为。

你的其他表达方式类似;只要CHAR_BIT - n 在同一有效范围内,它就是 ICE。

【讨论】:

    【解决方案2】:

    一般来说,C 标准不指定如何编译。你永远无法保证一种实现某事的方法比另一种更快。不能保证必须发生常量折叠,但由于编译器必须能够在编译时评估常量表达式(以处理 #if 指令和数组声明符),编译器很可能会进行常量折叠。毕竟,能够进行恒定折叠但又不这样做是没有意义的。

    【讨论】:

    • 这或多或少是我的想法。我已经 +1 了,除非出现更好的答案,否则我会在一两天内接受它。
    • #if 指令的内容由预处理器而不是编译器计算,但在由编译器处理的数组声明中允许使用常量表达式。
    • @supercat 标准并没有说明编译器是如何实现的,它只是规定了一系列翻译阶段,其中前几个阶段通常在预处理器中实现。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-01-31
    • 2021-10-19
    • 2015-02-03
    • 1970-01-01
    • 2021-03-11
    • 2010-12-16
    • 2014-03-09
    相关资源
    最近更新 更多