İmzalı / imzasız karşılaştırmalar


87

Aşağıdaki kodun belirtilen yerde neden uyarı vermediğini anlamaya çalışıyorum.

//from limits.h
#define UINT_MAX 0xffffffff /* maximum unsigned int value */
#define INT_MAX  2147483647 /* maximum (signed) int value */
            /* = 0x7fffffff */

int a = INT_MAX;
//_int64 a = INT_MAX; // makes all warnings go away
unsigned int b = UINT_MAX;
bool c = false;

if(a < b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a > b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a <= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a >= b) // warning C4018: '<' : signed/unsigned mismatch
    c = true;
if(a == b) // no warning <--- warning expected here
    c = true;
if(((unsigned int)a) == b) // no warning (as expected)
    c = true;
if(a == ((int)b)) // no warning (as expected)
    c = true;

Bunun arka plan tanıtımıyla ilgili olduğunu düşündüm, ancak son ikisi aksini söylüyor gibi görünüyor.

Bana göre, ilk ==karşılaştırma diğerleri kadar imzalı / imzasız bir uyumsuzluk mu?


3
gcc 4.4.2, '
-Wall

Bu bir spekülasyondur, ancak cevabı derleme zamanında bildiği için tüm karşılaştırmaları optimize edebilir.
Boş Set

2
Ah! yeniden. Bobah'ın yorumu: Tüm uyarıları açtım ve artık eksik uyarı görünüyor. Diğer karşılaştırmalarla aynı uyarı seviyesi ayarında görünmesi gerektiği kanısındayım.
Peter

1
@bobah: gcc 4.4.2'nin bu uyarıyı yazdırmasından gerçekten nefret ediyorum (ona yalnızca eşitsizlik için basmasını söylemenin bir yolu yok), çünkü bu uyarıyı susturmanın tüm yolları işleri daha da kötüleştiriyor . Varsayılan promosyon, hem -1 hem de ~ 0'ı herhangi bir işaretsiz türün olası en yüksek değerine güvenilir bir şekilde dönüştürür, ancak uyarıyı kendiniz yayınlayarak susturursanız, tam türü bilmeniz gerekir . Eğer türünü değiştirmek, yani çıplak ile karşılaştırmalar (uzun uzun imzasız söylemek uzatmak) -1ile karşılaştırmalar olacaktır hala iş (ancak bu vermek uyarısı) ise -1uveya (unsigned)-1her ikisi sefil başarısız olur.
Jan Hudec

Neden bir uyarıya ihtiyacın olduğunu ve neden derleyicilerin bunu çalıştıramadığını bilmiyorum. -1 negatiftir, yani işaretsiz sayılardan küçüktür. Basit.
CashCow

Yanıtlar:


96

İşaretli ile işaretsiz karşılaştırılırken, derleyici işaretli değeri işaretsiz olarak dönüştürür. Eşitlik için bu önemli değil -1 == (unsigned) -1. Bu konularda diğer karşılaştırmalar için aşağıdakileri örneğin doğrudur: -1 > 2U.

DÜZENLEME: Referanslar:

5/9: (İfadeler)

Aritmetik veya numaralandırma türünde işlenenler bekleyen birçok ikili işleç, dönüşümlere neden olur ve sonuç türlerini benzer şekilde verir. Amaç, aynı zamanda sonucun türü olan ortak bir tür elde etmektir. Bu model, aşağıdaki gibi tanımlanan normal aritmetik dönüşümler olarak adlandırılır:

  • İşlenenlerden biri long double türündeyse, diğeri long double türüne dönüştürülür.

  • Aksi takdirde, işlenenlerden biri double ise diğeri double'a dönüştürülür.

  • Aksi takdirde, işlenenlerden biri float ise diğeri float'a dönüştürülür.

  • Aksi takdirde, integral yükseltmeler (4.5) her iki işlenen üzerinde gerçekleştirilecektir. 54)

  • Daha sonra, işlenenlerden biri uzun süre işaretsiz ise, diğeri işaretsiz uzunluğa dönüştürülür.

  • Aksi takdirde, bir işlenen bir uzun int ve diğer işaretsiz int ise, o zaman bir uzun int, işaretsiz bir int'in tüm değerlerini temsil edebiliyorsa, işaretsiz int, bir long int'e dönüştürülür; aksi takdirde her iki işlenen de unsigned long int türüne dönüştürülür.

  • Aksi takdirde, işlenenlerden biri uzunsa, diğeri uzuna dönüştürülür.

  • Aksi takdirde, işlenenlerden biri işaretsiz ise, diğeri işaretsiz hale getirilecektir.

4.7 / 2: (İntegral dönüşümler)

Hedef tipi işaretsiz ise, sonuçta elde edilen değer kaynak tamsayı ile uyumlu en az işaretsiz tamsayıdır (modulo 2 n, burada n işaretsiz tipi temsil etmek için kullanılan bit sayısıdır). [Not: Bir ikinin tümleyen gösteriminde, bu dönüşüm kavramsaldır ve bit modelinde herhangi bir değişiklik olmaz (eğer kesilme yoksa). ]

