整数下溢导致模运算失败

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)

Kae*_*Rin 5

问题:

那是由于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(因为1u4uunsigned 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_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
Run Code Online (Sandbox Code Playgroud)