Wie gehe ich mit Warnungen vor "signierter / nicht signierter Nichtübereinstimmung" um (C4018)?

80

Ich arbeite mit viel in C ++ geschriebenem Berechnungscode mit Blick auf hohe Leistung und geringen Speicheraufwand. Es verwendet (meistens vector) häufig STL-Container und iteriert fast in jeder einzelnen Funktion über diese Container.

Der iterierende Code sieht folgendermaßen aus:

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

Es wird jedoch die vorzeichenlose / vorzeichenlose Nichtübereinstimmungswarnung ausgegeben (C4018 in Visual Studio).

Das Ersetzen intdurch einen unsignedTyp ist ein Problem, da wir häufig OpenMP-Pragmas verwenden und der Zähler erforderlich ist int.

Ich bin dabei, die (Hunderte von) Warnungen zu unterdrücken, aber ich fürchte, ich habe eine elegante Lösung für das Problem verpasst.

Auf Iteratoren . Ich denke, Iteratoren sind großartig, wenn sie an geeigneten Stellen angewendet werden. Der Code, mit dem ich arbeite, ändert niemals Container mit wahlfreiem Zugriff in listoder so etwas (das Iterieren mit int iist also bereits containerunabhängig) und benötigt immer den aktuellen Index. Und all der zusätzliche Code, den Sie eingeben müssen (Iterator selbst und der Index), macht die Sache nur komplizierter und verschleiert die Einfachheit des zugrunde liegenden Codes.

Andrew T.
quelle
1
Können Sie ein Beispiel veröffentlichen, in dem das OpenMP-Pragma die Verwendung eines vorzeichenlosen Typs verhindert? Laut diesem soll es für jeden intergal Typen arbeitet, nicht nur int.
Billy ONeal
4
Ich glaube, diese Frage ist besser für den Stackoverflow.
bcsanches
1
intund std::vector<T>::size_typekann auch in der Größe sowie in der Signiertheit unterschiedlich sein. Zum Beispiel auf einem LLP64-System (wie 64-Bit-Windows), sizeof(int) == 4aber sizeof(std::vector<T>::size_type) == 8.
Adrian McCarthy
Mögliches Duplikat von stackoverflow.com/questions/8188401/…
CinCout

Antworten:

60

Es ist alles in Ihrem things.size()Typ. Es ist nicht int, aber size_t(es existiert in C ++, nicht in C), was einem "üblichen" vorzeichenlosen Typ entspricht, dh unsigned intfür x86_32.

Der Operator "less" (<) kann nicht auf zwei Operanden mit unterschiedlichem Vorzeichen angewendet werden. Es gibt einfach keine solchen Opcodes und Standard gibt nicht an, ob der Compiler eine implizite Vorzeichenkonvertierung durchführen kann. Es behandelt also nur die signierte Nummer als nicht signiert und gibt diese Warnung aus.

Es wäre richtig, es so zu schreiben

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

oder noch schneller

for (size_t i = 0, ilen = things.size(); i < ilen; ++i) { /**/ }
Gigala
quelle
17
-1 nein, ist es nicht size_t. Es ist std::vector< THING >::size_type.
Raedwald
8
@Raedwald: Obwohl Sie technisch korrekt sind, ist es schwer vorstellbar, wie eine standardkonforme Implementierung zu unterschiedlichen zugrunde liegenden Typen für std::size_tund führen könnte std::vector<T>::size_type.
Adrian McCarthy
4
Warum wird ++ ich als besser angesehen? In for-Schleifen gibt es keinen "keinen" Unterschied?
Shoaib
2
@ShoaibHaider, es spielt überhaupt keine Rolle für Grundelemente, bei denen der Rückgabewert nicht verwendet wird. Bei benutzerdefinierten Typen (bei denen der Operator überladen ist) ist das Nachinkrementieren jedoch fast immer weniger effizient (da vor dem Inkrementieren eine Kopie des Objekts erstellt werden muss). Compiler können (nicht unbedingt) für benutzerdefinierte Typen optimieren. Der einzige Vorteil ist also die Konsistenz (von Grundelementen gegenüber benutzerdefinierten Typen).
Kat
2
@zenith: Ja, du hast recht. Meine Aussage gilt nur für den Standard-Allokator. Ein benutzerdefinierter Allokator könnte etwas anderes als std :: size_t verwenden, aber ich glaube, es müsste immer noch ein vorzeichenloser Integraltyp sein, und er könnte wahrscheinlich keinen größeren Bereich als std :: size_t darstellen, daher ist es immer noch sicher, std zu verwenden :: size_t als Typ für einen Schleifenindex.
Adrian McCarthy
13

Idealerweise würde ich stattdessen ein solches Konstrukt verwenden:

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

Dies hat den netten Vorteil, dass Ihr Code plötzlich containerunabhängig wird.

Und in Bezug auf Ihr Problem ist die API unordentlich , wenn für eine von Ihnen verwendete Bibliothek eine Verwendung erforderlich ist, intbei der eine unsigned intbesser passt. Wenn Sie sicher sind, dass diese intimmer positiv sind, können Sie Folgendes tun:

int int_distance = static_cast<int>(distance);

