【问题标题】:How do I deal with "signed/unsigned mismatch" warnings (C4018)?如何处理“签名/未签名不匹配”警告 (C4018)?
【发布时间】:2011-11-18 14:00:40
【问题描述】:

我使用大量用 编写的计算代码,考虑到高性能和低内存开销。它大量使用 STL 容器(主要是 std::vector),并且几乎在每个函数中都对这些容器进行迭代。

迭代代码如下所示:

for (int i = 0; i < things.size(); ++i)
{
    // ...
}

但它会产生 signed/unsigned mismatch 警告(Visual Studio 中的 C4018)。

用一些unsigned 类型替换int 是个问题,因为我们经常使用OpenMP pragma,它要求计数器是int

我即将取消(数百个)警告,但恐怕我错过了一些解决问题的优雅方法。

在迭代器上。我认为迭代器在适当的地方应用时很棒。我正在使用的代码将永远将随机访问容器更改为std::list 或其他东西(因此使用int i 进行迭代已经与容器无关),并且将始终需要当前索引。您需要输入的所有额外代码(迭代器本身和索引)只会使事情复杂化并混淆底层代码的简单性。

【问题讨论】:

  • 您能否发布一个示例,其中 OpenMP 杂注会阻止您使用无符号类型?根据this 它应该适用于任何intergal 类型,而不仅仅是int
  • 我相信这个问题更适合stackoverflow。
  • intstd::vector&lt;T&gt;::size_type 的大小和符号也可能不同。例如,在 LLP64 系统(如 64 位 Windows)上,sizeof(int) == 4sizeof(std::vector&lt;T&gt;::size_type) == 8

标签: c++ c++ comparison refactoring unsigned signed


【解决方案1】:

这一切都在您的things.size() 类型中。它不是 int,而是 size_t(它存在于 C++ 中,而不是 C 中),它等于某些“通常”的无符号类型,即 x86_32 的 unsigned int

运算符“less”(

这样写就对了

for (size_t i = 0; i < things.size(); ++i) { /**/ }

甚至更快

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }

【讨论】:

  • -1 不,它不是size_t。它是std::vector&lt; THING &gt;::size_type
  • @Raedwald:虽然您在技术上是正确的,但很难想象一个符合标准的实现最终会为std::size_tstd::vector&lt;T&gt;::size_type 提供不同的底层类型。
  • 为什么 ++i 被认为更好?在 for 循环中没有“没有”区别吗?
  • @ShoaibHaider,对于不使用返回值的原语来说,这根本不重要。但是,对于自定义类型(运算符重载),后自增几乎总是效率较低(因为它必须在对象自增之前创建一个副本)。编译器不能(必然)针对自定义类型进行优化。所以唯一的优势是一致性(原语与自定义类型)。
  • @zenith:是的,你是对的。我的声明仅适用于默认分配器。自定义分配器可能使用 std::size_t 以外的其他东西,但我相信它仍然必须是无符号整数类型,并且它可能无法表示比 std::size_t 更大的范围,因此使用 std::size_t 仍然是安全的::size_t 作为循环索引的类型。
【解决方案2】:

理想情况下,我会使用这样的结构:

for (std::vector<your_type>::const_iterator i = things.begin(); i != things.end(); ++i)
{
  // if you ever need the distance, you may call std::distance
  // it won't cause any overhead because the compiler will likely optimize the call
  size_t distance = std::distance(things.begin(), i);
}

这有一个巧妙的优势,即您的代码突然变得与容器无关。

关于您的问题,如果您使用的某些库要求您使用int,而unsigned int 更适合,那么它们的API 很混乱。无论如何,如果你确定那些int 总是积极的,你可以这样做:

int int_distance = static_cast<int>(distance);

这将清楚地表明您对编译器的意图:它不会再用警告来打扰您了。

