【问题标题】:How to instruct VC++ compiler to not inline a constant?如何指示 VC++ 编译器不要内联常量?
【发布时间】:2019-04-20 07:42:19
【问题描述】:

我的 C++ 程序中有以下全局常量:

const int K = 123456 ;

当我编译程序时,生成的可执行文件在所有使用该值的地方(数十次)都包含文字值123456

但是,如果我删除 const 限定符,则值 123456 在整个可执行文件中仅出现一次(在 .data 部分中)。
这是我正在寻找的结果。我希望值 123456 只出现一次,以便只需使用 HEX 编辑器编辑 .exe 文件即可更改它。

但是,我不想删除 const 限定符,因为我希望编译器防止我意外修改源代码中的常量。

是否可以以某种方式指示编译器不要内联所述常量的值?


我需要这样做的原因是为了让学生可以轻松地修改可执行文件,这些学生将负责“破解”示例程序以改变其行为。对于没有经验的人来说,练习必须足够简单。

【问题讨论】:

    标签: c++ assembly visual-c++ compilation constants


    【解决方案1】:

    如果您不想内联K,请将其放入头文件中:

    extern const int K;
    

    这意味着“K 在其他地方定义”。然后把它放到一个cpp文件中:

    const int K = 123456;
    

    在所有使用K的地方,编译器只知道K是一个const int声明的externally。编译器不知道K 的值,所以它不能被内联。链接器会在 cpp 文件中找到K 的定义,并将其放入可执行文件的.data 部分。

    或者,您可以像这样定义K

    const volatile int K = 123456;
    

    这意味着“K 可能会神奇地改变,所以你最好不要假设它的值”。它与前面的方法具有类似的效果,因为编译器不会内联K,因为它不能假设K 将始终是123456。如果启用 LTO,之前的方法会失败,但在这种情况下使用 volatile 应该可以工作。

    我必须说,这是一件非常奇怪的事情。如果你想让你的程序可配置,你应该把K的值放到一个文本文件中,然后在启动时读取这个文件。

    【讨论】:

    • 好主意,但如果启用链接时优化,这可能会失败。同意这首先是一件很奇怪的事情:)
    • @Thomas 是的,我想过 LTO,但没有提及。
    • volatile const有类似的效果吗?
    • @Richt 我完全忘记了volatile!我会更新我的答案。
    • @HeinzKessler 好吧,const 只是说它不能从代码中更改,在很多情况下,某些硬件(例如传感器数据)可能会更改值,但无法更改来自代码。
    【解决方案2】:

    最简单的选择可能是将其声明为全局没有const,因此编译器不能假定它仍然具有静态初始化程序的值。

    int K = 123456;
    

    假设您在程序中调用 any

    如果你使用static int K = 123456;,编译器会注意到编译单元中没有函数写入值,也没有一个函数传递或返回它的地址,因此对整个编译单元的逃逸分析可以发现它实际上是一个常量并且可以被优化掉。

    (如果您真的希望它是 static int K;,请包含一个您从未实际调用过的全局函数,例如 void setK(int x){K=x;}。如果没有链接时间优化,编译器将不得不假设此编译单元之外的某些东西可能已经调用这个函数并更改了K,并且任何对定义不可见的函数的调用都可能导致这样的调用。)


    请注意,volatile const int K = 123456; 对优化的影响远大于使其成为非const,尤其是当您有多次使用K 的表达式时。

    (但其中任何一个都可能造成很大的伤害,具体取决于可能的优化。不断传播可能是一个巨大的胜利。)

    每次 C 抽象机读取它时,编译器都需要发出一次准确加载 K 的 asm。 (例如,读取 K 被认为是一种可见的副作用,例如从 MMIO 端口或您有硬件观察点的位置读取。)

    如果您想让编译器在每个循环中加载一次,并假设K 是循环不变量,那么使用它的代码应该执行int local_k = K;。由您决定重读K 的频率,即您在什么范围内/重做local_k = K

    在 x86 上,使用在 L1d 缓存中保持热的内存源操作数可能不会造成太大的性能问题,但它会阻止自动矢量化。

    我需要这样做的原因是为了让学生可以轻松地修改可执行文件,这些学生将负责“破解”示例程序以改变其行为。对于没有经验的人来说,练习必须足够简单。

    对于 this 用例,是的,volatile 正是您想要的。当场从内存中重新读取所有用途,这比跟踪缓存在寄存器中的值要简单一些。

    而且性能本质上是无关紧要的,而且您不会想要自动矢量化。可能只是轻微的优化,因此学生不必在每个 C++ 语句之后都费力地存储/重新加载所有内容。就像 gcc 的 -Og 会很理想。

    使用 MSVC,也许可以试试 -O1-O2 看看它是否有什么令人困惑的地方。我认为它没有一些优化选项,但不是太激进,它可能是调试构建(对于单步执行 C++ 源代码很好,不利于阅读 asm),或者针对大小或速度进行了全面优化。

    【讨论】:

      【解决方案3】:

      尝试将常量声明为 volatile。这应该会产生一个不会被内联的单一且可变的值。

      【讨论】:

        猜你喜欢
        • 2015-04-12
        • 2020-09-28
        • 1970-01-01
        • 1970-01-01
        • 2010-11-08
        • 2010-09-27
        • 1970-01-01
        • 2012-09-06
        • 2017-05-30
        相关资源
        最近更新 更多