【问题标题】:The best way in C++ to cast different signedness types each other?C ++中相互转换不同签名类型的最佳方法?
【发布时间】:2021-03-14 06:06:13
【问题描述】:

通信对等方发送了一个 uint64_t 数据字段,它带有我需要存储到不支持无符号整数类型的 Postgresql-11 数据库中的订单 ID。 虽然真实数据可能超过 2^63,但我认为 Postgresql11 中的INT8 可以容纳它,如果我仔细进行一些转换。

假设有:

uint64_t order_id = 123; // received
int64_t  to_db;          // to be writed into db

我计划使用以下方法之一将 uint64_t 值转换为 int64_t 值:

  1. to_db = order_id; // 直接赋值;
  2. to_db = (int64_t)order_id; //c-style cast;
  3. to_db = static_cast<int64_t>(order_id);
  4. to_db = *reinterpret_cast<const int64_t*>( &order_id );

当我需要从数据库加载它时,我可以进行反向转换。

我知道它们都有效,我只是对哪个最符合 C++ 标准感兴趣。

换句话说,哪种方法在任何 64 位平台和任何编译器中始终可以工作?

谢谢!!!

【问题讨论】:

  • 如果值超过 2^63-1,每一个都是未定义的行为。
  • 还有一个附加选项:memcpy(&to_db, &order_id, 8);.
  • @DanielLangr 有效.. 只要写入这些值的系统和读取它们的系统处于相同条件
  • @n.'pronouns'm。此外,AFAIK,第 4 种情况总是产生未定义的行为。

标签: c++ casting reinterpret-cast static-cast signedness


【解决方案1】:

取决于它将在哪里编译和运行...如果没有 C++20 支持,任何那些都不能完全移植。

没有那个最安全的方法是通过改变值的范围来自己进行转换,类似的东西

int64_t to_db = (order_id > (uint64_t)LLONG_MAX) 
           ? int64_t(order_id - (uint64_t)LLONG_MAX - 1) 
           : int64_t(order_id ) - LLONG_MIN;

uint64_t from_db = (to_db < 0) 
                    ? to_db + LLONG_MIN
                    : uint64_t(to_db) +  (uint64_t)LLONG_MAX  + 1;

如果 order_id 大于 (2^63 -1),则 order_id - (uint64_t)LLONG_MAX - 1 产生非负值。如果不是,则强制转换为有符号是明确定义的,并且减法可确保将值转移到负范围内。

在反向转换期间,to_db + LLONG_MIN 将值放入 [0, ULLONG_MAX] 范围内。

在阅读时做相反的事情。您使用的数据库平台或编译器在将无符号值的二进制表示转换为有符号时可能会做一些糟糕的事情,更不用说确实存在不同的有符号格式。

出于同样的原因,跨平台协议通常涉及使用字符串格式或“最小位值”来将浮点值表示为整数,即编码定点。

【讨论】:

    【解决方案2】:

    我会选择memcpy。它避免了(?参见 cmets)未定义的行为,并且通常编译器会优化任何字节复制:

    int64_t uint64_t_to_int64_t(uint64_t u)
    {
      int64_t i;
      memcpy(&i, &u, sizeof(int64_t));
      return i;
    }
    
    order_id = uint64_t_to_int64_t(to_db);
    

    带有-O2 的GCC 为uint64_t_to_int64_t 生成了最佳程序集:

    mov rax, rdi
    ret
    

    现场演示:https://godbolt.org/z/Gbvhzh

    【讨论】:

    • “它避免了未定义的行为”我不会提出这样的要求。
    • @n.'pronouns'm。为什么不呢?
    • memcpy 本身是可以的,是值的后续使用有问题。 C++20 可能会成功,但不确定。
    • @n.'pronouns'm。你能解释一下吗?
    • 我认为您不能将任何随机位模式放入有符号整数类型中并期望它能够工作。我在标准中没有看到任何这样的保证。
    【解决方案3】:

    只要值在范围内,所有四种方法都将始终有效。第一个将在许多编译器上生成警告,因此可能不应该使用。第二个更像是 C 习语而不是 C++ 习语,但在 C++ 中被广泛使用。最后一个很丑陋,依赖于标准中的细微细节,不应该使用。

    【讨论】:

      【解决方案4】:

      这个功能似乎没有 UB

      int64_t fromUnsignedTwosComplement(uint64_t u)
      {
          if (u <= std::numeric_limits<int64_t>::max()) return static_cast<int64_t>(u);
          else return -static_cast<int64_t>(-u);
      }
      

      它在优化下减少为无操作。

      另一个方向的转换是直接转换为uint64_t。它总是定义明确的。

      【讨论】:

      • 您能否详细说明-u 在后一个演员阵容中的作用?
      • @Surt 它计算表达式-u2^64,如果我们可以写下常量2^64,它将与2^64-u 相同。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-28
      • 1970-01-01
      • 1970-01-01
      • 2011-01-22
      • 2019-11-01
      • 1970-01-01
      • 2014-03-29
      相关资源
      最近更新 更多