Belli bir tipte bir değişken tanımlarsam (ki bildiğim kadarıyla sadece değişkenin içeriği için veri tahsis eder), değişkenin hangi tip değişken olduğunu nasıl takip eder?
Belli bir tipte bir değişken tanımlarsam (ki bildiğim kadarıyla sadece değişkenin içeriği için veri tahsis eder), değişkenin hangi tip değişken olduğunu nasıl takip eder?
Yanıtlar:
Değişkenler (veya daha genel olarak: "C" anlamındaki "nesneler") çalışma türlerinde türlerini depolamaz. Makine kodu ile ilgili olarak, yalnızca yazılmamış bellek var. Bunun yerine, bu verilerdeki işlemler verileri belirli bir tür (örneğin bir şamandıra veya işaretçi olarak) olarak yorumlar. Tipler sadece derleyici tarafından kullanılır.
Örneğin, bir yapıya veya sınıfa struct Foo { int x; float y; };
ve değişkene sahip olabiliriz Foo f {}
. Bir saha erişimi auto result = f.y;
nasıl derlenebilir? Derleyici bu f
tür bir nesne olduğunu Foo
bilir ve Foo
-objects düzenini bilir . Platforma özgü ayrıntılara bağlı olarak, bu, “İşaretçiyi başlangıcına götür, f
4 bayt ekle, sonra 4 bayt yükle ve bu verileri bir kayan nokta olarak yorumla ” olarak derlenebilir . Birçok makine kodu komut setinde (x86-64 dahil) ) Şamandıralar veya ints yüklemek için farklı işlemci talimatları vardır.
C ++ tip sisteminin bizim için tipini takip edemediği bir örnek, bunun gibi bir birlikteliktir union Bar { int as_int; float as_float; }
. Bir sendika, çeşitli türde birden fazla nesne içerir. Bir nesneyi bir sendikada saklarsak, bu sendikanın aktif türüdür. Bu türden sadece sendikanın dışına çıkmaya çalışmalıyız, başka herhangi bir şey tanımsız davranış olacaktır. Aktif tipin ne olduğunu programlarken “biliyoruz” veya bir tip etiketini (genellikle enum) ayrı ayrı sakladığımız etiketli bir birlik oluşturabiliriz . Bu, C'de yaygın bir tekniktir, ancak birliği ve type etiketini eşitlemede tutmamız gerektiğinden, bu oldukça yanlıştır. Bir void*
işaretçi bir birliğe benzer ancak işlev işaretçileri dışında yalnızca işaretçi nesnelerini tutabilir.
C ++, bilinmeyen türdeki nesnelerle uğraşmak için iki daha iyi mekanizma sunar: Tip silme işlemi yapmak için nesne yönelimli teknikler kullanabiliriz (nesne ile yalnızca gerçek metodları bilmemize gerek kalmadan sanal yöntemlerle etkileşime girebiliriz) veya Kullanım std::variant
, tip-güvenli birliğin bir tür.
C ++ 'ın bir nesnenin türünü sakladığı bir durum vardır: nesnenin sınıfı herhangi bir sanal yönteme sahipse (bir “polimorfik tip”, aka. Arayüz). Sanal yöntem çağrısının hedefi derleme zamanında bilinmiyor ve nesnenin dinamik türüne (“dinamik gönderme”) dayalı çalışma zamanında çözüldü. Çoğu derleyici bunu, nesnenin başlangıcında sanal bir işlev tablosu (“değişken”) depolayarak gerçekleştirir. Vtable, çalışma zamanında nesnenin türünü almak için de kullanılabilir. Daha sonra, bir ifadenin derleme zamanı olarak bilinen statik türü ile çalışma zamanında bir nesnenin dinamik türü arasında bir ayrım yapabiliriz.
C ++, bize bir nesne typeid()
veren işleci ile bir nesnenin dinamik türünü incelememize izin verir std::type_info
. Derleyici derleme zamanında nesnenin türünü bilir veya derleyici nesnenin içinde gerekli tip bilgisini saklar ve çalışma zamanında alabilir.
void*
).
typeid(e)
İfadenin statik türünü inceler e
. Statik tür polimorfik bir tür ise, ifade değerlendirilir ve o nesnenin dinamik tipi alınır. Türün kimliğini bilinmeyen türün belleğine işaret edemez ve yararlı bilgiler edemezsiniz. Örneğin, bir sendikanın kimliği, sendikadaki nesneyi değil, birliği tanımlar. A void*
türü sadece geçersiz bir işaretçidir. Ve bir a void*
içeriğini almak için caydırmak mümkün değildir . C ++ 'da açıkça bu şekilde programlanmadıkça boks yoktur.
Diğer cevap teknik yönü iyi açıklıyor, ancak bazı genel "makine kodu hakkında nasıl düşünüleceğini" eklemek istiyorum.
Derlemeden sonraki makine kodu oldukça aptalca ve gerçekten her şeyin planlandığı gibi çalıştığını varsayar. Diyelim ki basit bir işleve sahipsiniz.
bool isEven(int i) { return i % 2 == 0; }
Bir int alır ve bir bool tükürür.
Derlemeden sonra, bu otomatik portakal sıkacağı gibi bir şey düşünebilirsiniz:
Portakal alır ve meyve suyu verir. İçeri girdiği nesne tipini tanıyor mu? Hayır, sadece portakal olması gerekiyordu. Portakal yerine elma alırsa ne olur? Belki kırılacak. Önemli değil, çünkü sorumlu bir sahibi bu şekilde kullanmaya çalışmaz.
Yukarıdaki işlev benzerdir: giriş yapmak için tasarlanmıştır ve başka bir şeyi beslediğinde alakasız bir şey kırabilir veya yapabilir. Bu (genellikle) farketmez, çünkü derleyici (genel olarak) asla gerçekleşmeyeceğini kontrol eder - ve gerçekten de hiçbir zaman iyi biçimlendirilmiş kodda olmaz. Derleyici, bir işlevin yanlış yazılmış değer alma olasılığını tespit ederse, kodu derlemeyi reddeder ve bunun yerine tür hatalarını döndürür.
Uyarı, derleyicinin geçeceği bazı kötü biçimli kod vakaları olduğudur. Örnekler:
void*
için orange*
pointer diğer ucunda bir elma varken,Söylediğim gibi, derlenen kod aynı meyve sıkacağı makinesi gibi - ne işlediğini bilmiyor, sadece talimatlar veriyor. Ve talimatlar yanlışsa, kırılır. Bu nedenle C ++ 'daki problemlerin kontrolsüz çökmelere yol açması budur.
void*
için zorlar foo*
zamanki aritmetik promosyonlar, union
tipi cinaslı, NULL
vs nullptr
, hatta sadece sahip kötü işaretçi vb UB olduğunu Ama izne en iyisi bu yüzden, maddi olarak cevabınızı artıracak dışarı bunların hepsi listeleme sanmıyorum olduğu gibi.
void*
da örtük olarak dönüştürülmez foo*
ve union
tip yazın desteklenmez (UB vardır).
Bir değişkenin C gibi bir dilde bir takım temel özellikleri vardır:
Kaynak kodunuzda , konum (5) kavramsaldır ve bu yer adıyla (1) belirtilir. Bu nedenle, değerin yerini ve alanını oluşturmak için değişken bildirimi kullanılır (6) ve diğer kaynak satırlarında, değişkeni bir ifadede adlandırarak bu konuma ve içerdiği değeri belirtiriz.
Program, derleyici tarafından makine koduna çevrildikten sonra, konum, (5), bir miktar bellek veya CPU kayıt yeri ve değişkeni referans alan herhangi bir kaynak kodu ifadesi, bu belleği referans alan makine kodu dizilerine çevrilir. veya CPU kayıt yeri.
Bu nedenle, çeviri tamamlandığında ve program işlemci üzerinde çalıştığında, değişkenlerin adları makine kodunda etkin bir şekilde unutulur ve derleyici tarafından oluşturulan talimatlar yalnızca değişkenlerin atanmış konumlarına atıfta bulunur. adları). Hata ayıklama yapıyorsanız ve hata ayıklama istiyorsanız, adla ilişkilendirilen değişkenin konumu programın meta verilerine eklenir, ancak işlemci yine de konumlarını (makine verileri değil) kullanarak makine kodu talimatlarını görür. (Bazı isimler bağlantı, yükleme ve dinamik arama amacıyla programın meta verilerinde yer aldığından, bu aşırı bir basitleştirmedir - hala işlemci sadece program için söylenen makine kodu talimatlarını yerine getirir ve bu makine kodunda isimler konumlara dönüştürüldü.)
Aynısı tip, kapsam ve ömür boyu da geçerlidir. Derleyici tarafından oluşturulan makine kodu yönergeleri, değeri depolayan konumun makine sürümünü bilir. Tür gibi diğer özellikler, değişkenin konumuna erişen belirli talimatlar olarak çevrilmiş kaynak kodunda derlenir. Örneğin, söz konusu değişken, imzasız bir 8 bit bayta karşı imzalı bir 8 bit baytsa, bu durumda değişkeni referans alan kaynak kodundaki ifadeler, örneğin imzalı bayt yüklerine karşı imzalı bayt yüklerine çevrilir. (C) dilinin kurallarını yerine getirmek için gerektiği gibi. Böylece değişkenin türü, kaynak kodun makine komutlarına çevrilmesine kodlanır ve bu da CPU'ya, her defasında değişkenin konumunu kullandığında bellek veya CPU kayıt yerini nasıl yorumlayacağını emreder.
Bunun özü, CPU'ya, işlemcinin makine kodu talimat setindeki talimatlar (ve daha fazla talimatlar) yoluyla ne yapması gerektiğini söylememiz gerektiğidir. İşlemci az önce yaptığı veya söylenenleri çok az hatırlıyor - sadece verilen talimatları yerine getiriyor ve değişkenleri uygun şekilde işleyebilmesi için tam bir komut dizisi seti vermek derleyici veya derleme dili programcısının görevi.
Bir işlemci doğrudan bayt / word / int / uzun imzalı / imzasız, kayan nokta, çift vb. Gibi bazı temel veri türlerini destekler. İmzalı veya imzasız olarak aynı bellek yerine dönüşümlü olarak davranırsanız, işlemci genellikle şikayet etmez veya itiraz etmez. Örneğin, bu programda genellikle bir mantık hatası olurdu rağmen. Değişkenle her etkileşimde işlemciye talimat vermek programlama işidir.
Bu temel ilkel türlerin ötesinde, veri yapılarındaki şeyleri kodlamamız ve bunları bu ilkeller açısından manipüle etmek için algoritmalar kullanmamız gerekir.
C ++ 'da, polimorfizm için sınıf hiyerarşisinde yer alan nesneler, genellikle nesnenin başlangıcında, sanal gönderme, döküm, vb.
Özetle, işlemci aksi takdirde depolama yerlerinin kullanım amacını bilmiyor veya hatırlamıyor - CPU kaydında ve ana bellekte depolamayı nasıl değiştireceğini açıklayan programın makine kodu talimatlarını yerine getiriyor. Programlama, o zaman, depolamayı anlamlı bir şekilde kullanmak ve programı bir bütün olarak güvenilir bir şekilde yürüten işlemciye tutarlı bir dizi makine kodu talimatı sunmak için yazılımın (ve programcıların) işidir.
useT1(&unionArray[i].member1); useT2(&unionArray[j].member2); useT1(&unionArray[i].member1);
, clang ve gcc her ikisi de aynı türetilmiş olsa bile işaretçinin unionArray[j].member2
erişemediğini varsaymaya eğilimlidir . unionArray[i].member1
unionArray[]
eğer belli bir tipin değişkenini tanımlarsam, değişken tipini nasıl takip eder.
Burada iki ilgili aşama vardır:
C derleyicisi C kodunu makine diline derler. Derleyici, kaynak dosyanızdan (ve kütüphanelerden ve işini yapması için gereken diğer şeylerden) alabileceği tüm bilgilere sahiptir. C derleyicisi ne anlama geldiğini izler. C derleyicisi, bir değişken olduğunu bildirirseniz char
, bunun char olduğunu bilir .
Bunu, değişkenlerin adlarını, türlerini ve diğer bilgileri listeleyen bir "sembol tablosu" kullanarak yapar. Oldukça karmaşık bir veri yapısıdır, ancak bunu sadece insan tarafından okunabilen adların ne anlama geldiğini takip etmek olarak düşünebilirsiniz. Derleyiciden gelen ikili çıktıda, bunun gibi hiçbir değişken adı görünmez (programlayıcı tarafından istenebilecek isteğe bağlı hata ayıklama bilgisini yok sayarsak).
Derleyicinin çıktısı - derlenebilir çalıştırılabilir - işletim sisteminiz tarafından RAM'e yüklenen ve doğrudan CPU'nuz tarafından yürütülen makine dilidir. Makine dilinde, hiçbir şekilde "tür" kavramı yoktur - yalnızca RAM'deki bazı yerlerde çalışan komutlar vardır. Komutlar aslında onlar ile faaliyet sabit türü var mı (yani bir makine dili komutu "RAM yerleri 0x100 ve 0x521 saklanan bu iki 16 bitlik tamsayılar eklemek" olabilir), ancak hiçbir bilgi yoktur yerde o sistemde Bu konumlardaki baytlar aslında tam sayıları temsil ediyor. Tip hatalardan koruma yok hiç burada.
char *ptr = 0x123
C'deki gibi ). "İşaretçi" kelimesini kullanmamın bu bağlamda oldukça açık olması gerektiğine inanıyorum. Olmazsa, bana yardımcı olmaktan çekinmeyin, ben de cevaba bir cümle ekleyeyim.
C ++ 'ın çalışma zamanında bir tür depoladığı birkaç önemli özel durum vardır.
Klasik çözüm, ayrımcılığa uğramış bir birlikteliktir: çeşitli nesne türlerinden birini içeren bir veri yapısı ve şu anda hangi tür içerdiğini söyleyen bir alan. Şablonlu versiyonu olarak C ++ standart kütüphanede std::variant
. Normalde, etiket bir olur enum
, ancak verileriniz için tüm depolama alanlarına ihtiyacınız yoksa, bir bit alanı olabilir.
Bunun diğer bir yaygın örneği dinamik yazmadır. When class
a sahip virtual
işlevi, program o işlev işaretçisi saklayacak sanal fonksiyon tablosunun o her örneği için başlatır, class
bunun inşa edildiğinde. Normalde bu, tüm sınıf örnekleri için bir sanal işlev tablosu anlamına gelir ve her örnek uygun tabloya bir işaretçi tutar. (Bu, tablo tek bir işaretçiden çok daha büyük olacağından zaman ve bellek tasarrufu sağlar.) Bu virtual
işlevi bir işaretçi veya başvuru yoluyla çağırdığınızda , program sanal tablodaki işlev işaretçisini arar. (Derleme zamanında tam türü biliyorsa, bu adımı atlayabilir.) Bu, kodun temel sınıfın yerine türetilmiş bir türün uygulamasını çağırmasını sağlar.
Burada bu alakalı kılar şeydir: Her ofstream
bir gösterici içerir ofstream
sanal masa, her ifstream
üzere ifstream
sanal masa, vb. Sınıf hiyerarşileri için, sanal tablo işaretçisi, programa bir sınıf nesnesinin ne tür olduğunu söyleyen etiket görevi görebilir!
Dil standardı, derleyiciler tasarlayanlara çalışma süresini kaputun altında nasıl uygulamaları gerektiğini söylemese de, beklediğiniz dynamic_cast
ve typeof
çalıştığınız şey budur.