boyutlar, dizin vb. için size_t veya int


15

C ++ 'da, size_t(veya daha doğru bir şekilde T::size_type"genellikle" olan size_t; yani, bir unsignedtür) , vb. size()Argümanının dönüş değeri olarak kullanılır .operator[]std::vector

Öte yandan, .NET dilleri aynı amaç için kullanır int(ve isteğe bağlı olarak long); aslında, imzasız türleri desteklemek için CLS uyumlu diller gerekli değildir .

.NET'in C ++ 'dan daha yeni olduğu göz önüne alındığında, bir şey bana bir dizi dizini veya uzunluğu gibi "muhtemelen" negatif olamaz şeyler için bile kullanma sorunları olduğunu söylüyor unsigned int. C ++ yaklaşımı geriye dönük uyumluluk için "tarihsel eser" midir? Yoksa iki yaklaşım arasında gerçek ve önemli tasarım ödünleşimleri var mı?

Bu neden önemli? Şey ... C ++ 'da yeni bir çok boyutlu sınıf için ne kullanmalıyım; size_tveya int?

struct Foo final // e.g., image, matrix, etc.
{
    typedef int32_t /* or int64_t*/ dimension_type; // *OR* always "size_t" ?
    typedef size_t size_type; // c.f., std::vector<>

    dimension_type bar_; // maybe rows, or x
    dimension_type baz_; // e.g., columns, or y

    size_type size() const { ... } // STL-like interface
};

6
Dikkat çekmeye değer: .NET Framework'ün çeşitli yerlerinde, -1"bulunamadı" veya "aralık dışında" belirtmek için bir dizin döndüren işlevlerden döndürülür. Ayrıca Compare()işlevlerden döndürülür (uygulama IComparable). 32 bit int, genel bir sayı için türe gitme olarak kabul edilir, umarım açık nedenlerdir.
Robert Harvey

Yanıtlar:


9

.NET C ++ 'dan daha yeni olduğu göz önüne alındığında, bir şey bana bir dizi dizini veya uzunluğu gibi negatif olamaz "şeyler" için bile imzasız int kullanarak sorunlar olabileceğini söylüyor.

Evet. Görüntü işleme veya dizi işleme gibi belirli uygulama türleri için, öğelere geçerli konuma göre erişmek genellikle gereklidir:

sum = data[k - 2] + data[k - 1] + data[k] + data[k + 1] + ...

Bu tür uygulamalarda, dikkatlice düşünmeden işaretsiz tamsayılarla aralık kontrolü gerçekleştiremezsiniz:

if (k - 2 < 0) {
    throw std::out_of_range("will never be thrown"); 
}

if (k < 2) {
    throw std::out_of_range("will be thrown"); 
}

if (k < 2uL) {
    throw std::out_of_range("will be thrown, without signedness ambiguity"); 
}

Bunun yerine aralık denetimi ifadenizi yeniden düzenlemelisiniz . Temel fark budur. Programcılar ayrıca tamsayı dönüştürme kurallarını da hatırlamalıdır. Şüphe duyduğunuzda, http://en.cppreference.com/w/cpp/language/operator_arithmetic#Conversions sayfasını tekrar okuyun.

Birçok uygulamanın çok büyük dizi indeksleri kullanmasına gerek yoktur, ancak aralık kontrolleri gerçekleştirmeleri gerekir. Ayrıca, birçok programcı bu ifade yeniden düzenleme jimnastiği yapmak için eğitilmemiştir. Kaçırılan tek bir fırsat bir sömürüye kapı açar.

C # gerçekten dizi başına 2 ^ 31 öğeye ihtiyaç duymayacak uygulamalar için tasarlanmıştır. Örneğin, bir e-tablo uygulamasının bu kadar satır, sütun veya hücre ile uğraşmasına gerek yoktur. C # derleyici seçenekleri ile uğraşmadan bir anahtar kelime ile kod bloğu için etkinleştirilebilir isteğe bağlı kontrol edilmiş aritmetik üst sınırı ile ilgilenir . Bu nedenle, C # imzalı tamsayı kullanımını destekler. Bu kararlar birlikte değerlendirildiğinde, mantıklıdır.

C ++ basitçe farklıdır ve doğru kodu almak daha zordur.

İmzalı aritmetiğin "en az şaşkınlık ilkesinin" potansiyel ihlallerini ortadan kaldırmasına izin vermenin pratik önemi ile ilgili olarak, bir nokta, matris öğesi dizini, dizi boyutu, piksel kanalı sayısı vb. İçin imzalı 32 bit tam sayı kullanan OpenCV'dir. işleme, göreli dizi dizinini yoğun şekilde kullanan bir programlama etki alanı örneğidir. İmzasız tam sayı alt akışı (etrafına sarılan negatif sonuç) algoritma uygulamasını ciddi şekilde zorlaştıracaktır.


Bu benim durumum; özel örnekler için teşekkürler. (Evet, bunu biliyorum, ancak alıntı yapmak için "daha yüksek makamlara" sahip olmak yararlı olabilir.)
Aralık'ta