EDIT2: MSVC uyarı seviyeleri

MSVC'nin farklı uyarı seviyelerinde uyarılan, elbette geliştiriciler tarafından yapılan seçimlerdir. Gördüğüm kadarıyla, imzalı / işaretsiz eşitliğe karşı büyük / az karşılaştırmalarıyla ilgili seçimleri anlamlı, bu elbette tamamen özneldir:

-1 == -1aynı anlama gelir -1 == (unsigned) -1- bunu sezgisel bir sonuç buluyorum.

-1 < 2 gelmez aynı anlama -1 < (unsigned) 2Bu ilk bakışta az sezgisel ve IMO bir "erken" uyarısı hak -.


İmzalıyı imzasız olarak nasıl dönüştürebilirsiniz? İşaretli -1 değerinin işaretsiz versiyonu nedir? (işaretli -1 = 1111, işaretsiz 15 = 1111, bit düzeyinde eşit olabilirler, ancak mantıksal olarak eşit değildirler.) Bu dönüşümü zorlarsanız işe yarayacağını anlıyorum, ama derleyici bunu neden yapsın? Mantıksız. Üstelik yukarıda da belirttiğim gibi uyarıları açtığımda eksik == uyarısı belirdi, bu söylediklerimi destekliyor gibi görünüyor?
Peter

1
4.7 / 2'nin dediği gibi, işaretsiz olarak işaretli, ikinin tamamlayıcısı için bit modelinde değişiklik olmadığı anlamına gelir. Derleyicinin bunu neden yaptığına gelince, C ++ standardı tarafından gereklidir. VS'nin farklı düzeylerdeki uyarılarının ardındaki mantığın, bir ifadenin kasıtsız olma olasılığı olduğuna inanıyorum - ve imzalı / işaretsiz eşitlik karşılaştırmasının eşitsizlik karşılaştırmalarına göre "daha az olası" bir sorun olduğu konusunda onlara katılıyorum. Bu elbette özneldir - bunlar VC derleyici geliştiricileri tarafından yapılan seçimlerdir.
Erik

Tamam, sanırım neredeyse anladım. Bunu okuma şeklim, derleyicinin (kavramsal olarak) şu şekilde yapmasıdır: 'if (((unsigned _int64) 0x7fffffff) == ((unsigned _int64) 0xffffffff))', çünkü _int64 hem 0x7fffffff hem de 0xffffffff'ı temsil edebilen en küçük türdür imzasız terimlerle mi?
Peter

2
Aslında karşılaştırarak (unsigned)-1veya -1usık sık kötü karşılaştırarak daha -1. Çünkü (unsigned __int64)-1 == -1, ama (unsigned __int64)-1 != (unsigned)-1. Dolayısıyla, derleyici bir uyarı verirse, imzasız olarak çevirerek veya kullanarak onu susturmaya çalışırsınız -1uve değer gerçekten 64 bit olursa veya daha sonra değiştirirseniz, kodunuzu kırarsınız! Ve bunun size_timzasız olduğunu , sadece 64-bit platformlarda 64-bit olduğunu ve geçersiz değer için -1 kullanmanın çok yaygın olduğunu unutmayın.
Jan Hudec

1
Belki de cpmpilers bunu yapmamalı. İşaretli ve işaretsizleri karşılaştırırsa, işaretli değerin negatif olup olmadığını kontrol edin. Öyleyse, imzasız olandan daha az olması garanti edilir.
CashCow

33

İmzalı / imzasız uyarıların neden önemli olduğu ve programcıların bunlara dikkat etmesi gerektiği aşağıdaki örnekle gösterilmiştir.

Bu kodun çıktısını tahmin edin?

#include <iostream>

int main() {
        int i = -1;
        unsigned int j = 1;
        if ( i < j ) 
            std::cout << " i is less than j";
        else
            std::cout << " i is greater than j";

        return 0;
}

Çıktı:

i is greater than j

Şaşırdın mı? Çevrimiçi Demo: http://www.ideone.com/5iCxY

Bottomline: tek işlenen ise karşılaştırıldığında, unsigneddaha sonra diğer işlenen örtülü dönüştürülür unsigned eğer onun tipi imzalanmış!


