代码之家  ›  专栏  ›  技术社区  ›  goodvibration

实施安全左移

  •  1
  • goodvibration  · 技术社区  · 8 年前

    我想实现一个左移函数,它会在溢出时触发故障。

    这是我的代码:

    uint32_t safe_shl(uint32_t x, uint8_t y) {
        uint32_t z = x << y;
        assert((z >> y) == x);
        return z;
    }
    

    请假设我 assert 函数在我的系统中注册错误。

    我希望确保我的方法是防弹的(即,在每次错误输入时失败,并且仅在错误输入时失败)。

    我还想问你是否知道一种更有效的方法来实现这一点(假设它确实是防弹的)。

    5 回复  |  直到 5 年前
        1
  •  4
  •   molbdnilo    8 年前

    如果 x << y 未定义,所有赌注均已取消。
    唯一安全的方法是在尝试之前检查它是否有效。

    uint32_t safe_shl(uint32_t x, uint8_t y) {
        assert (y < 32);
        if (y < 32)
        {
            uint32_t z = x << y;
            assert((z >> y) == x);
            return z;
        }
        return 0;
    }
    

    请注意,您需要条件–无条件移位让编译器假定 y < 32 是真的。

        2
  •  1
  •   chux    8 年前

    第1步。如果 x == 0 而任何移位量,从概念上讲,结果仍然是0,这不是问题。

    第2步。不要尝试过度换档。

    如果右操作数的值为负数或大于或等于提升后的左操作数的宽度,则行为未定义。C11§6.5.7 3

    第3步。移位时确保无符号数学。

    如果 int/unsigned 宽度大于 uintN_t x 然后 x << y 已完成 int 数学这是罕见的 N==32 但这是可能的。有符号数学溢出可能导致UB。通过 1u*x (0u+x) ,代码可以确保shift使用更广泛的 unsigned uintN_t 数学好的编译器仍然可以生成最佳代码。

    第4步。检测是否发生减少。

    如果E1具有无符号类型,则结果的值为E1 2E2,减少模 比结果类型§6.5.7中可表示的最大值多出一个4

    uint32_t safe_shl(uint32_t x, uint8_t y) {
      if (x == 0) {
        return 0;
      } 
      assert(y < 32);
      uint32_t z = (1u*x) << y;
      assert((z >> y) == x);
      return z;
    }
    
        3
  •  1
  •   Richard Hodges    8 年前

    您是否要求断言该移位是否会导致进位?

    在这种情况下,如果不使用内部函数或汇编程序,在c++中就有点麻烦了。

    #include <cassert>
    #include <cstdint>
    #include <limits>
    
    bool shl_would_carry(uint32_t x, uint8_t y)
    {
        constexpr auto nof_bits = std::numeric_limits<decltype(x)>::digits;
        if (y >= nof_bits)
        {
            if (x != 0) return true;
        }
        else
        {
            auto limit = decltype(x)(1) << (nof_bits - y);
            if (x >= limit) return true;
        }
        return false;
    }
    
    uint32_t safe_shl(uint32_t x, uint8_t y) 
    {
        assert(!shl_would_carry(x, y));
        return x << y;
    }
    

    我想这是对的。

    这可能更好:

    std::tuple<uint32_t, uint32_t> shl(uint32_t x, uint8_t y)
    {
        uint32_t overflow, result;
        constexpr auto nof_bits = std::numeric_limits<decltype(x)>::digits;
        overflow = x >> (nof_bits - y); 
        result = x << y;
        return std::make_tuple(overflow, result);
    }
    
    uint32_t safe_shl(uint32_t x, uint8_t y) 
    {
        auto t = shl(x, y);
        assert(!std::get<0>(t));
        return std::get<1>(t);
    }
    
        4
  •  1
  •   Serge Ballesta    8 年前

    在C中, x << y 如果定义用于 uint32_t 假如 y < 32 . 根据6.5.7位移位运算符中C11的n1570草稿:

    如果右操作数的值为负数或大于或等于提升后的左操作数的宽度,则行为未定义。

    然后要求中的结果为:x 2 y , 约化模 比结果类型中可表示的最大值多一个

    让我们调用该值 z 就像你提议的代码一样。就像使用无符号类型一样 z >> y 必须是 z/2的组成部分 y .

    这意味着 提供y<32 如果存在溢出,则 z>&燃气轮机;y 由于模的存在,将严格小于x,如果没有溢出,则得到x

    6.5.7位移位运算符的完整参考:

    ...
    4 E1和lt的结果&书信电报;E2为E1左移E2位位置;空出的位充满 零。如果E1具有无符号类型,则结果的值为E1 2E2,减少模 比结果类型中可表示的最大值多一个。如果E1有签名 类型和非负值,而E1×2E2可在结果类型中表示,则 结果值;否则,行为未定义。

    5 E1的结果>&燃气轮机;E2是E1右移E2位位置。如果E1具有无符号类型 或者,如果E1具有有符号类型和非负值,则结果的值为整数 E1/2E2商的一部分。如果E1具有有符号类型和负值,则 结果值由实现定义。


    这与5.8移位运算符[expr.Shift]中针对C++14的n4296草稿中的C++完全相同:

    ...如果右操作数 为负,或大于或等于提升后的左操作数的位长度。

    2 E1值<&书信电报;E2为E1左移E2位位置;空出的位为零填充。如果E1具有无符号 类型,结果值为E1×2E2,比可表示的最大值多模1 在结果类型中。否则,如果E1具有有符号类型和非负值,并且E1×2E2是可表示的 在结果类型的相应无符号类型中,转换为结果类型的值是 产生的价值;否则,行为未定义。

    3 E1值>&燃气轮机;E2是E1右移E2位位置。如果E1具有无符号类型或E1具有有符号类型 类型和非负值,结果的值是E1/2E2商的整数部分。如果E1 具有有符号类型和负值,结果值是实现定义的。


    所以在两种语言中,假设 assert函数在[您的]系统中注册错误 ,代码应为:

    uint32_t safe_shl(uint32_t x, uint8_t y) {
        assert(y<32);
        uint32_t z = x << y;
        assert((z >> y) == x);
        return z;
    }
    
        5
  •  1
  •   Lundin    8 年前

    为了编写一个安全的函数,您必须首先确定哪些是不安全的。如果你不这样做,这项任务就是胡说八道。你提到的那种“溢出”实际上是定义明确的。但存在以下危险行为:

    • 左移大于变量的大小,包括将数据移到有符号变量的符号位。(未定义的行为)
    • 右运算符是负数。(未定义的行为)
    • 右移负数。(实施定义的行为)
    • 左操作数的隐式整数提升,导致其静默更改有符号性,从而调用上述错误之一。

    要避免这种情况,您需要确保:

    1. 左操作数必须是无符号的。
    2. 右操作数必须有效,并且在左操作数类型的范围内。
    3. 左操作数不能是小整数类型。

    1) 和3)通过使用 uint32_t . 不存在任何系统 uint32\u t 小于 int .

    2) 通过使用无符号类型并检查其是否太大来解决。

    此外,您似乎有一个要求,即不允许左操作数越界。这很奇怪,但好吧,让我们也实现它。可以通过检查MSB位位置加上移位数是否大于31来完成。

    uint8_t msb_pos32 (uint32_t data)
    {
      uint8_t result = 0;
      while(data>>=1 > 0)
      {
        result++;
      }
      return result;
    }
    
    uint32_t safe_LSL32 (uint32_t x, uint8_t y) 
    {
      if(y > 31 || y+msb_pos32(x) > 31)
      {
        __asm HCF;               // error handling here
      }
      return x << y;
    }
    

    请注意,此代码可以进一步优化。