1
@ Dan: bir şeyden alıntı yapmanız gerekiyorsa, bu yazı daha iyi olurdu.
rwong

1
@ Dan: John Regehr bu sorunu programlama dillerinde aktif olarak araştırıyor. Bkz. Blog.regehr.org/archives/1401
rwong


14

Bu cevap gerçekten kodunuzu kimlerin kullanacağına ve hangi standartları görmek istediklerine bağlıdır.

size_t amacı olan bir tamsayı boyutudur:

Tür size_t, herhangi bir nesnenin bayt cinsinden boyutu içerebilecek kadar büyük olan, uygulama tanımlı, imzasız bir tamsayı türüdür. (C ++ 11 spesifikasyonu 18.2.6)

Bu nedenle, bayt cinsinden nesnelerin boyutu ile çalışmak istediğinizde , kullanmalısınız size_t. Şimdi birçok durumda, bayt saymak için bu boyutları / dizinleri kullanmıyorsunuz, ancak çoğu geliştirici size_ttutarlılık için orada kullanmayı seçiyor .

Sınıfınızın bir STL sınıfının görünümü ve hissine sahip olması gerekiyorsa daima kullanmanız gerektiğini unutmayın size_t. Spesifikasyondaki tüm STL sınıfları kullanılır size_t. Derleyicinin typedef size_tolması için unsigned intgeçerlidir ve typedef'in olması da geçerlidir unsigned long. Doğrudan intveya longdoğrudan kullanırsanız , sonunda sınıfınızı takip eden bir kişinin STL'nin stilini takip ettiğini düşünen derleyicilere koşacaksınız çünkü standardı takip etmediniz.

İmzalı türleri kullanmaya gelince, birkaç avantajı vardır:

  • Daha kısa adlar - insanların yazması gerçekten kolaydır int, ancak kodu karıştırmak çok daha zordur unsigned int.
  • Her boyut için bir tam sayı - 32 bitlik tek bir CLS uyumlu tam sayı vardır, bu da Int32'dir. C ++ 'da iki ( int32_tve uint32_t) vardır. Bu, API birlikte çalışabilirliğini basitleştirebilir

İmzalı türlerin en büyük dezavantajı barizdir: alan adınızın yarısını kaybedersiniz. İmzalı bir sayı, imzasız bir sayı kadar yüksek sayılamaz. C / C ++ geldiğinde bu çok önemliydi. İşlemcinin tam kapasitesini ele alabilmek ve imzasız sayılar kullanmanız için bunu yapmanız gerekiyordu.

.NET'in hedeflediği uygulama türleri için, tam etki alanı imzasız bir dizine ihtiyaç duyulmamıştır. Bu sayıların amaçlarının çoğu yönetilen bir dilde geçersizdir (bellek havuzlaması akla gelir). Ayrıca, .NET'in ortaya çıkmasıyla, 64 bit bilgisayarlar açık bir şekilde gelecekti. 64 bitlik bir tam sayı aralığına ihtiyaç duymaktan çok uzaktayız, bu yüzden bir biti feda etmek eskisi kadar acı verici değildir. Gerçekten 4 milyar dizine ihtiyacınız varsa, 64 bitlik tamsayılara geçmeniz yeterlidir. En kötüsü, 32 bitlik bir makinede çalıştırıyorsunuz ve biraz yavaş.

Ticareti kolaylık olarak görüyorum. Asla hiç kullanmayacağınız dizin türünüzün bir kısmını boşa harcamayıncaya kadar yeterli bilgi işlem gücüne sahipseniz, sadece yazmak intveya longondan uzaklaşmak uygundur. Bu son parçayı gerçekten istediğinizi düşünüyorsanız, muhtemelen sayılarınızın imzasına dikkat etmelisiniz.


en uygulanmasını diyelim size()oldu return bar_ * baz_;; şimdi kullanmasaydım sahip olamadığım tamsayı taşması (sarma) ile ilgili potansiyel bir sorun yaratmıyor size_tmu?
16'da

5
@Dan İmzasız ints'ların önemli olduğu durumlarda bu tür vakalar oluşturabilir ve bu gibi durumlarda çözmek için tam dil özelliklerini kullanmak en iyisidir. Ancak, bar_ * baz_imzalı bir tamsayı taşmayan ancak imzasız bir tamsayı olmayan bir sınıfa sahip olmanın ilginç bir yapı olacağını söylemeliyim . Kendimizi C ++ ile sınırlamak, imzasız taşmanın spesifikasyonda tanımlandığını, ancak imzalı taşmanın tanımsız davranış olduğunu belirtmek gerekir, bu nedenle imzasız tam sayıların modulo aritmetiği isteniyorsa, kesinlikle kullanın, çünkü aslında tanımlanmıştır!
Cort Ammon

1
@Dan - eğersize() taştı imzalı çarpma, dil UB arazi demektir. (ve fwrapvmodda, bir sonrakine bakın :) O zaman , sadece küçük bir çiş biraz daha, imzasız çarpımı aştı, kullanıcı kodu-hata ülkesinde - sahte bir boyut dönecekti. Bu yüzden burada imzasız alımlar çok fazla almıyor.
Martin Ba

