Jak sobie radzić z ostrzeżeniami o „niezgodności podpisanych / niepodpisanych” (C4018)?


80

Pracuję z wieloma kodami obliczeniowymi napisanymi w C ++ z myślą o wysokiej wydajności i niskim zużyciu pamięci. Używa (głównie vector) kontenerów STL i iteruje je prawie w każdej funkcji.

Kod iteracyjny wygląda następująco:

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

ale generuje ostrzeżenie o niezgodności podpisanej / niepodpisanej (C4018 w programie Visual Studio).

Zastąpienie intjakimś unsignedtypem jest problemem, ponieważ często używamy pragm OpenMP i wymaga to, aby licznik był int.

Mam zamiar stłumić (setki) ostrzeżeń, ale obawiam się, że przegapiłem jakieś eleganckie rozwiązanie problemu.

Na iteratorach . Myślę, że iteratory są świetne, gdy są stosowane w odpowiednich miejscach. Kod, z którym pracuję, nigdy nie zmieni kontenerów o dostępie swobodnym na listlub coś (więc iteracja z użyciem int ijest już agnostyczna) i zawsze będzie potrzebować bieżącego indeksu. A cały dodatkowy kod, który trzeba wpisać (sam iterator i indeks) tylko komplikuje sprawę i zaciemnia prostotę kodu bazowego.


1
Czy możesz opublikować przykład, w którym pragma OpenMP uniemożliwia użycie typu bez znaku? W związku z tym powinien działać dla każdego typu międzygwiazdowego, nie tylko int.
Billy ONeal

4
Uważam, że to pytanie jest lepsze w przypadku przepełnienia stosu.
bcsanches

1
inti std::vector<T>::size_typemoże również różnić się rozmiarem i podpisem. Na przykład w systemie LLP64 (takim jak 64-bitowy system Windows), sizeof(int) == 4ale sizeof(std::vector<T>::size_type) == 8.
Adrian McCarthy


Odpowiedzi:


60

Wszystko jest w twoim things.size()typie. Nie jest int, ale size_t(istnieje w C ++, nie w C), co jest równe unsigned intpewnemu "zwykłemu" typowi bez znaku, np. Dla x86_32.

Operator „mniej” (<) nie może być zastosowany do dwóch operandów o różnych znakach. Po prostu nie ma takich rozkazów, a standard nie określa, czy kompilator może dokonać niejawnej konwersji znaku. Więc po prostu traktuje podpisany numer jako niepodpisany i wysyła to ostrzeżenie.

Byłoby dobrze napisać to w ten sposób

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

lub nawet szybciej

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

17
-1 nie, nie jest size_t. Tak jest std::vector< THING >::size_type.
Raedwald,

8
@Raedwald: Chociaż masz techniczną poprawność, trudno sobie wyobrazić, jak implementacja zgodna ze standardami może skończyć się z różnymi podstawowymi typami dla std::size_ti std::vector<T>::size_type.
Adrian McCarthy,

4
dlaczego ++ I jest uważany za lepszy? Czy w pętlach for nie ma różnicy „nie”?
Shoaib

2
@ShoaibHaider, nie ma to żadnego znaczenia dla prymitywów, w których wartość zwracana nie jest używana. Jednak w przypadku typów niestandardowych (gdzie operator jest przeciążony) inkrementacja postu jest prawie zawsze mniej wydajna (ponieważ musi wykonać kopię obiektu przed zwiększeniem). Kompilatory nie mogą (koniecznie) optymalizować pod kątem typów niestandardowych. Więc jedyną zaletą jest spójność (typów pierwotnych vs niestandardowych).
Kat.

2
@zenith: Tak, masz rację. Mój wyciąg dotyczy tylko domyślnego alokatora. Niestandardowy alokator może używać czegoś innego niż std :: size_t, ale uważam, że nadal musiałby to być typ całkowity bez znaku i prawdopodobnie nie mógłby reprezentować większego zakresu niż std :: size_t, więc nadal można bezpiecznie używać std :: size_t jako typ indeksu pętli.
Adrian McCarthy

13

Idealnie, zamiast tego użyłbym takiej konstrukcji:

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

Ma to tę zaletę, że kod nagle staje się agnostykiem kontenera.

A jeśli chodzi o twój problem, jeśli jakaś biblioteka, której używasz, wymaga, abyś używał inttam, gdzie unsigned intbyłoby lepiej, ich API jest brudne. W każdym razie, jeśli jesteś pewien, że intsą one zawsze pozytywne, możesz po prostu zrobić:

int int_distance = static_cast<int>(distance);

Który jasno określi twoje zamiary dla kompilatora: nie będzie już powodował błędów z ostrzeżeniami.


