C ve C ++ 'da işaretsiz tamsayıları kullanma


23

Uzun zamandır beni şaşırtan çok basit bir sorum var. Ağlar ve veritabanları ile uğraşıyorum, bu yüzden uğraştığım pek çok veri 32 bit ve 64 bit sayıcılar (işaretsiz), 32 bit ve 64 bit tanılama kimlikleridir (ayrıca işaret için anlamlı eşlemelere sahip değildir). Pratik olarak hiçbir zaman negatif sayı olarak ifade edilebilecek herhangi bir gerçek kelime sorunuyla ilgilenmiyorum.

Ben ve iş arkadaşlarım rutin olarak bu gibi uint32_tve gibi imzasız türleri kullanıyoruz ve uint64_tbu sık sık gerçekleştiği için bunları da dizi indeksleri ve diğer ortak tamsayılar için kullanıyoruz.

Aynı zamanda, okuduğum (örneğin Google) çeşitli kodlama kılavuzları, imzasız tamsayı türlerinin kullanımını engelliyor ve Java ya da Scala'nın imzasız tamsayı türleri olmadığını bildiğim kadarıyla.

Bu yüzden, yapılacak doğru şeyin ne olduğunu bulamadım: çevremizdeki işaretli değerleri kullanmak çok sakıncalı olurdu, aynı zamanda kodlama rehberleri de tam olarak bunu yapmak için ısrar ediyorlardı.


Yanıtlar:


31

Bu konuda iki düşünce okulu var ve ikisi de aynı fikirde değil.

İlki, dizi indeksleri gibi, doğası gereği imzasız olan bazı kavramlar olduğunu savunuyor. Hatalı olabileceği için imzalı sayıları kullanmanın bir anlamı yoktur. Ayrıca, nesnelere gereksiz sınırlamalar getirebilir - imzalı 32 bitlik dizinleri kullanan bir dizi yalnızca 2 milyar girişe erişebilirken, imzasız 32 bitlik sayılara geçiş yapmak 4 milyar girişe izin verir.

İkincisi, imzasız sayıları kullanan herhangi bir programda, er ya da geç karışık imzasız aritmetik işleminin biteceğini iddia eder. Bu garip ve beklenmedik sonuçlar verebilir: imzalı bir imzasız değerin yayınlanması negatif bir sayı verir ve tersine imzasız bir negatif sayı yazılması büyük bir pozitif değer verir. Bu büyük bir hata kaynağı olabilir.


8
Karma imzalı imzasız aritmetik konular derleyici tarafından algılanır; Yapınızı uyarıdan uzak tutun (yeterince yüksek bir uyarı seviyesiyle). Ayrıca, yazmak intiçin kısa :)
rucamzu

7
İtiraf: Ben ikinci düşünce okulundayım ve imzasız türlerle ilgili düşünceleri anlasam da: intdizi indeksleri için zamanın% 99,99'undan fazlası yeterli. İmzalı - imzasız aritmetik konular çok daha yaygındır ve bu nedenle neyin önleneceği konusunda öncelik kazanır. Evet, derleyiciler sizi bu konuda uyarır, fakat herhangi bir büyük projeyi derlerken ne kadar uyarı alırsınız? Uyarıları dikkate almamak tehlikeli ve kötü bir uygulamadır, ancak gerçek dünyada ...
Elias Van Ootegem

11
Cevabına +1. Dikkat : Önümüzdeki Künt Görüşler : 1: İkinci düşünce okuluna verdiğim cevabım şudur: C'deki işaretsiz integral türlerinden beklenmeyen sonuçlar elde eden herhangi birinin tanımsız davranışa sahip olacağına (yalnızca tamamen akademik olmayan) işaretli integral türlerini kullanan önemsiz olmayan C programları . İmzasız türlerin kullanmanın daha iyi olduğunu düşünecek kadar iyi C bilmiyorsanız, C'den kaçınmanızı öneririm. 2: C'deki dizi indeksleri ve boyutları için tam olarak doğru bir tür size_tvar. Aksi takdirde iyi bir sebep.
mtraceur

5
Karışık bir imza olmadan başını belaya sokarsın. Sadece imzasız int eksi imzasız int hesaplayın.
gnasher729

4
Sizinle sorun değil Simon, sadece “doğal olarak imzasız bazı kavramlar var - dizi indeksleri” olduğunu savunan ilk düşünce okulu ile . Özellikle: "Dizi dizinleri için tam olarak bir tane doğru tip var ... C," Saçma! . Biz DSP'ler her zaman negatif endeksleri kullanıyoruz. özellikle nedensel olmayan eşit veya tek simetri dürtü yanıtlarıyla. ve LUT matematik için. Ben ikinci düşünce okulundayım ama C ve C ++ ' da hem imzalanmış hem de imzasız tamsayıları kullanmanın faydalı olduğunu düşünüyorum .
robert bristow-johnson

