Soru “Strlen (s1) - strlen (s2)” asla sıfırdan az değildir


Şu anda dize uzunlukları sık karşılaştırma gerektiren bir C programı yazıyorum, bu yüzden aşağıdaki yardımcı işlevi yazdım:

int strlonger(char *s1, char *s2) {
    return strlen(s1) - strlen(s2) > 0;
}

Ne zaman olsa bile fonksiyonun gerçek geri döndüğünü fark ettim s1 daha kısa uzunluk var s2. Birisi bu garip davranışı açıklayabilir mi?


76
2018-05-06 22:19


Menşei


Bu bir Fortran-66-ish yazma şekli return strlen(s1) > strlen(s2);. - Jonathan Leffler
@TimThomas: Neden bu soruya lütuf teklif ediyorsun? Yeterince ilgi görmediğini söylüyorsunuz, ama oldukça mutlu görünüyorsunuz. Alex Lockwood'un cevabı. Ödül kazanmak için gereken daha fazla şey değil! :) - eggyal
Bir kazaydı, bir lütufun lol olduğunu bilmiyordum. -_- Utanç verici bir şey ... - Adrian Monk
Sanırım Alex Lockwood için iyi, çünkü büyük cevabı daha fazla dikkat çekecek ... oylama Alex Lockwood'un cevabı !! : D - Adrian Monk
BenceTimThomas'ın son izin verilebilen tarihe kadar açık kalmasını sağlamak daha iyi bir şey olduğunu düşünüyorum, bu yüzden onun da sorusu biraz dikkat çekiyor ... Bildiğim kadarıyla 100 ününü kaybetmiş, biraz geri dönmesine izin vermiş .. - Krishnabhadra


Cevaplar:


Karşılaştığınız şey, hem imzalı hem de imzasız miktarları içeren ifadeleri ele alırken C'de ortaya çıkan bazı tuhaf davranışlardır.

Bir işlenenin imzalandığı ve diğerinin imzasız olduğu bir işlem gerçekleştirildiğinde, C imzalanmış argümanı imzasız olarak dolaylı olarak dönüştürecek ve sayıların negatif olduğunu varsayarak işlemleri gerçekleştirecektir. Bu sözleşme, genellikle, ilişki operatörleri için mantık dışı davranışlara yol açar. < ve >.

Yardımcı fonksiyonunuzla ilgili olarak şunu unutmayın: strlen döner tipi size_t (imzasız bir miktar), fark ve karşılaştırma imzasız aritmetik kullanılarak hesaplanır. Ne zaman s1 daha kısa s2, fark strlen(s1) - strlen(s2) negatif olmalı, bunun yerine büyük, imzasız bir sayıya dönüşür. 0. Böylece,

return strlen(s1) - strlen(s2) > 0;

döner 1 Bile s1 daha kısa s2. İşlevinizi düzeltmek için, bu kodu kullanın:

return strlen(s1) > strlen(s2);

C'nin harika dünyasına hoş geldiniz! :)


Ek örnekler

Bu soru kısa süre önce çok fazla ilgi gördüğünden, sadece fikrin karşılığını aldığımdan emin olmak için birkaç (basit) örnek sunmak istiyorum. İki tamamlayıcı sunumunu kullanarak 32-bit bir makineyle çalıştığımızı varsayacağım.

C'de imzasız / imzalı değişkenlerle çalışırken anlaşılması gereken önemli bir kavramdır. Tek bir ifadede imzasız ve imzalı miktarların bir karışımı varsa, imzalı değerler imzasız olarak işaretlenir..

Örnek 1:

Aşağıdaki ifadeyi göz önünde bulundurun:

-1 < 0U

İkinci işlenen imzasız olduğundan, birincisi dolaylı olarak cast imzasız ve dolayısıyla ifade karşılaştırmaya eşdeğerdir,

4294967295U < 0U

tabi ki yanlıştır. Bu muhtemelen beklediğiniz davranış değildir.

Örnek 2:

Bir dizinin öğelerini toplamayı deneyen aşağıdaki kodu göz önünde bulundurun. aöğelerin sayısının parametre ile verildiği yer length:

int sum_array_elements(int a[], unsigned length) {
    int i;
    int result = 0;

    for (i = 0; i <= length-1; i++) 
        result += a[i];

    return result;
}

Bu işlev, imzalı imzasız işaretlemeden dolayı hataların ne kadar kolay ortaya çıkabileceğini göstermek için tasarlanmıştır. Parametreyi geçmek oldukça doğal görünüyor length imzasız olarak; Sonuçta, kim negatif uzunluk kullanmak ister ki? Durdurma kriteri i <= length-1 ayrıca oldukça sezgisel görünüyor. Ancak, argüman ile çalıştırıldığında length eşittir 0Bu ikisinin kombinasyonu beklenmedik bir sonuç verir.

