如何处理“有符号/无符号不匹配”警告(C4018)?


80

我使用大量用C ++编写的计算代码,同时考虑了高性能和低内存开销。它大量使用STL容器(大部分是vector),并且几乎在每个函数中都对该容器进行迭代。

迭代代码如下所示:

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

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

int用某种unsigned类型替换是一个问题,因为我们经常使用OpenMP编译指示,并且要求计数器为int

我将禁止显示(数百个)警告,但是恐怕我错过了一些优雅的解决方案。

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


1
您是否可以发布一个示例,其中OpenMP编译指示阻止您使用无符号类型?根据应该适用于任何类型的intergal,而不仅仅是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


Answers:


60

这都是您的things.size()类型。它不是int,但是size_t(它存在于C ++中,而不存在于C中)等于一个“常规”无符号类型,即unsigned intx86_32。

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

像这样写是正确的

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

甚至更快

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

17
-1不,不是size_t。它是std::vector< THING >::size_type
Raedwald

8
@Raedwald:当你在技术上是正确的,这是很难想象如何符合标准的实施可以与不同的基本类型结束了std::size_tstd::vector<T>::size_type
Adrian McCarthy 2013年

4
为什么++ i被认为更好?在for循环中没有“没有”区别吗?
Shoaib 2014年

2
@ShoaibHaider,对于不使用返回值的原语来说,这根本没有关系。但是,对于自定义类型(运算符很重载),后增量几乎总是效率较低(因为它必须在增加对象之前先对其进行复制)。编译器无法(必需)针对自定义类型进行优化。因此,唯一的优势是(原语与自定义类型的)一致性。
2014年

2
@zenith:是的,你是对的。我的语句仅适用于默认分配器。自定义分配器可能使用的不是std :: size_t,但我认为它仍然必须是无符号整数类型,并且它可能不能代表比std :: size_t大的范围,因此使用std仍然是安全的:: size_t作为循环索引的类型。
Adrian McCarthy

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);
}

这有一个整洁的优点,您的代码突然变得与容器无关。

关于您的问题,如果您使用的某些库要求您intunsigned int更适合的位置使用,则它们的API很乱。无论如何,如果您确定那些int总是正面的,则可以这样做:

int int_distance = static_cast<int>(distance);

它将清楚地表明您对编译器的意图:它不会再出现警告错误。


1
总是需要距离。static_cast<int>(things.size())如果没有其他解决方案,也许是解决方案。
Andrew T

@Andrew:如果您决定取消该警告,则最佳方法可能是使用编译器特定的编译指示(在MSVC上为#pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)),而不是使用不必要的强制转换。(广播隐藏合法错误,对吗?;))
Billy ONeal

不对。强制转换!该警告是关于标准所允许的隐式转换的警告,这可能是不安全的。但是,强制转换会导致当事人进行明确的conversion依,这比警告最初说的要更加不安全。严格来讲,至少在x86上,根本不会进行任何转换-处理器并不关心您是否将内存的特定部分视为带符号的还是无符号的,只要您使用正确的指令进行处理即可。
比利·奥尼尔

当与容器无关时,会导致O(N ^ 2)的复杂性(在您的示例中,确实如此,因为list <>的distance()为O(N)),我不确定这是不是一个优点:
No-Bugs野兔

@ No-BugsHare这就是重点:我们不确定。如果OP的元素很少,那可能很棒。如果他有数以百万计的人,可能就不那么多了。最后只有分析可以说明问题,但好消息是:您始终可以优化可维护的代码!
ereOn '18

9

如果您不能/不使用迭代器,并且不能/不使用std::size_t循环索引,请使用.size()toint转换函数来记录假设并明确进行转换以使编译器警告消失。

#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()一样快。这两个版本都不会产生编译器警告,并且只是对惯用循环的轻微修改。

如果您还想在发行版中捕获假设失败,则可以用引发类似的if语句替换断言std::out_of_range("container size exceeds range of int")

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


6

您可以使用:

  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());
}

3

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

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

}

(对于自动p = 0,C ++不够智能,因此我必须输入p = 0U...。)


1
C ++ 11为+1。除非有充分的理由不能使用C ++ 11,否则我认为最好使用这些新功能……它们对您有很大的帮助。for (auto thing : vector_of_things)如果您确实不需要索引,则可以使用。
parker.sikand

但这只能解决签名问题。如果size()返回的类型大于unsigned int(这是非常常见的),则无济于事。
Adrian McCarthy

3

我会给你一个更好的主意

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

decltype

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

因此,它演绎的类型things.size()i将是一个类型相同things.size()。因此, i < things.size()将在没有任何警告的情况下执行


0

我有一个类似的问题。使用size_t无法正常工作。我尝试了另一个对我有用的东西。(如下)

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

0

我会做

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.