Aco*_*orn 2 c++ modulo underflow undefined-behavior
我想显示 4 张图像,因此我使用 来unsigned short表示当前图像的索引。当我按A时,索引减1;当我按 D 时,索引增加 1。
image_index = (image_index - 1) % 4;我正在使用(以及 D 按键上的image_index = (image_index + 1) % 4;)计算 A 按键上的索引
如果我向前循环(即,按 D),一切都会按预期工作,但如果我位于索引 0 并按 A,它会下溢到无符号短整型的最大值,并且不会以 4 为模来给出索引3.
我知道对于有符号类型,上溢/下溢是 UB,但我确信对于无符号类型,它是定义良好的。有什么想法可能导致它忽略模数吗?
enum class Errors : short {
kSdlSuccess = 0,
kSdlInitFailure = -1,
kSdlWindowCreationError = -2,
kSdlRendererCreationError = -3
};
int main(int argc, char** argv) {
sdl::Context ctx;
sdl::Window window({ .w = 600, .h = 480, .flags = 0});
sdl::Renderer renderer(window, {0});
bool is_running {true};
unsigned short texture_index = 0;
SDL_Event event {};
while(is_running) {
while(SDL_PollEvent(&event)) {
if(event.type == SDL_QUIT) { is_running = false; }
else if(event.type == SDL_KEYDOWN) {
if(event.key.keysym.scancode == SDL_SCANCODE_A) { texture_index = (texture_index - 1) % 4; }
else if(event.key.keysym.scancode == SDL_SCANCODE_D) { texture_index = (texture_index + 1) % 4; }
}
}
printf("%d\n", texture_index);
renderer.setDrawColor(255, 0, 0, 255);
renderer.clearBuffer();
renderer.setDrawColor(0,0,0,255);
renderer.drawTexture(texture_index);
renderer.swapBuffer();
}
return static_cast<int>(Errors::kSdlSuccess);
}
Run Code Online (Sandbox Code Playgroud)
那是由于unsigned short类型提升为(signed) int. (有关更多详细信息,请参阅此答案)。
假设你的texture_index初始值为零。
当您这样做时texture_index - 1,您texture_index将晋升为(signed) int。这样的计算结果-1在 中(signed) int。的结果(-1) % 4是-1(涉及负值的模计算可能很棘手且违反直觉,请参阅此问题了解更多详细信息)。
然后,将-1(a signed int) 分配(转换)为texture_index(an unsigned short)。该转换产生65535( 或0xFFFF)。有关有符号到无符号转换的更多详细信息,请参阅此答案。因此,问题不是忽略模运算,而是不需要的类型转换(或升级)。
因此,解决方案是消除不需要的转换。
在这个问题下的一条评论中,我看到了这样的内容:
texture_index = (texture_index - 1u) % 4u;
Run Code Online (Sandbox Code Playgroud)
这消除了到 的转换signed int,太棒了。它仍然会触发提升到unsigned int(因为1u和4u是unsigned int文字),但由于你的模很小,所以这并不重要。
这在你的情况下工作正常,但很脆弱。
如果有一天你想要五张图片怎么办?
unsigned short texture_index = 0;
texture_index = (texture_index - 1U) % 5U;
assert(texture_index == 4U); //Assertion fails!
Run Code Online (Sandbox Code Playgroud)
为什么?调试器现在说的texture_index是0. 那是因为0U - 1U == 4294967295U。
绕过此类问题的一个好技巧是在求模之前将除数添加到被除数中。
unsigned short texture_index = 0;
texture_index = (texture_index - 1U + 5U) % 5U; //Add 5U before modding 5U
assert(texture_index == 4U); //Assertion passes
Run Code Online (Sandbox Code Playgroud)