1
Ja zawsze potrzebny jest dystans. Może static_cast<int>(things.size())mogłyby to być rozwiązania, gdyby nie było innych.
Andrew T

@Andrew: Jeśli zdecydujesz się pominąć ostrzeżenie, najlepszym sposobem byłoby prawdopodobnie użycie pragmy specyficznej dla kompilatora (w MSVC będzie to a #pragma warning(push) #pragma warning(disable: 4018) /* ... function */ #pragma warning(pop)) zamiast użycia niepotrzebnego rzutowania. (Casts ukrywa uzasadnione błędy, m'kay?;))
Billy ONeal Kwietnia

Nie prawda. Cast! = Konwersja. Ostrzeżenie jest ostrzeżeniem o niejawnej konwersji dozwolonej przez standard, która może być niebezpieczna. Jednak obsada zaprasza na imprezę do jawnych nawróceń, co może być bardziej niebezpieczne niż to, o czym pierwotnie mówiono w ostrzeżeniu. Ściśle mówiąc, przynajmniej na x86, żadna konwersja w ogóle nie nastąpi - procesor nie dba o to, czy traktujesz określoną część pamięci jako podpisaną czy niepodpisaną, o ile używasz właściwych instrukcji do pracy z nią.
Billy ONeal

Kiedy bycie agnostykiem kontenerowym powoduje złożoność O (N ^ 2) (aw twoim przykładzie tak, ponieważ odległość () jest O (N) dla listy <>), nie jestem pewien, czy to zaleta :-(.
No-Bugs Hare

@ No-BugsHare O to właśnie chodzi: nie możemy być pewni. Jeśli OP ma kilka elementów, to prawdopodobnie jest świetny. Jeśli ma ich miliony, prawdopodobnie nie tak dużo. Tylko profilowanie może ostatecznie powiedzieć, ale dobra wiadomość jest taka: zawsze możesz zoptymalizować utrzymywany kod!
ereOn

9

Jeśli nie może / nie używać iteratorów a jeśli nie może / nie używać std::size_tdo indeksu pętli zrobić .size()do intfunkcji konwersji, że dokumenty założeniu i czy konwersja wyraźnie uciszyć ostrzeżenie kompilatora.

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

Następnie piszesz swoje pętle w ten sposób:

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

Instancja tego szablonu funkcji będzie prawie na pewno wbudowana. W kompilacjach debugowania założenie zostanie sprawdzone. W kompilacjach wydań nie będzie, a kod będzie tak szybki, jak w przypadku bezpośredniego wywołania size (). Żadna wersja nie wyświetli ostrzeżenia kompilatora i jest to tylko niewielka modyfikacja pętli idiomatycznej.

Jeśli chcesz wychwycić błędy założeń również w wydanej wersji, możesz zamienić potwierdzenie na instrukcję if, która generuje coś podobnego std::out_of_range("container size exceeds range of int").

Zauważ, że rozwiązuje to zarówno porównanie ze znakiem / bez znaku, jak i potencjalny problem sizeof(int)! = sizeof(Container::size_type). Możesz pozostawić wszystkie ostrzeżenia włączone i użyć ich do wyłapywania prawdziwych błędów w innych częściach kodu.


6

Możesz użyć:

  1. typ size_t, aby usunąć komunikaty ostrzegawcze
  2. iteratory + odległość (jak pierwsza wskazówka)
  3. tylko iteratory
  4. obiekt funkcji

Na przykład:

// 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

Mogę również zaproponować następujące rozwiązanie dla C ++ 11.

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

}

(C ++ nie jest wystarczająco inteligentny dla auto p = 0, więc muszę umieścić p = 0U ....)


1
+1 dla C ++ 11. Jeśli nie ma dobrego powodu, dla którego nie możesz używać C ++ 11, myślę, że najlepiej jest korzystać z nowych funkcji ... mają być bardzo pomocne. I zdecydowanie użyj, for (auto thing : vector_of_things)jeśli faktycznie nie potrzebujesz indeksu.
parker.sikand

Ale to rozwiązuje tylko problem podpisu. Nie pomaga, jeśli size()zwraca typ większy niż unsigned int, co jest niezwykle powszechne.
Adrian McCarthy

3

Dam ci lepszy pomysł

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

decltype jest

Sprawdza zadeklarowany typ jednostki lub typ i kategorię wartości wyrażenia.

Więc wydedukuje typ things.size()i ibędzie typem takim samym jak things.size(). Więc i < things.size()zostanie wykonany bez żadnego ostrzeżenia


0

Miałem podobny problem. Używanie size_t nie działało. Wypróbowałem inny, który działał dla mnie. (jak poniżej)

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

0

Po prostu bym to zrobił

int pnSize = primeNumber.size();
for (int i = 0; i < pnSize; i++)
    cout << primeNumber[i] << ' ';
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.