Parametre beri length imzalanmamış, hesaplama 0-1 Modüler ilavelere eşdeğer olan işaretsiz aritmetik kullanılarak gerçekleştirilir. Sonuç o zaman Umax. <= Karşılaştırma ayrıca imzasız bir karşılaştırma kullanılarak yapılır ve herhangi bir sayı daha az veya eşit olduğundan UmaxKarşılaştırma her zaman tutar. Böylece, kod dizinin geçersiz öğelerine erişmeye çalışacaktır. a.

Kod, bildirerek giderilebilir length olmak intya da for döngü olmak i < length.

Sonuç: İmzasız ne zaman kullanılmalı?

Burada tartışmaya açık bir şey ifade etmek istemiyorum, fakat burada C programlarını yazdığımda sık sık uyduğum kurallardan birkaçı.

  • YAPMAYIN Sadece bir sayı negatif değildir çünkü kullanın.


174
2018-05-06 22:21



Bir başka güzel örnek nasıl yazılır az programı yapar Daha doğru. - Kerrek SB
@TimThomas Bir yol veya ötekini atmak zorundadır ve imzasız imzalı bilgi dökümünü kaybeder, yani değer yarısı. - user207421
Kesin olarak, çıkarma iki arasında gerçekleştirilir size_t imzasız garantili değerler ve imzasız aritmetik sarımlar, ikisinin uygun gücünü modüle eder. İmzalı / imzasız dönüşümün mümkün olduğu tek yer result > 0 bölüm, nerede result öyle mi size_t iki boyutun çıkarılmasından elde edilen değer. - Jonathan Leffler
Değil oyuncular, o dönüştürür. Dönem oyuncular yalnızca parantezize edilmiş bir addan oluşan açık bir işleci belirtir. Bir yayın operatörü, bir dönüşümü açıkça belirtir; Bir dönüşüm, açık veya kapalı olabilir. - Keith Thompson
Kodumda, karşıt yaklaşımı benimsemek ve kullanmak için yeterince nadir bulunan negatif tamsayıları buluyorum. unsigned int yapmamanız için somut bir sebep yoksa. Bu, bazı eşitsizlikler ile uğraşırken dikkat gerektirse de, tüm operasyonların iyi tanımlanmış olmasına (hatta “etrafı sarmak”) yarar sağlar. - Joshua Green


strlen bir döndürür size_t hangisi typedef bir ... için unsigned yazın.

Yani,

(unsigned) 4 - (unsigned) 7 == (unsigned) - 3

Herşey unsigned değerler büyüktür veya eşittir 0. Tarafından döndürülen değişkenleri dönüştürmeyi deneyin strlen için long int.


25
2018-05-06 22:21



ptrdiff_t, doğru taşınabilir dökümdür. 64 bit sistemlerde 32 bit imzalı bir tamsayı olması uzun süredir yaygındır (64 bit sistemlerde 64 bitlik işaretçilerdir). Aslında, hem Visual C ++ hem de x86 ve x86_64 için gcc, 32 bit uzunluğunda kullanır. - Mr Fooz
düşündüm ptrdiff_t işaretçilerin çıkarılması için, çıkarma değil size_t değerler ... - Mr Lister
"Çıkarma" için POSIX türü yok size_t "; C, onu basitçe tanımlar size_t ayrılmaz bir tür olduğu ve türlerin eşleştiği için. Bunun olduğunu iddia edebilirsin. off_tama bu aslında dosya ofsetleri için. Böylece yapacağınız en iyi şey, size_t Platformun işleyebildiği herhangi bir dizini tutmak için gereklidir, daha sonra herhangi bir işaretçi değerini de gösterebilir, çünkü baytları dizinlemek için kullanılabilir 0. Böylece ptrdiff_t olarak aynı sayıda bit olması gerekir size_tbasitçe signed versiyonu size_t. - Mike DeSimone


Alex Lockwood'un Cevap en iyi çözümdür (kompakt, net semantik vb.).

Bazen açıkça imzalı bir şekle dönüştürmek mantıklıdır. size_t: ptrdiff_t, Örneğin.

return ptrdiff_t(strlen(s1)) - ptrdiff_t(strlen(s2)) > 0;

Bunu yaparsanız, size_t değer bir ptrdiff_t (bir tane daha az mantı biti vardır).


1
2018-06-02 00:26