21

Öncelikle, Google C ++ kodlama yönergesi izlemesi çok iyi bir şey değil: Modern C ++ 'nın zımbaları olan istisnalar, destek vb. İkincisi, X şirketinin belirli bir kılavuzun işe yaraması, sizin için uygun olacağı anlamına gelmez. İmzasız türleri kullanmaya devam edeceğim, çünkü sizin için iyi bir ihtiyaç var.

C ++ için iyi bir temel kural şudur: intBaşka bir şey kullanmak için iyi bir nedeniniz yoksa tercih edin .


8
Demek istediğim bu değil. Yapıcılar değişmezler kurmak içindir ve işlev olmadıkları için basitçe return falsedeğişmezler kurulmazsa yapamazlar. Böylece, nesneleri ayırabilir ve nesneleriniz için init işlevlerini kullanabilirsiniz ya da std::runtime_erroristifleme işleminin gerçekleşmesine izin verebilir ve tüm RAII nesnelerinizin kendilerini otomatik olarak temizlemesine izin verebilir ve geliştirici sizin için uygun olan istisnayı kaldırabilir. bunu yapman için.
bstamour

5
Uygulama türünün nasıl bir fark yarattığını anlamıyorum. Ne zaman bir nesneyle ilgili bir kurucu çağırırsanız, parametreleri içeren bir değişmez oluşturursunuz. Eğer bu değişmez karşılanamıyorsa, o zaman bir programın iyi durumda olmadığını gösteren bir hatayı işaret etmeniz gerekir. Yapıcılar bir bayrak döndüremediklerinden, bir istisna atmak doğal bir seçenektir. Lütfen bir iş başvurusunun böyle bir kodlama stilinden neden faydalanamayacağı konusunda sağlam bir argüman verin.
bstamour

8
Tüm C ++ programcılarının yarısının istisnaları düzgün kullanamadığından şüpheliyim. Ama yine de, iş arkadaşlarınızın modern C ++ yazma yeteneğine sahip olmadığını düşünüyorsanız, o zaman elbette modern C ++ 'dan uzak durun.
bstamour

6
@ zzz777 İstisnalar kullanılmaz mı? İstisnaları karşılayan ve geri dönüş yapan kamu fabrika fonksiyonları tarafından sarılmış özel kurucular var mı nullptr? "default" nesnesini döndür (bu ne anlama gelebilir)? Hiçbir şey çözmediniz - sorunu halının altına gizlediniz ve kimsenin anlamadığını umdunuz.
Mael

5
@ zzz777 Yine de kutuyu kilitleyecekseniz, neden bir istisnadan kaynaklandığını önemsiyorsunuz signal(6)? Bir istisna kullanırsanız, onlarla nasıl başa çıkacağını bilen geliştiricilerin% 50'si iyi kod yazabilir ve gerisi de akranları tarafından taşınabilir.
IllusiveBrian

6

Diğer cevaplar gerçek dünya örneklerinden yoksundur, ben de bir tane ekleyeceğim. Kişisel olarak imzasız türlerden kaçınmaya çalışmamın nedenlerinden biri.

Dizi dizini olarak standart size_t kullanmayı düşünün:

for (size_t i = 0; i < n; ++i)
    // do something here;

Tamam, tamamen normal. Ardından, bir nedenden dolayı döngünün yönünü değiştirmeye karar verdiğimizi düşünün:

for (size_t i = n - 1; i >= 0; --i)
    // do something here;

Ve şimdi çalışmıyor. Bir intyineleyici olarak kullanırsak, sorun olmazdı. Son iki yılda iki kez böyle bir hata gördüm. Bir zamanlar üretimde oldu ve hata ayıklamak zordu.

Benim için bir başka neden de, sizi her seferinde böyle bir şey yazmanızı sağlayan rahatsız edici uyarılar :

int n = 123;  // for some reason n is signed
...
for (size_t i = 0; i < size_t(n); ++i)

Bunlar küçük şeyler, ama toparlar. Her yerde yalnızca işaretli tamsayılar kullanılıyorsa kodun daha temiz olduğunu hissediyorum.

Düzenleme: Tabii, örnekler aptal görünüyor, ama insanların bu hatayı yaptığını gördüm. Bunu önlemek için bu kadar kolay bir yol varsa, neden kullanmıyorsunuz?

Aşağıdaki kod parçasını VS2015 veya GCC ile derlerken, varsayılan uyarı ayarlarıyla ilgili hiçbir uyarı göremiyorum (GCC için -Wall ile bile). -Wextra'dan GCC'de bununla ilgili bir uyarı almasını istemek zorundasın. Bu, her zaman Wall ve Wextra ile derlemenizin (ve statik analizörü kullanmanız) nedenlerinden biridir, ancak birçok gerçek yaşam projesinde insanlar bunu yapmaz.

