我该如何处理“signed/unsigned mismatch”警告(C4018)?

88

我处理了很多计算代码,这些代码是使用高性能和低内存开销为目标编写的。它大量使用STL容器(主要是std::vector),并在几乎每个函数中迭代这些容器。

迭代的代码看起来像这样:

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

但是它产生了有符号/无符号不匹配警告(在Visual Studio中为C4018)。

int替换为某个unsigned类型是一个问题,因为我们经常使用OpenMP指令,而且计数器必须为int

我将要抑制(hundreds of)这些警告,但我担心我可能错过了一些优雅的解决方案。

关于迭代器。我认为迭代器在适当的地方应用会很好。我正在处理的代码将永远不会将随机访问容器转换成std::list或其他容器(因此使用int i进行迭代已经与容器无关),并且将始终需要当前索引。而且所有额外需要输入的代码(迭代器本身和索引)只会使问题更加复杂和混淆底层代码的简洁性。


1
你能否提供一个例子,其中OpenMP pragma防止您使用无符号类型?根据此处的说明,它应该适用于任何整数类型,而不仅仅是int - Billy ONeal
4
我认为这个问题更适合在StackOverflow上提问。 - bcsanches
1
在大小和签名上,“int”和“std::vector<T>::size_type”也可能不同。例如,在LLP64系统(如64位Windows)上,“sizeof(int) == 4”,但“sizeof(std::vector<T>::size_type) == 8”。 - Adrian McCarthy
可能是重复的问题:大多数有符号/无符号警告的可接受解决方案? - Adrian McCarthy
可能是 https://dev59.com/jGsy5IYBdhLWcg3w-i_h 的重复问题。 - CinCout
请查看此建议 https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/33336253-do-not-show-warning-on-a-mixed-comparison-where-on - KindDragon
9个回答

70

问题出在你的 things.size() 类型上。它不是 int,而是 size_t(它存在于 C++ 中,而不是 C),它等同于一些“通常”的无符号类型,例如 x86_32 的 unsigned int

运算符“小于”(<)不能应用于两个不同符号的操作数。没有这样的操作码,并且标准没有指定编译器是否可以进行隐式符号转换。因此,它将有符号数视为无符号数并发出警告。

正确的写法应该是:

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

甚至更快

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

21
不,它不是size_t,而是std::vector<THING>::size_type - Raedwald
9
虽然你在技术上是正确的,但很难想象一个符合标准的实现会在std::size_tstd::vector<T>::size_type上具有不同的基础类型。 - Adrian McCarthy
4
为什么++i被认为是更好的选择?在for循环中它不是和i++没有区别吗? - Shoaib
2
@AdrianMcCarthy std::vector<T>::size_type 取决于所使用的分配器。如果使用默认分配器 (std::allocator<T>),则 size_type == std::size_t。对于自定义分配器,size_type 可能与 std::size_t 不同。 - emlai
3
是的,你说得对。我的陈述仅适用于默认分配器。自定义分配器可能使用除 std::size_t 之外的其他类型,但我认为它仍必须是无符号整数类型,并且它可能不能表示比 std::size_t 更大的范围,因此在循环索引的类型中使用 std::size_t 仍然是安全的。 - Adrian McCarthy
显示剩余3条评论

13
理想情况下,我会使用类似这样的结构来代替:
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);

这将清晰地告知编译器您的意图:它不会再向您发出警告。

1
我总是需要距离。如果没有其他解决方案,也许static_cast<int>(things.size())可以成为解决方案。 - Andrew T
@Andrew:如果你决定抑制警告,最好的方法可能是使用编译器特定的#pragma指令(在MSVC上,这将是#pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)),而不是使用不必要的强制转换。(强制转换会隐藏合法错误,明白吗?;)) - Billy ONeal
不是真的。Cast != conversion。警告是关于标准允许的隐式转换可能不安全。然而,强制转换邀请显式转换参加聚会,这可能比警告中最初讨论的更不安全。严格来说,在x86上,根本不会发生任何转换——处理器不关心您是否将内存的特定部分视为有符号或无符号,只要您使用正确的指令来处理它即可。 - Billy ONeal
当容器无关性导致O(N^2)复杂度时(在您的示例中,distance()对于list<>是O(N)),我不确定这是否是一个优势 :-(。 - No-Bugs Hare
@No-BugsHare 这正是关键所在:我们无法确定。如果 OP 只有少量元素,那么这可能非常好。如果他有数百万个元素,那么可能就不是那么好了。最终只有通过分析才能得出结论,但好消息是:您始终可以优化可维护的代码! - ereOn

11

如果您不能/不想使用迭代器,或者不能/不想使用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)问题。您可以保留所有警告并使用它们来捕获代码其他部分的真正错误。


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

这种情况下应该在<utility>头文件中添加。

比较两个整数tu的值。与内置比较运算符不同,负有符号整数始终小于(而不是等于)无符号整数: 比较对于有损整数转换是安全的

也就是说,如果(由于某些奇怪的原因)必须将i作为integer使用,并且需要与无符号整数进行比较,则可以执行此操作:

#include <utility> // std::cmp_less

for (int i = 0; std::cmp_less(i, things.size()); ++i)
{
    // ...
}

这也包括以下情况:如果我们错误地将-1(即int)转换为unsigned int,那么也会被涵盖在内。这意味着,以下代码不会报错:
static_assert(1u < -1);

但是使用 std::cmp_less 将会

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

6
您可以使用以下方法之一来解决问题:
  1. 使用size_t类型,以消除警告信息
  2. 使用迭代器和distance函数(如第一个提示所示)
  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());
}

4
我可以为C++11提供以下解决方案。
for (auto p = 0U; p < sys.size(); p++) {

}

(C++对于auto p = 0不够聪明,所以我必须将p = 0U。)


1
如果没有充分的理由不能使用C++11,我认为最好使用新功能...它们旨在提供巨大的帮助。如果您实际上不需要索引,请一定使用for (auto thing : vector_of_things)。+1 for C++11. - parker.sikand
但这只解决了有符号问题。如果size()返回的类型大于无符号整数,那就没有帮助了,而这种情况非常普遍。 - Adrian McCarthy

4

我将为您提供更好的想法。

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

decltype是一种用于检查实体的声明类型或表达式的类型和值类别的方法。

因此,它推断出things.size()i的类型将与things.size()相同。因此,i < things.size()将在没有任何警告的情况下执行。


0

我会这样做

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

0

我曾经遇到过类似的问题。使用 size_t 没有起作用。我尝试了另一种方法,对我来说有效。(如下所示)

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接