4

Bence yukarıda rwong cevabı zaten mükemmel sorunları vurgular.

002 kodumu ekleyeceğim:

  • size_t, yani, ...

    herhangi bir türde (dizi dahil) teorik olarak mümkün bir nesnenin maksimum boyutunu saklayabilir.

    ... yalnızca aralık sizeof(type)==1baytlarında, yani byte ( char) türleriyle uğraşıyorsanız gereklidir . (Ancak, bir ptr türünden daha küçük olabileceğini belirtiyoruz :

  • Bu nedenle, xxx::size_typeimzalı boyutlu bir tip olsa bile% 99,9 oranında kullanılabilir. (karşılaştır ssize_t)
  • Aslında std::vectorseçti ve arkadaşları size_t, bir imzasız boyut ve endeksleme için, tip olan bazıları tarafından kabul bir tasarım kusuru olmak. Hemfikirim. (Cidden, 5 dakika ayır ve yıldırım konuşmasını izle CppCon 2016: Jon Kalb “imzasız: Daha İyi Kod İçin Bir Kılavuz” .)
  • Bugün bir C ++ API'sı tasarladığınızda, sıkı bir yerdesiniz: size_tStandart Kitaplıkla tutarlı olmak için kullanın veya ( imzalı ) kullanın intptr_tveya ssize_thataya daha az eğilimli dizinleme hesaplamaları için kullanın.
  • İnt32 veya int64 kullanmayın - intptr_timzalı gitmek istiyorsanız kullanın ve makine sözcük boyutu veya kullanın ssize_t.

Doğrudan soruya cevap vermek için, bu adres alanı tamamen yarısından fazlası adrese gerek teorik bir sorun olarak bir "tarihi eser", değil ( "endeksleme" ya) gerekmektedir aehm, düşük seviyeli dil gibi içinde her nasılsa ele olmak C ++.

Gez, ben şahsen düşünüyorum, bu ise Standart Kütüphane imzasız kullanır bir tasarım kusuru size_tbütün bir ham bellek boyutunu temsil etmez hatta yerde, ancak koleksiyonları için gibi yazılan verilerin bir kapasite:

  • verilen C ++ s tamsayı tanıtım kuralları ->
  • işaretsiz türler, anlamsal olarak işaretsiz boyut gibi bir şey için "anlamsal" türler için iyi adaylar yapmazlar.

Jon'un tavsiyesini burada tekrarlayacağım :

  • Destekledikleri işlemler için türleri seçin (değer aralığı değil). (* 1)
  • API'nızda işaretsiz türler kullanmayın. Bu, herhangi bir ters faydası olmayan böcekleri gizler.
  • Miktarlar için "imzasız" kullanmayın. (* 2)

(* 1) yani unsigned == bitmask, asla matematik yapmayın (burada ilk istisnayı vurur - saran bir sayaca ihtiyacınız olabilir - bu imzasız bir tür olmalıdır.)

(* 2) saydığınız ve / veya matematik yaptığınız bir şey anlamına gelen miktarlar.


"Tam avilable düz bellek" ile ne demek istiyorsun? Ayrıca, herhangi bir (üye olmayan) işaretçiyi depolayabilen ve dolayısıyla daha büyük olabilecek, yerine ssize_timzalı asılı olarak tanımlanan istemediğinizden emin misiniz ? size_tintptr_t
Tekilleştirici

@Deduplicator - Sanırım size_ttanımı biraz karışmış olabilir . Bkz. Size_t vs. intptr ve en.cppreference.com/w/cpp/types/size_t Bugün yeni bir şey öğrendim. :-) Bence argümanların geri kalanı, kullanılan türleri düzeltebilir miyim göreceğim.
Martin Ba

0

Ben sadece performans nedenleriyle ben normalde size_t kullanın, her iki aralık kontrolleri (sıfırın altında ve boyutu ()) bir azaltılabilir anlamına gelir bir taşma neden olduğundan emin olmak için ekleyeceğiz :

imzalı int kullanarak:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

if (i < 0)
{
    //error
}

if (i > size())
{
    //error
}

imzasız int kullanarak:

int32_t i = GetRandomNumberFromRange(-1000, 1000);

/// This will underflow any number below zero, so that it becomes a very big *positive* number instead.
uint32_t asUnsigned = static_cast<uint32_t>(i);

/// We now don't need to check for below zero, since an unsigned integer can only be positive.
if (asUnsigned > size())
{
    //error
}

1
Sen gerçekten daha iyice Şunu anlatmak istiyorum.
Martin Ba

Cevabı daha kullanışlı hale getirmek için, tamsayı dizi sınırlarının veya ofset karşılaştırmasının (imzalı ve imzasız) çeşitli derleyici satıcılarının makine kodunda nasıl göründüğünü açıklayabilirsiniz. Verilen C ++ kodu ve derleyici bayrakları için karşılık gelen derlenmiş makine kodunu gösterebilen birçok çevrimiçi C ++ derleyicisi ve sökme sitesi vardır.
rwong

Bunu biraz daha açıklamaya çalıştım.
asger
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.