#include <vector>
#include <iostream>


void unsignedTest()
{
    std::vector<int> v{ 1, 2 };

    for (int i = v.size() - 1; i >= 0; --i)
        std::cout << v[i] << std::endl;

    for (size_t i = v.size() - 1; i >= 0; --i)
        std::cout << v[i] << std::endl;
}

int main()
{
    unsignedTest();
    return 0;
}

İmzalı tiplerle daha da yanlış anlayabilirsiniz ... Ve örnek kodunuz o kadar beyin ölmüş ve göze batan bir şekilde yanlış. Herhangi bir düzgün derleyici uyarılar isterseniz sizi uyaracaktır.
Deduplicator

1
Geçmişte for (size_t i = n - 1; i < n; --i), doğru çalışması için bu tür korkulara başvurdum .
Simon B

2
For- size_tfor (size_t revind = 0u; revind < n; ++revind) { size_t ind = n - 1u - revind; func(ind); }
loop'ların

2
@ rwong Omg, bu çirkin. Neden sadece kullanmıyorsun int? :)
Aleksei Petrenko

1
@AlexeyPetrenko - Mevcut C veya C ++ standartlarının inttüm geçerli değerlerini tutacak kadar büyük garanti etmediğine dikkat edin size_t. Özellikle, intyalnızca 2 ^ 15-1'e kadar sayılara izin verebilir ve genellikle bunu 2 ^ 16 (veya bazı durumlarda daha yüksek) bellek ayırma sınırları olan sistemlerde yapar. Çalışması garantilong edilmese de, daha güvenli bir bahis olabilir . Yalnızca tüm platformlarda ve her durumda çalışması garanti edilir. size_t
Jules

4
for (size_t i = v.size() - 1; i >= 0; --i)
   std::cout << v[i] << std::endl;

Buradaki problem, döngüyü hatalı bir davranışa yol açan net olmayan bir şekilde yazmanızdır. Döngünün yapısı yeni başlayanlar için imzalı tipler için öğretilir (tamam ve doğru) ancak imzasız değerlere uymaz. Ancak bu, imzasız türlerin kullanılmasına karşı bir karşı argüman işlevi göremez, buradaki görev sizin döngüsünüzü doğru yapmaktır. Ve bu gibi imzasız türleri için güvenilir bir şekilde çalışmak için kolayca tespit edilebilir:

for (size_t i = v.size(); i-- > 0; )
    std::cout << v[i] << std::endl;

Bu değişiklik basitçe karşılaştırmanın sırasını ve azaltma işlemini geri döndürür ve bence imzasız sayaçları geri çevrimlerde ele almanın en etkili, rahatsız edici, temiz ve kısa yoludur. Bir süre döngü kullanırken aynı şeyi (sezgisel) yaparsınız:

size_t i = v.size();
while (i > 0)
{
    --i;
    std::cout << v[i] << std::endl;
}

Hiçbir alt akış meydana gelmez, boş bir kabın durumu, imzalı sayaç ilmeği için iyi bilinen bir varyantta olduğu gibi dolaylı olarak kaplanır ve ilmeğin gövdesi, imzalı bir sayaç veya ileri ilmeğe kıyasla değişmeden kalabilir. Sadece ilk başta biraz garip görünen döngü yapısına alışmak zorundasınız. Ama bir düzine kez gördükten sonra, artık anlaşılmaz bir şey yok.

Yeni başlayanlar için kurslar yalnızca imzalı tipler için değil, imzalı türler için de doğru döngüyü gösterirse şanslı olurum. Bu, IMHO'nun imzasız türü suçlamak yerine isteksiz geliştiricilere suçlanması gereken birkaç hatadan kaçınacaktır.

HTH


1

İmzasız tamsayıların bir nedeni var.

Örneğin, verileri bir bayt olarak, örneğin bir ağ paketinde veya bir dosya arabelleğinde göndermeyi düşünün. Zaman zaman 24-bit tamsayılar gibi canavarlarla karşılaşabilirsiniz. 8 bit işaretli tamsayılardan kolayca bit kaydırılır, 8 bit işaretli tamsayılarla kolay değildir.

Veya karakter arama tablolarını kullanarak algoritmaları düşünün. Bir karakter 8 bit işaretsiz tamsayıysa, arama tablosunu bir karakter değerine göre indeksleyebilirsiniz. Ancak, programlama dili işaretsiz tamsayıları desteklemiyorsa ne yaparsınız? Bir dizi için negatif dizinler olurdu. Sanırım böyle bir şey kullanabilirsiniz charval + 128ama bu çok çirkin.

Aslında birçok dosya formatı işaretsiz tamsayılar kullanıyor ve uygulama programlama dili işaretsiz tamsayıları desteklemiyorsa, bu bir sorun olabilir.

