C imzasız dönüşüm imzaladı - her zaman güvenli mi?


135

Aşağıdaki C koduna sahip olduğumu varsayalım.

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

Ne örtük dönüşümler burada devam ve tüm değerleri için bu kod güvenlidir edilir uve i? (Güvenli, bu örnekteki sonuç büyük bir pozitif sayıya taşacak olsa da , bunu bir int'e geri gönderebilir ve gerçek sonucu elde edebilirim.)

Yanıtlar:


223

Kısa cevap

Kişisel iolacak dönüştürülen eklenerek işaretsiz bir tamsayı UINT_MAX + 1büyük sonuçlanan, daha sonra fazladan işaretsiz değerleri ile gerçekleştirilecektir resultdeğerlerine bağlı olarak ( uvei ).

Uzun cevap

C99 Standardına göre:

6.3.1.8 Genel aritmetik dönüşümler

  1. Her iki işlenen de aynı türe sahipse, başka bir dönüşüme gerek yoktur.
  2. Aksi takdirde, her iki işlenen imzalı tamsayı tipine sahipse veya her ikisi de işaretsiz tamsayı tipine sahipse, daha az tamsayı dönüşüm sırası türüne sahip işlenen, daha büyük bir sıraya sahip işlenen türüne dönüştürülür.
  3. Aksi takdirde, işaretsiz tamsayı türüne sahip olan işlenen, diğer işlenenin türünden daha büyük veya ona eşitse, o zaman işaretli tamsayı tipine sahip işlenen, işaretsiz tamsayı tipine sahip işlenen türüne dönüştürülür.
  4. Aksi takdirde, işaretli tamsayı türüne sahip işlenen türü, işaretsiz tamsayı türüne sahip işlenen türünün tüm değerlerini temsil edebiliyorsa, işaretsiz tamsayı türüne sahip işlenen işaretli tamsayı türüne sahip işlenen türüne dönüştürülür.
  5. Aksi takdirde, her iki işlenen de işaretli tamsayı tipine sahip işlenen tipine karşılık gelen imzasız tamsayı tipine dönüştürülür.

Sizin durumunuzda, imzasız bir int ( u) ve imzalı int ( i) var. Yukarıdaki (3) 'e bakıldığında, her iki işlenen de aynı sıraya sahip olduğundan, işaretsiz bir tamsayıya dönüştürülmenizi gerekecektir .

6.3.1.3 İşaretli ve işaretsiz tam sayılar

  1. Tamsayı türündeki bir değer _Bool dışında başka bir tamsayı türüne dönüştürüldüğünde, değer yeni türle gösterilebiliyorsa, değişmez.
  2. Aksi takdirde, yeni tür imzasızsa, değer, yeni türün aralığına gelinceye kadar, yeni türde temsil edilebilecek maksimum değerden bir kez daha fazla eklenerek veya çıkarılarak dönüştürülür.
  3. Aksi takdirde, yeni tür imzalanır ve değer, içinde temsil edilemez; sonuç uygulama tarafından tanımlanır veya uygulama tarafından tanımlanan bir sinyal oluşturulur.

Şimdi yukarıdaki (2) 'ye başvurmamız gerekiyor. Sizin iekleyerek işaretsiz değere dönüştürülecektir UINT_MAX + 1. Dolayısıyla sonuç, UINT_MAXuygulamanızda nasıl tanımlandığınıza bağlı olacaktır . Büyük olacak, ancak taşmayacak, çünkü:

6.2.5 (9)

İmzasız işlenenleri içeren bir hesaplama hiçbir zaman taşamaz çünkü sonuçta işaretsiz tamsayı türü ile temsil edilemeyen bir sonuç, modüle, sonuçta elde edilen tür tarafından temsil edilebilecek en büyük değerden daha büyük olan sayıdır.

Bonus: Aritmetik Dönüşüm Yarı-WTF

#include <stdio.h>

int main(void)
{
  unsigned int plus_one = 1;
  int minus_one = -1;

  if(plus_one < minus_one)
    printf("1 < -1");
  else
    printf("boring");

  return 0;
}

Çevrimiçi olarak denemek için bu bağlantıyı kullanabilirsiniz: https://repl.it/repls/QuickWhimsicalBytes

Bonus: Aritmetik Dönüşüm Yan Etkisi

Aritmetik dönüşüm kuralları, UINT_MAXimzasız bir değerin şu şekilde başlatılmasıyla değerini elde etmek için kullanılabilir -1, yani:

unsigned int umax = -1; // umax set to UINT_MAX

Yukarıda açıklanan dönüşüm kuralları nedeniyle sistemin imzalı numara temsilinden bağımsız olarak taşınabilir olması garanti edilir. Daha fazla bilgi için şu SO sorusuna bakın: Tüm bitleri true olarak ayarlamak için -1 kullanmak güvenli midir?


Neden sadece mutlak bir değer yapamayacağını anlamıyorum ve sonra pozitif sayılar gibi tedavi imzasızdır?
Jose Salvatierra

7
@ D.Singh cevap içinde yanlış kısımları işaret edebilir misiniz?
Shmil The Cat

İmzasız olarak imzasız hale getirmek için, imzasız değerin maksimum değerini ekliyoruz (UINT_MAX +1). Benzer şekilde imzasızdan imzalıya dönüştürmenin kolay yolu nedir? Belirtilen sayıyı maksimum değerden çıkarmamız gerekir mi (işaretsiz karakter olması durumunda 256)? Örneğin: 140, işaretli sayıya dönüştürüldüğünde -116 olur. Fakat 20, 20 olur. Burada kolay bir numara var mı?
Jon Wheelock


24

İmzalıdan imzasıza dönüştürme, yalnızca imzalı değerin temsilini kopyalamak veya yeniden yorumlamak zorunda değildir . C standardından alıntı (C99 6.3.1.3):

Tamsayı türündeki bir değer _Bool dışında başka bir tamsayı türüne dönüştürüldüğünde, değer yeni türle gösterilebiliyorsa, değişmez.

Aksi takdirde, yeni tür imzasızsa, değer, yeni türün aralığına gelinceye kadar, yeni türde temsil edilebilecek maksimum değerden bir kez daha fazla eklenerek veya çıkarılarak dönüştürülür.

Aksi takdirde, yeni tür imzalanır ve değer, içinde temsil edilemez; sonuç uygulama tarafından tanımlanır veya uygulama tarafından tanımlanan bir sinyal oluşturulur.

İkisinin bugünlerde neredeyse evrensel olan tamamlayıcı temsili için, kurallar bitleri yeniden yorumlamaya karşılık geliyor. Ancak diğer temsiller için (işaret ve büyüklük veya tamamlayıcılar), C uygulamasının yine de aynı sonucu ayarlaması gerekir, bu da dönüşümün sadece bitleri kopyalayamayacağı anlamına gelir. Örneğin, gösterime bakılmaksızın (işaretsiz) -1 == UINT_MAX.

Genel olarak, C'deki dönüşümler, temsiller üzerinde değil, değerler üzerinde çalışacak şekilde tanımlanır.

Orijinal soruyu cevaplamak için:

unsigned int u = 1234;
int i = -5678;

unsigned int result = u + i;

İ'nin değeri, işaretsiz int değerine dönüştürülür UINT_MAX + 1 - 5678. Bu değer daha sonra işaretsiz değere (1234) eklenir ve elde edilir UINT_MAX + 1 - 4444.

(İmzasız taşma işleminden farklı olarak, imzalı taşma tanımsız davranışı başlatır. Wraparound yaygındır, ancak C standardı tarafından garanti edilmez - ve derleyici optimizasyonları haksız varsayımlar yapan koda zarar verebilir.)


5

İncil'e atıfla :

  • Ekleme işleminiz int öğesinin imzasız int biçimine dönüştürülmesine neden olur.
  • İkisinin tamamlayıcı gösterimi ve eşit boyutta türler varsayarsak, bit kalıbı değişmez.
  • İmzasız int'ten işaretli int'e dönüşüm uygulamaya bağlıdır. (Ancak bugünlerde çoğu platformda beklediğiniz gibi çalışıyor.)
  • İmzalı ve imzasız farklı boyutların birleştirilmesi durumunda kurallar biraz daha karmaşıktır.

3

Bir imzasız ve bir imzalı değişken eklendiğinde (veya herhangi bir ikili işlem) her ikisi de örtük olarak imzasıza dönüştürülür, bu durumda büyük bir sonuç elde edilir.

Bu nedenle, sonucun büyük ve yanlış olabileceği açısından güvenlidir, ancak asla çökmeyecektir.


Doğru değil. 6.3.1.8 Genel aritmetik dönüşümler Bir int ve işaretsiz bir karakter toplarsanız , ikincisi int değerine dönüştürülür. İki imzasız karakter toplarsanız int değerine dönüştürülürler.
2501

3

İmzalıdan imzasıza dönüşürken iki olasılık vardır. Başlangıçta pozitif olan sayılar aynı değer olarak kalır (veya olarak yorumlanır). Başlangıçta negatif olan sayı şimdi daha büyük pozitif sayılar olarak yorumlanacaktır.


1

Daha önce yanıtlandığı gibi, imzalı ve imzasız arasında sorunsuz bir şekilde ileri ve geri gidebilirsiniz. İşaretli tamsayılar için sınır durumu -1 (0xFFFFFFFF) şeklindedir. Ondan toplama ve çıkarma yapmayı deneyin ve geri alabileceğinizi ve doğru olmasını sağlayabileceğinizi göreceksiniz.

Ancak, ileri geri döküm yapacaksanız, değişkenlerinizin ne tür olduklarını, örneğin:

int iValue, iResult;
unsigned int uValue, uResult;

Daha önemli konularla dikkatinizi dağıtmak ve ipucu olmadan adlandırılırlarsa hangi değişkenin ne tür olduğunu unutmak çok kolaydır. İmzasız bir oyuncuya yayın yapmak ve bunu dizi dizini olarak kullanmak istemezsiniz.


0

Burada hangi örtük dönüşümler oluyor,

i imzasız bir tam sayıya dönüştürülecektir.

ve bu kod tüm u ve i değerleri için güvenli midir?

İyi tanımlanmış olması anlamında güvenlidir evet (bkz. Https://stackoverflow.com/a/50632/5083516 ).

Kurallar tipik olarak okunması zor standartlarla yazılır, ancak esasen işaretli tamsayıda kullanılan gösterim ne olursa olsun, işaretsiz tamsayı sayının 2'nin tamamlayıcı gösterimini içerecektir.

Toplama, çıkarma ve çarpma bu sayılar üzerinde doğru bir şekilde çalışarak "gerçek sonucu" temsil eden bir ikişer tamamlayıcı numarası içeren başka bir imzasız tamsayı ile sonuçlanır.

daha büyük imzasız tamsayı türlerine bölme ve döküm iyi tanımlanmış sonuçlara sahip olacaktır, ancak bu sonuçlar 2'nin "gerçek sonuç" u tamamlayıcı gösterimleri olmayacaktır.

(Güvenli, bu örnekteki sonuç büyük bir pozitif sayıya taşacak olsa da, bunu bir int'e geri gönderebilir ve gerçek sonucu elde edebilirim.)

İmzalıdan imzasıza dönüşümler standart tarafından tanımlanırken, tersi uygulama tanımlıdır, hem gcc hem de msvc, imzasız bir tamsayıda saklanan bir 2'nin tamamlayıcı numarasını işaretli bir tam sayıya dönüştürürken "gerçek sonuç" elde edeceğiniz şekilde dönüşümü tanımlar. . Sadece 2'nin tamamlayıcısını işaretli tamsayılar için kullanmayan, belirsiz sistemlerde başka davranışlar bulacağınızı umuyorum.

https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation https://msdn.microsoft.com/en-us/library/0eex498h.aspx


-17

Korkunç Yanıtlar Galore

Özgür Özçitak

İmzalıdan imzasıza (ve tersi) atadığınızda, numaranın dahili gösterimi değişmez. Değişen şey, derleyicinin işaret bitini nasıl yorumladığıdır.

Bu tamamen yanlış.

Mats Fredriksson

Bir imzasız ve bir imzalı değişken eklendiğinde (veya herhangi bir ikili işlem) her ikisi de örtük olarak imzasıza dönüştürülür, bu durumda büyük bir sonuç elde edilir.

Bu da yanlış. İmzasız tipler, imzasız tipteki dolgu bitleri nedeniyle eşit hassasiyete sahip oldukları takdirde ints'e yükseltilebilir.

brisbanetimes

Ekleme işleminiz int öğesinin imzasız int biçimine dönüştürülmesine neden olur.

Yanlış. Belki öyle, belki de değil.

İmzasız int'ten işaretli int'e dönüşüm uygulamaya bağlıdır. (Ancak bugünlerde çoğu platformda beklediğiniz gibi çalışıyor.)

Yanlış. Taşmaya neden oluyorsa veya değer korunuyorsa tanımsız bir davranıştır.

Anonim

İ değeri imzasız int dönüştürülür ...

Yanlış. İmzasız bir int'e göre int'nin hassasiyetine bağlıdır.

Taylor Price

Daha önce yanıtlandığı gibi, imzalı ve imzasız arasında sorunsuz bir şekilde ileri ve geri gidebilirsiniz.

Yanlış. İşaretli bir tam sayı aralığının dışında bir değer depolamaya çalışmak tanımsız davranışa neden olur.

Şimdi nihayet soruya cevap verebilirim.

İnt kesinliği imzasız int değerine eşitse, u imzalı bir int değerine yükseltilir ve ifadeden -4444 değerini alırsınız (u + i). Şimdi, u ve ben başka değerlere sahipseniz, taşma ve tanımsız davranış alabilirsiniz, ancak bu kesin sayılarla -4444 alacaksınız [1] . Bu değer int türüne sahip olacaktır. Ancak bu değeri imzasız bir int'de depolamaya çalışıyorsunuz, böylece imzasız bir int'e yayınlanacak ve sonuçta elde edilen değer (UINT_MAX + 1) - 4444 olacaktır.

İmzasız int'in kesinliği int'den daha büyükse, imzalı int diğer imzasız int 1234'e eklenecek olan (UINT_MAX + 1) - 5678 değerini veren imzasız bir int'e yükseltilecektir. ifadeyi {0..UINT_MAX} aralığının dışında yapan diğer değerler (UINT_MAX + 1) değeri, sonuç {0..UINT_MAX) aralığına girene ve tanımlanmamış bir davranış gerçekleşmeyene kadar eklenecek veya çıkarılacaktır. .

Hassasiyet nedir?

Tamsayıların dolgu bitleri, işaret bitleri ve değer bitleri vardır. İmzasız tamsayıların açık bir şekilde işaret biti yoktur. İmzasız karakterin ayrıca dolgu biti olmaması garanti edilir. Bir tamsayının sahip olduğu bit sayısı, ne kadar hassasiyete sahip olduğudur.

[Sorunlar]

Dolgu bitleri mevcutsa, tek başına makro boyutu makrosu bir tamsayının kesinliğini belirlemek için kullanılamaz. Ve bir baytın büyüklüğünün, C99 tarafından tanımlanan bir sekizli (sekiz bit) olması gerekmez.

[1] Taşma iki noktadan birinde meydana gelebilir. Eklemeden önce (tanıtım sırasında) - int içine sığmayacak kadar büyük imzasız bir intiniz olduğunda. Taşma ayrıca, imzalanmamış int bir int aralığında olsa bile ekleme işleminden sonra ortaya çıkabilir, ekleme işleminden sonra sonuç yine de taşabilir.


6
Msgstr "İmzasız ints ints'a yükseltilebilir". Doğru değil. Türler sıralı> = int olduğundan tamsayı yükseltmesi gerçekleşmez. 6.3.1.1: "İmzasız herhangi bir tamsayı türünün sırası, varsa, karşılık gelen imzalı tamsayı türünün sırasına eşit olacaktır." ve 6.3.1.8: "Aksi takdirde, işaretsiz tamsayı türüne sahip işlenen diğer işlenen türünün sıralamasından daha büyük veya ona eşitse , işaretli tamsayı türüne sahip işlenen işaretsiz tamsayıya sahip işlenen türüne dönüştürülür yazın." her ikisi de olağan aritmetik dönüşümler uygulandığında intdönüştürülen garantidir unsigned int.
CB Bailey

1
6.3.1.8 Yalnızca tamsayı tanıtımından sonra oluşur. Açılış paragrafında "Aksi takdirde, tamsayı tanıtımları her iki işlenende de gerçekleştirilir. Bundan sonra, yükseltilen işlenenlere aşağıdaki kurallar uygulanır". Bu yüzden promosyon kurallarını okuyun 6.3.1.1 ... "Tamsayı türüne sahip bir tamsayı türüne sahip bir nesne veya ifade, int ve unsigned int sıralamasına eşit veya EQUAL" ve "int, orijinal türünde, değer int değerine dönüştürülür ".
Elite Mx

1
6.3.1.1 Olmayan tamsayı türlerini dönüştürmek için kullanılan tamsayı tanıtımı intveyaunsigned int türünün unsigned intveya intbeklenen bir türün beklendiği türlerden birine . "Veya eşit", TC2'ye , bu türlerden birine eşit olan intveya unsigned intbu türlerden birine dönüştürülecek numaralandırılmış dönüşüm sırasının türlerine izin vermek için eklenmiştir . Açıklanan promosyonun unsigned intve arasında değişmesi asla amaçlanmamıştır int. TC2'den sonra bile unsigned intve arasındaki ortak tip belirlenmesi inthala 6.3.1.8 tarafından yönetilmektedir.
CB Bailey

19
Eleştiren başkalarının yanlış cevaplar ise yanlış cevaplar Gönderme çalışma ... ;-) almak için iyi bir strateji gibi görünmüyor
R .. GitHub DUR YARDIMCI ICE

6
Kibirle birleştirilen bu yanlışlık seviyesi çok eğlenceli olduğu için silmek için oy vermiyorum
MM
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.