如何指示VC ++编译器不内联常量?

问题描述 投票:4回答:3

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

const int K = 123456 ;

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

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

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

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


我需要这样做的原因是,可执行文件可以很容易地修改,这些学生的任务是“破解”示例程序以改变其行为。对于没有经验的人来说,锻炼必须足够简单。

c++ assembly visual-c++ compilation const
3个回答
2
投票

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

int K = 123456;

假设您在程序中调用任何一个,即使链接时优化也无法知道库函数不会访问此全局。

如果您使用的static int K = 123456;,编译器可能会注意到编译单元中没有函数写入值,并且它们都没有传递或返回其地址,因此整个编译单元的转义分析可能会发现它实际上是一个常量并且可以进行优化远。

(如果你真的想让它成为static int K;,请包含一个你从未实际调用的像void setK(int x){K=x;}这样的全局函数。如果没有链接时优化,编译器将不得不假设这个编译单元之外的东西可以调用这个函数并改变了K,并且对定义不可见的函数的任何调用都可能导致这样的调用。)


请注意volatile const int K = 123456;可能会比使用非const更有害于优化,特别是如果你有多次使用K的表达式。

(但是这些中的任何一个都会受到很大的伤害,这取决于可能的优化。持续传播可能是一个巨大的胜利。)

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

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

在x86上,使用在L1d缓存中保持热点的内存源操作数可能不是一个性能问题,但它会阻止自动向量化。

我需要这样做的原因是,可执行文件可以很容易地修改,这些学生的任务是“破解”示例程序以改变其行为。对于没有经验的人来说,锻炼必须足够简单。

对于这个用例,是的volatile正是你想要的。在现场重新读取所有内容使得它比遵循寄存器中缓存的值稍微简单一些。

性能本质上是无关紧要的,你不会想要自动矢量化。可能只是轻量级优化,因此学生不必在每个C ++语句之后通过存储/重新加载所有内容。像gcc的-Og将是理想的。

使用MSVC,也许尝试-O1-O2,看看它是否有任何混乱。我不认为它有一些但不是过于激进的优化选项,它可能是调试版本(适用于单步执行C ++源代码,不适合阅读asm),或者完全针对大小或速度进行优化。


5
投票

如果你不想要内联K,那么把它放在一个头文件中:

extern const int K;

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

const int K = 123456;

在使用K的所有地方,编译器只知道Kconst int声明的extern。编译器不知道K的值,因此无法内联。链接器将在cpp文件中找到K的定义,将其放在可执行文件的.data部分中。

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

const volatile int K = 123456;

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

我必须说,这是一件非常奇怪的事情。如果要使程序可配置,则应将K的值放入文本文件中,然后在启动时读取该文件。


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.