Ardından TCP sıra numaralarını göz önünde bulundurun. Herhangi bir TCP işlem kodu yazarsanız, kesinlikle işaretsiz tamsayıları kullanmak isteyeceksiniz.

Bazen, verimlilik o kadar önemlidir ki, bu işaretsiz tam sayıların gerçekten bir kısmına ihtiyacınız vardır. Milyonlarca gönderilen IoT cihazlarını düşünün. Mikro optimizasyonlara harcanması için birçok programlama kaynağı doğrulanabilir.

İmzasız tamsayı tiplerini (karışık işaret aritmetik, karışık işaret karşılaştırmaları) kullanmaktan kaçınmanın gerekçesinin, uygun uyarıları olan bir derleyici tarafından üstesinden gelinebileceğini savunuyorum. Bu tür uyarılar genellikle varsayılan olarak etkin değildir, ancak örneğin -Wextraveya ayrı olarak -Wsign-compare( -WextraC ++ 'da otomatik olarak etkinleştirildiğini düşünmeme rağmen C tarafından otomatik olarak etkinleştirilir) bakın -Wsign-conversion.

Bununla birlikte, şüpheniz varsa imzalı bir tür kullanın. Çoğu zaman, iyi çalışan bir seçimdir. Ve bu derleyici uyarılarını etkinleştirin!


0

Tam sayıların gerçekte sayıları temsil etmediği birçok durum vardır, ancak örneğin bir bit maskesi, bir kimlik vb. Temel olarak bir tam sayıya 1 eklemenin anlamlı bir sonucu olmadığı durumlar. Bu gibi durumlarda, imzasız kullanın.

Tamsayılı aritmetik yaptığınız birçok durum vardır. Bu durumlarda, sıfıra yakın hatalı davranmayı önlemek için işaretli tamsayıları kullanın. Döngülerin sıfıra indirilmesi ya çok sezgisel olmayan kodlar kullandığı ya da işaretsiz sayıların kullanılması nedeniyle kesildiği durumlarda döngülerle ilgili çok sayıda örnek görün. "Ama endeksler asla negatif değil" argümanı var - elbette, ancak endekslerin farklılıkları negatif.

Endekslerin 2 ^ 31'i geçtiği ancak 2 ^ 32'yi aşmadığı çok nadir durumlarda, işaretsiz tamsayıları kullanmazsanız, 64 bit tamsayıları kullanırsınız.

Son olarak, güzel bir tuzak: "(i = 0; i <n; ++ i) a [i] ..." döngüsünde, eğer imzasız 32 bitseniz ve bellek 32 bit adresleri aşıyorsa, derleyici en iyi duruma getiremez işaretleyiciyi artırarak [i] 'ye erişim, çünkü i = 2 ^ 32 - 1' i sarar. N hiç bu kadar büyürken bile. İşaretli tamsayıları kullanmak bunu önler.


-5

Sonunda, burada gerçekten iyi bir cevap buldum: J.Viega ve M.Messier tarafından "Güvenli Programlama Yemek Kitabı" ( http://shop.oreilly.com/product/9780596003944.do )

İmzalı tamsayılarla ilgili güvenlik sorunları:

  1. İşlev pozitif bir parametre gerektiriyorsa, alt aralığı kontrol etmeyi unutmak kolaydır.
  2. Negatif tamsayı boyutlu dönüşümlerden sezgisel olmayan bit deseni.
  3. Negatif bir tamsayı sağa kaydırma işlemi tarafından üretilen sezgisel olmayan bit desen.

İmzalı <-> imzasız dönüşümlerle ilgili sorunlar var bu nedenle karışım kullanmanız önerilmez.


1
Neden iyi bir cevap? Reçete 3.5 nedir? Tamsayı taşması vb. Hakkında ne diyor?
Baldrickk

Pratik deneyimime göre, denedim ve her yönüyle değerli tavsiyelerle çok iyi bir kitap ve bu tavsiyede oldukça sağlam. 4G'den daha uzun dizilerde tamsayı taşma tehlikeleri ile karşılaştırıldığında oldukça zayıf görünüyor. Bu kadar büyük dizilerle uğraşmak zorunda kalırsam, performans cezalarını önlemek için programımın çok fazla hassas ayarları olacak.
zzz777

1
bu kitabın iyi olup olmadığıyla ilgili değil. Cevabınız, reçetenin kullanımına ilişkin herhangi bir gerekçe sağlamamaktadır ve herkesin bakması için kitabın bir kopyası bulunmayacaktır. İyi bir cevap yazma örneklerine bakın
Baldrickk

FYI henüz imzasız tamsayı kullanmanın başka bir nedenini öğrendi: biri aşırı gerilimi kolayca tespit edebilir: youtube.com/…
zzz777
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.