【讨论】:

  • 总是需要距离。如果没有其他解决方案,也许static_cast&lt;int&gt;(things.size()) 可能是解决方案。
  • @Andrew:如果您决定取消警告,最好的方法可能是使用编译器特定的编译指示(在 MSVC 上,这将是 #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop))而不是使用不必要的强制转换. (演员表隐藏了合法的错误,m'kay?;))
  • 不正确。 Cast != 转换。该警告是关于标准允许的可能不安全的隐式转换的警告。然而,演员表会引起对派对的明确转换,这可能比警告最初谈到的更不安全。严格来说,至少在 x86 上,根本不会发生任何转换——处理器并不关心您将内存的特定部分视为有符号还是无符号,只要您使用正确的指令来处理它。
  • 当与容器无关时会导致 O(N^2) 复杂性(在您的示例中确实如此,因为 list 的 distance() 是 O(N)),我不太确定是一个优势:-(。
  • @No-BugsHare 这正是重点:我们无法确定。如果 OP 的元素很少,那么它可能很棒。如果他有数百万个,可能不会那么多。最终只能通过分析来判断,但好消息是:您始终可以优化可维护的代码!
【解决方案3】:

如果您不能/不会使用迭代器,并且如果您不能/不会使用 std::size_t 作为循环索引,请创建一个 .size()int 的转换函数来记录假设并执行显式转换以使编译器警告静音。

#include <cassert>
#include <cstddef>
#include <limits>

// When using int loop indexes, use size_as_int(container) instead of
// container.size() in order to document the inherent assumption that the size
// of the container can be represented by an int.
template <typename ContainerType>
/* constexpr */ int size_as_int(const ContainerType &c) {
    const auto size = c.size();  // if no auto, use `typename ContainerType::size_type`
    assert(size <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
    return static_cast<int>(size);
}

然后你这样写你的循环:

for (int i = 0; i < size_as_int(things); ++i) { ... }

这个函数模板的实例化几乎肯定会被内联。在调试版本中,将检查假设。在发布版本中,它不会,并且代码将与您直接调用 size() 一样快。这两个版本都不会产生编译器警告,并且只是对惯用循环的轻微修改。

如果您还想在发布版本中捕获假设失败,您可以将断言替换为抛出类似std::out_of_range("container size exceeds range of int") 的 if 语句。

请注意,这解决了有符号/无符号比较以及潜在的 sizeof(int) != sizeof(Container::size_type) 问题。您可以启用所有警告并使用它们来捕获代码其他部分中的真正错误。

【讨论】:

    【解决方案4】:

    你可以使用:

    1. size_t 类型,用于删除警告消息
    2. 迭代器 + 距离(就像是第一个提示)
    3. 只有迭代器
    4. 函数对象

    例如:

    // simple class who output his value
    class ConsoleOutput
    {
    public:
      ConsoleOutput(int value):m_value(value) { }
      int Value() const { return m_value; }
    private:
      int m_value;
    };
    
    // functional object
    class Predicat
    {
    public:
      void operator()(ConsoleOutput const& item)
      {
        std::cout << item.Value() << std::endl;
      }
    };
    
    void main()
    {
      // fill list
      std::vector<ConsoleOutput> list;
      list.push_back(ConsoleOutput(1));
      list.push_back(ConsoleOutput(8));
    
      // 1) using size_t
      for (size_t i = 0; i < list.size(); ++i)
      {
        std::cout << list.at(i).Value() << std::endl;
      }
    
      // 2) iterators + distance, for std::distance only non const iterators
      std::vector<ConsoleOutput>::iterator itDistance = list.begin(), endDistance = list.end();
      for ( ; itDistance != endDistance; ++itDistance)
      {
        // int or size_t
        int const position = static_cast<int>(std::distance(list.begin(), itDistance));
        std::cout << list.at(position).Value() << std::endl;
      }
    
      // 3) iterators
      std::vector<ConsoleOutput>::const_iterator it = list.begin(), end = list.end();
      for ( ; it != end; ++it)
      {
        std::cout << (*it).Value() << std::endl;
      }
      // 4) functional objects
      std::for_each(list.begin(), list.end(), Predicat());
    }
    

    【讨论】:

      【解决方案5】:

      我还可以为 C++11 提出以下解决方案。

      for (auto p = 0U; p < sys.size(); p++) {
      
      }
      

      (C++ 不够聪明,无法自动 p = 0,所以我必须把 p = 0U....)

      【讨论】:

      • +1 用于 C++11。除非有一个你不能使用 C++11 的理由,否则我认为最好使用新功能......它们将提供很大的帮助。如果您实际上不需要索引,请务必使用for (auto thing : vector_of_things)
      • 但这只能解决签名问题。如果size() 返回的类型大于 unsigned int 也无济于事,这种情况非常常见。
      【解决方案6】:

      我会给你一个更好的主意

      for(decltype(things.size()) i = 0; i < things.size(); i++){
                         //...
      }
      

      decltype

      检查实体的声明类型或类型和值类别 一个表达式。

      所以,它推断things.size() 的类型和i 将是与things.size() 相同的类型。所以, i &lt; things.size() 将在没有任何警告的情况下执行

      【讨论】:

        【解决方案7】:

        C++20 现在有std::cmp_less

        中,我们有标准的constexpr 函数

        std::cmp_equal
        std::cmp_not_equal
        std::cmp_less
        std::cmp_greater
        std::cmp_less_equal
        std::cmp_greater_equal
        

        &lt;utility&gt; 标头中添加,正是针对这种情况。

        比较两个整数tu 的值。与内置比较运算符不同,带负号的整数总是比较小于(且不等于)无符号整数:比较可以安全地避免有损整数转换

        这意味着,如果(由于某些有线原因)必须使用i 作为integer,循环,并且需要与无符号整数进行比较,可以这样做:

        #include <utility> // std::cmp_less
        
        for (int i = 0; std::cmp_less(i, things.size()); ++i)
        {
            // ...
        }
        

        如果我们将static_cast 错误地-1(即int)转换为unsigned int,这也涵盖了这种情况。这意味着,以下内容不会给您错误:

        static_assert(1u < -1);
        

        但是std::cmp_less的用法会

        static_assert(std::cmp_less(1u, -1)); // error
        

        【讨论】:

          【解决方案8】:

          我遇到了类似的问题。使用 size_t 不起作用。我尝试了另一个对我有用的。 (如下)

          for(int i = things.size()-1;i>=0;i--)
          {
           //...
          }
          

          【讨论】:

            【解决方案9】:

            我会做的

            int pnSize = primeNumber.size();
            for (int i = 0; i < pnSize; i++)
                cout << primeNumber[i] << ' ';
            

            【讨论】:

              猜你喜欢
              • 2020-08-03
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-09-01
              • 2018-10-23
              • 1970-01-01
              相关资源
              最近更新 更多