Dadurch wird Ihre Absicht gegenüber dem Compiler klar angegeben: Sie werden nicht mehr mit Warnungen belästigt.

ereOn
quelle
1
Ich brauche immer die Entfernung. Vielleicht static_cast<int>(things.size())könnten die Lösungen sein, wenn es keine anderen gibt.
Andrew T
@ Andrew: Wenn Sie sich entscheiden, die Warnung zu unterdrücken, ist es wahrscheinlich am besten, ein compilerspezifisches Pragma zu verwenden (bei MSVC ist dies a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)), anstatt eine unnötige Besetzung zu verwenden. (Casts verstecken legitime Fehler, m'kay ?;))
Billy ONeal
Nicht wahr. Besetzung! = Umwandlung. Die Warnung warnt vor einer impliziten Konvertierung, die vom Standard zugelassen wird und möglicherweise unsicher ist. Eine Besetzung lädt jedoch explizite Konvertierungen zur Partei ein, die unsicherer sein können als das, worüber ursprünglich in der Warnung gesprochen wurde. Genau genommen wird zumindest auf x86 überhaupt keine Konvertierung stattfinden - dem Prozessor ist es egal, ob Sie einen bestimmten Teil des Speichers als signiert oder nicht signiert behandeln, solange Sie die richtigen Anweisungen verwenden, um damit zu arbeiten.
Billy ONeal
Wenn Container-Agnostik die Komplexität von O (N ^ 2) verursacht (und in Ihrem Beispiel, da distance () für Liste <> O (N) ist), bin ich mir nicht sicher, ob dies ein Vorteil ist :-(.
No-Bugs Hare
@ No-BugsHare Das ist genau der Punkt: Wir können nicht sicher sein. Wenn das OP nur wenige Elemente enthält, ist es wahrscheinlich großartig. Wenn er Millionen davon hat, wahrscheinlich nicht so sehr. Nur die Profilerstellung kann am Ende sagen, aber die gute Nachricht ist: Sie können den wartbaren Code immer optimieren!
Am
9

Wenn Sie keine Iteratoren verwenden können / wollen und wenn Sie std::size_tden Schleifenindex nicht verwenden können / wollen, erstellen Sie eine .size()to- intKonvertierungsfunktion, die die Annahme dokumentiert und die Konvertierung explizit ausführt, um die Compiler-Warnung auszuschalten.

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

Dann schreiben Sie Ihre Loops wie folgt:

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

Die Instanziierung dieser Funktionsvorlage wird mit ziemlicher Sicherheit inline sein. In Debug-Builds wird die Annahme überprüft. In Release-Builds ist dies nicht der Fall und der Code ist so schnell, als hätten Sie size () direkt aufgerufen. Keine der beiden Versionen erzeugt eine Compiler-Warnung und es handelt sich nur um eine geringfügige Änderung der idiomatischen Schleife.

Wenn Sie Vermutungsfehler auch in der Release-Version abfangen möchten, können Sie die Behauptung durch eine if-Anweisung ersetzen, die so etwas wie auslöst std::out_of_range("container size exceeds range of int").

Beachten Sie, dass dies sowohl den vorzeichenbehafteten / vorzeichenlosen Vergleich als auch das potenzielle sizeof(int)! = sizeof(Container::size_type)Problem löst . Sie können alle Ihre Warnungen aktiviert lassen und sie verwenden, um echte Fehler in anderen Teilen Ihres Codes zu erkennen.

Adrian McCarthy
quelle
6

Sie können verwenden:

  1. size_t Typ, um Warnmeldungen zu entfernen
  2. Iteratoren + Abstand (wie sind erster Hinweis)
  3. nur Iteratoren
  4. Funktionsobjekt

Zum Beispiel:

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

quelle
3

Ich kann auch folgende Lösung für C ++ 11 vorschlagen.

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

}

(C ++ ist nicht klug genug für Auto p = 0, also muss ich p = 0U setzen ....)

Stepan Yakovenko
quelle
1
+1 für C ++ 11. Sofern es keinen guten Grund gibt, warum Sie C ++ 11 nicht verwenden können, ist es meiner Meinung nach am besten, die neuen Funktionen zu verwenden. Sie sollen eine große Hilfe sein. Und auf jeden Fall verwenden, for (auto thing : vector_of_things)wenn Sie den Index nicht wirklich benötigen.
parker.sikand
Damit ist jedoch nur das Signaturproblem gelöst. Es hilft nicht, wenn size()ein Typ zurückgegeben wird, der größer als int ohne Vorzeichen ist, was sehr häufig vorkommt.
Adrian McCarthy
3

Ich werde Ihnen eine bessere Idee geben

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

decltype ist

Untersucht den deklarierten Typ einer Entität oder die Typ- und Wertekategorie eines Ausdrucks.

Es leitet also den Typ von ab things.size()und iwird ein Typ sein, der dem von entspricht things.size(). So i < things.size()wird ausgeführt, ohne Vorwarnung

Daniel Kim
quelle
0

Ich hatte ein ähnliches Problem. Die Verwendung von size_t funktionierte nicht. Ich habe den anderen ausprobiert, der für mich funktioniert hat. (wie nachstehend)

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

Ich würde es einfach tun

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