整数下溢导致取模失败

Interger underflow causing modulo to fail

我有 4 张图像要显示,所以我使用 unsigned short 来表示当前图像的索引。当我按 A 时,索引减 1;当我按 D 时,索引增加 1。

我正在使用 image_index = (image_index - 1) % 4; 计算 A 按键上的索引(以及 D 按键上的 image_index = (image_index + 1) % 4;

如果我向前循环(IE,按 D),一切都按预期工作,但如果我在索引 0 处并按 A,它会下溢到无符号短整型的最大值,并且不会取模4 给我索引 3。

我知道对于有符号类型,overflow/underflow 是 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);
}

问题:

这是由于 unsigned short 类型提升为 (signed) int。 (有关详细信息,请参阅 this answer)。

假设您的 texture_index 最初为零。

当您执行 texture_index - 1 时,您的 texture_index 将升级为 (signed) int。这样计算的结果是(signed) int中的-1(-1) % 4 的结果是 -1(涉及负值的模计算可能很棘手且违反直觉,有关详细信息,请参阅 this question)。

然后,您将 -1signed int)分配(转换)为 texture_indexunsigned short)。此转换产生 65535(或 0xFFFF)。有关有符号到无符号转换的更多详细信息,请参阅 。所以问题不是忽略的模运算,而是不需要的类型转换(或提升)。


解决方法:

因此解决方案是消除不需要的转换。

在这个问题下的一条评论中,我看到了这样的内容:

texture_index = (texture_index - 1u) % 4u;

这消除了到 signed int 的转换,太棒了。它仍然会触发对 unsigned int 的提升(因为 1u4uunsigned int 文字),但是由于您的模很小,所以这无关紧要。

这对你的情况很好,但很脆弱

如果有一天您想要 五张 图片怎么办?

unsigned short texture_index = 0;
texture_index = (texture_index - 1U) % 5U;
assert(texture_index == 4U);    //Assertion fails!

为什么?调试器现在说 texture_index0。那是因为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