2
O haklı! Aptalca ama haklı. Bu, daha önce hiç karşılaşmadığım büyük bir sorun. Neden işaretsizleri (daha büyük) işaretli bir değere dönüştürmüyor ?! Eğer "if (i <((int) j))" yaparsanız, beklediğiniz gibi çalışır. "İf (i <((_int64) j))" daha mantıklı olsa da (yapamayacağınızı varsayarak, _int64'ün int boyutunun iki katı olduğunu varsayarsak).
Peter

6
@Peter "Neden ungiend'i (daha büyük) işaretli bir değere dönüştürmüyor?" Cevap basit: Daha büyük işaretli bir değer olmayabilir. 32 bitlik bir makinede, long long'dan önceki günlerde hem int hem de long 32 bitti ve daha büyük bir şey yoktu. İşaretli ve işaretsiz karşılaştırılırken, en eski C ++ derleyicileri her ikisini de işaretli olarak dönüştürdü. C standartları komitesinin nedenlerini unuttuğum için bunu değiştirdi. En iyi çözümünüz, mümkün olduğunca imzasız olmaktan kaçınmaktır.
James Kanze

5
@JamesKanze: İmzalı taşmanın sonucunun Tanımsız Davranış olduğu gerçeğiyle de bir şeyler yapması gerektiğinden şüpheleniyorum ve bu nedenle büyük işaretsiz değerin negatif işarete dönüştürülürken negatif işaretli değerin işaretsiz olarak dönüştürülmesi tanımlanıyor değer değildir .
Jan Hudec

2
@James Derleyici her zaman bu karşılaştırmanın daha sezgisel anlamlarını daha büyük bir türe dönüştürme yapmadan uygulayacak derleme üretebilir. Bu özel örnekte, önce olup olmadığını kontrol etmek yeterli olacaktır i<0. O zaman kesin olduğundan idaha küçük j. iSıfırdan küçük değilse , ìkarşılaştırmak için güvenli bir şekilde işaretsiz duruma dönüştürülebilir j. Elbette, işaretli ve işaretsiz arasındaki karşılaştırmalar daha yavaş olacaktır, ancak sonuçları bir anlamda daha doğru olacaktır.
Sven

@Sven katılıyorum. Standart, karşılaştırmaların iki türden birine dönüştürmek yerine tüm gerçek değerler için çalışmasını gerektirebilirdi. Ancak bu yalnızca karşılaştırmalar için işe yarar; Komitenin karşılaştırmalar ve diğer işlemler için farklı kurallar istemediğinden şüpheleniyorum (ve karşılaştırılan tür mevcut olmadığında karşılaştırma belirtme sorununa saldırmak istemedi).
James Kanze

4

== operatörü yalnızca bit düzeyinde bir karşılaştırma yapar (0 olup olmadığını görmek için basit bölme ile).

Karşılaştırmalardan daha küçük / büyük, sayının işaretine çok daha fazla bağlıdır.

4 bit Örnek:

1111 = 15? veya -1?

yani 1111 <0001'e sahipseniz ... belirsizdir ...

ama eğer 1111 == 1111'e sahipseniz ... Bunu kastetmemiş olmanıza rağmen aynı şeydir.


Bunu anlıyorum ama soruma cevap vermiyor. Sizin de işaret ettiğiniz gibi, 1111! = 1111 eğer işaretler uyuşmuyorsa. Derleyici, türlerden bir uyumsuzluk olduğunu biliyor, öyleyse neden bu konuda uyarmıyor? (Demek
Peter

Tasarlanma şekli bu. Eşitlik testi benzerliği kontrol eder. Ve benzer. Bu şekilde olmaması gerektiği konusunda sana katılıyorum. Bir makro veya x == y'yi aşırı yükleyen bir şey yapabilirsiniz! ((X <y) || (x> y))
Yochai Timmer

1

2-tamamlayıcı kullanan (çoğu modern işlemci) değerleri temsil eden bir sistemde, ikili biçimlerinde bile eşittirler. Derleyicinin a == b'den şikayet etmemesinin nedeni bu olabilir .

Ve bana göre tuhaf bir derleyici sizi a == ((int) b) konusunda uyarmıyor . Sanırım size tamsayı kesilme uyarısı falan vermeli.


1
C / C ++ 'ın felsefesi şudur: derleyici, geliştiricinin türler arasında açıkça dönüştürme yaparken ne yaptığını bildiğine güvenir. Bu nedenle, uyarı yok (en azından varsayılan olarak - uyarı seviyesi varsayılandan daha yüksek ayarlanmışsa bunun için uyarılar üreten derleyiciler olduğuna inanıyorum).
Péter Török

0

Söz konusu kod satırı bir C4018 uyarısı oluşturmaz çünkü Microsoft bu durumu işlemek için farklı bir uyarı numarası (örneğin, C4389 ) kullanmıştır ve C4389 varsayılan olarak etkinleştirilmemiştir (yani 3. seviyede).

C4389 için Microsoft dokümanlarından :

// C4389.cpp
// compile with: /W4
#pragma warning(default: 4389)

int main()
{
   int a = 9;
   unsigned int b = 10;
   if (a == b)   // C4389
      return 0;
   else
      return 0;
};

Diğer yanıtlar, Microsoft'un neden eşitlik operatöründen özel bir durum oluşturmaya karar verebileceğini oldukça iyi açıkladı, ancak bu yanıtların C4389'dan veya Visual Studio'da nasıl etkinleştirileceğinden .

C4389'u etkinleştirecekseniz, C4388'i de etkinleştirmeyi düşünebileceğinizi de belirtmeliyim. Maalesef C4388 için resmi bir belge yok ancak aşağıdaki gibi ifadelerde karşımıza çıkıyor:

int a = 9;
unsigned int b = 10;
bool equal = (a == b); // C4388
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.