Bir sayı çok büyükse, bir sonraki hafıza konumuna taşar mı?


30

C programlamayı gözden geçirdim ve beni rahatsız eden birkaç şey var.

Örneğin bu kodu alalım:

int myArray[5] = {1, 2, 2147483648, 4, 5};
int* ptr = myArray;
int i;
for(i=0; i<5; i++, ptr++)
    printf("\n Element %d holds %d at address %p", i, myArray[i], ptr);

Bir int'nin maksimum pozitif 2,147,483,647 değerine sahip olabileceğini biliyorum. Öyleyse, bunun üzerinden geçerek, eleman 2'nin bu adreste "-2147483648" olarak görünmesini sağlayan bir sonraki hafıza adresine "akıyor" mu? Ama sonra bu gerçekten bir anlam ifade etmiyor çünkü çıktıda hala bir sonraki adresin 4, 5 değerine sahip olduğunu söylüyor. Eğer sayı bir sonraki adrese taşmış olsaydı, o zaman o adreste saklanan değeri değiştirmezdi. ?

MIPS Assembly'de programlamayı ve program adreslerinin adres değiştirme değerlerini adım adım izleyerek bu adreslere atanan değerlerin değişeceğini hatırlıyorum.

Yanlış hatırlamıyorsam, o zaman burada başka bir soru var: Belirli bir adrese atanan numara türden daha büyükse (myArray [2] 'de olduğu gibi), o zaman bir sonraki adreste saklanan değerleri etkilemez mi?

Örnek: 0x10010000 adresinde int myNum = 4 milyar var. Tabii ki myNum 4 milyar depolayamıyor, bu yüzden bu adreste negatif bir rakam gibi görünüyor. Bu büyük sayıyı saklayamamakla birlikte, sonraki 0x10010004 adresinde depolanan değer üzerinde hiçbir etkisi yoktur. Doğru?

Hafıza adresleri sadece belirli boyutlarda sayı / karakterleri tutacak kadar yeterli alana sahiptir ve boyut limitin üzerine çıkarsa, farklı şekilde temsil edilir (4 milyar int'ye depolanmaya çalışılır ancak negatif bir sayı olarak görünür) ve dolayısıyla bir sonraki adrede saklanan rakamlar / karakterler üzerinde bir etkisi yoktur.

Eğer denize düştüysem özür dilerim. Bundan bütün gün büyük bir beyin osuruğu geçirdim.


10
Dize aşmaları ile karıştırılıyor olabilirsiniz .
Robbie Dee,

19
Ödev: Değiştir o yüzden basit bir işlemci yapar sızıntısı. Mantığın çok daha karmaşık hale geldiğini göreceksiniz, hepsi de her yerde güvenlik delikleri sağlayacak her şeyden önce güvenlik delillerini garanti edecek bir "özellik" için.
phihag,

4
Gerçekten büyük sayılara ihtiyacınız varsa, büyük sayılara sığdırmak için ne kadar bellek kullandığını artıran bir sayı gösterimi yapmak mümkündür. İşlemcinin kendisi bunu yapamaz ve C dilinin bir özelliği değildir, ancak bir kütüphane uygulayabilir - ortak bir C kütüphanesi GNU Multiple Precision aritmetik kütüphanesidir . Kütüphane, aritmetikin üstünde performans maliyeti olan sayıları depolamak için belleği yönetmelidir. Birçok dilde bu tür bir şey vardır (bu maliyetlerden kaçınmaz).
Steve314

1
basit bir test yaz, ben bir C programcısı değilim ama çizgileri boyunca bir şey int c = INT.MAXINT; c+=1;ve c'ye ne olduğunu görün.
JonH

2
@JonH: Problem, Tanımsız Davranışta taşma sorunudur. AC derleyicisi bu kodu tespit edebilir ve koşulsuz olarak taşması nedeniyle erişilemez kod olduğunu tespit edebilir. Ulaşılamayan kod önemli olmadığından, elimine edilebilir. Sonuç: kod kalmadı.
MS

Yanıtlar:


48

Hayır, değil. C değişkenlerinde, çalışacak sabit bir bellek adresleri kümesi vardır. 4 baytlık bir sistemde çalışıyorsanız intsve bir intdeğişken ayarlayıp 2,147,483,647eklerseniz 1, değişken genellikle içerecektir -2147483648. (Çoğu sistemde. Davranış aslında tanımsız.) Başka hiçbir bellek konumu değiştirilmez.

Temel olarak, derleyici, tür için çok büyük bir değer atamanıza izin vermez. Bu bir derleyici hatası üretecektir. Bir davaya zorlarsanız, değer kesilecektir.

Tür, yalnızca 8 bit saklayabiliyorsa ve değeri bir 1010101010101kasayla zorlamaya çalışırsanız , alt 8 bit veya sonunda bitirdiniz 01010101.

Örnekte olursa olsun yaptıklarınızdan için myArray[2], myArray[3]'4' içerecektir. Üzerine "dökülme" yok. 4 bayttan daha fazla olan bir şey koymaya çalışıyorsunuz, sadece üstteki 4 bayttan çıkarak üstteki her şeyi bırakıyor. Çoğu sistemde bu ortaya çıkar -2147483648.

Pratik bir bakış açısıyla, bunun asla, asla olmadığından emin olmak istiyorsun. Bu tür taşmalar genellikle çözülmesi zor kusurlarla sonuçlanır. Başka bir deyişle, tüm değerlerin milyarlarca olması ihtimalinin olduğunu düşünüyorsanız, kullanmayın int.


52
4 baytlık bir sistemde çalışıyorsanız ve bir int değişkeni 2,147,483,647 olarak ayarlayıp 1 eklerseniz, değişken -2147483648 içerir. => Hayır , Tanımsız Davranış , bu yüzden etrafa dolanabilir veya tamamen başka bir şey yapabilir; Derleyicilerin taşma olmamasına dayanan kontrolleri optimize ettiğini gördüm ve örneğin sonsuz döngülere sahip oldum ...
Matthieu M.

Üzgünüm, evet haklısın. Oraya "genellikle" eklemeliydim.
Robotu

@MatthieuM dil açısından, bu doğru. Burada bahsettiğimiz şey olan sistemde icra etme açısından, bu tamamen saçmalık.
hobbs

@hobbs: Sorun, derleyiciler Tanımsız Davranış nedeniyle programı yönettiğinde, aslında programın çalıştırılmasının, hafızanın üzerine yazılmasıyla karşılaştırılabilir olarak beklenmeyen bir davranış üretmesi beklenir.
Matthieu M.

24

İmzalanan tamsayı taşması tanımsız davranış. Bu durumda programınız geçersizdir. Derleyicinin bunu sizin için kontrol etmesi gerekmez, bu nedenle makul bir şey yapmış gibi görünen bir çalıştırılabilir dosya oluşturabilir, ancak yapacağı garantisi yoktur.

Ancak, işaretsiz tamsayı taşması iyi tanımlanmıştır. UINT_MAX + 1 modülünü saracak. Değişkeniniz tarafından işgal edilmeyen hafıza etkilenmeyecektir.

Ayrıca bkz. Https://stackoverflow.com/q/18195715/951890


İmzalı tamsayı taşması işaretsiz tamsayı taşması kadar iyi tanımlanmıştır. kelime $ N $ bit içeriyorsa, işaretlenmiş tamsayı taşmasının üst sınırı $$ 2 ^ {N-1} -1 $$ (burada $ -2 ^ {{N-1} $ civarındadır) imzasız tamsayı taşması için üst sınır, $ 2 ^ N - 1 $$ (0 $ $ civarındadır). Toplama ve çıkarma için aynı mekanizmalar, temsil edilebilecek sayı aralığında ($ 2 ^ N $) aynı boyutta. taşma için sadece farklı bir sınır.
robert bristow-johnson

1
@ robertbristow-johnson: C standardına göre değil.
Vaughn Cato

peki, standartlar bazen anakronistiktir. SO referansına bakıldığında, doğrudan isabet eden bir yorum var: “Buradaki önemli not, modern dünyada 2'nin tamamlayıcı imzalı aritmetik dışında herhangi bir şey kullanan hiçbir mimarinin kalmamasıdır. Dil standartlarının hala uygulamaya izin verdiği örneğin, bir PDP-1 saf bir tarihi eserdir. - Andy Ross 12: 13
Ağustos'ta

ben herhalde değil C standardında, ama düzenli bir ikili aritmetik için kullanılmayan bir uygulama olabilir varsayalım int. onlar kullanabilir s'pose Gri kodunu veya BCD veya EBCDIC . neden herkesin Gray kodu veya EBCDIC ile aritmetik yapmak için donanım tasarlayacağını bilmiyorum, ama sonra yine, neden birinin unsignedikili dosyaya gireceğini ve int2'nin tamamlayıcısı dışında bir şey ile imzaladığını bilmiyorum .
robert bristow-johnson,

14

Yani burada iki şey var:

  • Dil seviyesi: C'nin anlambilimi nedir
  • makine seviyesi: kullandığınız montaj / CPU'nun anlamı nedir

Dil seviyesinde:

C’de:

  • taşma ve taşma işaretsiz tamsayılar için modulo aritmetik olarak tanımlanır , bu nedenle değerleri "döngüler"
  • taşma ve taşma işaretli tamsayılar için Tanımsız Davranış , böylece her şey olabilir

Bunlar için "ne bir şey" örneği isterdim, gördüm:

for (int i = 0; i >= 0; i++) {
    ...
}

dönüşmek:

for (int i = 0; true; i++) {
    ...
}

ve evet, bu meşru bir dönüşümdür.

Bu, bazı garip derleyici dönüşümleri nedeniyle taşma konusundaki belleğin üzerine yazma potansiyel riskleri olduğu anlamına gelir.

Not: Clang veya gcc'de , imzalı tamsayıların taşması / taşması üzerine iptal edilecek Tanımsız Davranış Dezenfektanını-fsanitize=undefined etkinleştirmek için Debug'ta kullanın .

Veya bir dizine indekslemek (işaretlenmemiş) işlemin sonucunu kullanarak belleğin üzerine yazabileceğiniz anlamına gelir. Bu ne yazık ki, taşma / taşma tespitinin olmaması durumunda çok daha muhtemeldir.

Not: Clang veya gcc'de , sınır dışı erişimi iptal edecek Adres Temizleyiciyi-fsanitize=address etkinleştirmek için Debug'ta kullanın .


Makine seviyesinde :

Gerçekten kullandığınız montaj talimatlarına ve CPU'ya bağlıdır:

  • x86'da, ADD taşma / taşma işleminde 2 bileşen kullanacak ve OF (Taşma Bayrak) olarak ayarlayacaktır.
  • Gelecekteki Mill CPU'sunda, aşağıdakiler için 4 farklı taşma modu olacaktır Add:
    • Modulo: 2 bileşenli modulo
    • Tuzak: Bir tuzak üretilir, hesaplamayı durdurur
    • Doygunluk: değer taşma sırasında min. Veya taşma maks.
    • Çift Genişlik: sonuç, çift genişlikte bir kayıt defterinde üretilir

İşlemcilerin kayıtlarda mı yoksa bellekte mi gerçekleştiğine dikkat edin, CPU hiçbir durumda taşma üzerine belleğin üzerine yazmaz.


Son üç mod imzalandı mı? (Birincisi için fark etmez, çünkü 2 tamamlayıcıdır.)
Deduplicator

1
@Deduplicator: Mill CPU Programlama Modeline Giriş'e göre imzalı ekleme ve imzasız ekleme için farklı kodlar vardır; Her iki opodun da 4 modu destekleyeceğini umuyorum (ve çeşitli bit genişlikleri ve skaler / vektörler üzerinde çalışabiliyoruz). Sonra tekrar, şu an için buhar donanım;)
Matthieu M.

4

@ StevenBurnap'ın cevabını daha da ileriye götürmek için bunun olmasının nedeni, bilgisayarların makine düzeyinde nasıl çalıştığıdır.

Diziniz bellekte saklanır (örn. RAM'de). Bir aritmetik işlem gerçekleştirildiğinde, bellekteki değer aritmetik işlemi gerçekleştiren devrenin giriş kayıt defterine kopyalanır (ALU: Aritmetik Mantık Birimi ), işlem daha sonra bir sonuç üreten giriş kayıtlarındaki verilerde gerçekleştirilir. çıktı kaydında. Bu sonuç daha sonra hafızadaki doğru adresten tekrar belleğe kopyalanarak diğer hafıza alanlarına dokunulmaz.


4

Öncelikle (C99 standardını varsayarak), <stdint.h>standart başlık eklemek isteyebilirsiniz ve burada tanımlanmış tiplerden bazılarını, özellikle int32_ttam olarak 32 bit işaretli bir tamsayı olan veya uint64_ttam olarak 64 bit işaretsiz bir tamsayı vb. Kullanmak isteyebilirsiniz . int_fast16_tPerformans nedenleriyle gibi türleri kullanmak isteyebilirsiniz .

İmzasız aritmetiğin bitişik bellek konumlarına asla dökülmediğini (veya taşmadığını) açıklayan diğer cevapları okuyun. İmzalanmış taşma üzerindeki tanımsız davranışlara dikkat edin .

Daha sonra, tam olarak çok büyük tam sayıları hesaplamanız gerekiyorsa (örneğin 1000'in faktörlüğünü ondalık basamaktaki tüm 2568 basamağıyla hesaplamak istiyorsanız), bigints aka rasgele kesinlikli sayılar (ya da bignumlar) istiyorsunuz . Verimli bigint aritmetiği için algoritmalar oldukça zekidir ve genellikle özel makine talimatları kullanılmasını gerektirir (örneğin, işlemciniz varsa, bazıları taşıma içeren sözcükler ekler). Bu nedenle, GMPlib gibi varolan bazı bigint kütüphanelerini kullanmanızı şiddetle tavsiye ediyorum.

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.