Gömülü C – 3 : Veri Tipleri, Değişkenler ve İsimlendirme

Any fool can write code that a computer can understand. Good programmers write code that humans can understand.Martin Fowler

Gömülü sistemler yazı dizisinin güzide yazılarından bir olan “Merhaba Dünya” yazısından sonra geldik artık veri tipleri ve değişkenlere. Tekrara düşmek manasız olduğundan sürekli söylemek istemesem de, bu yazı da bilinenlere referans vererek önemli başka konuları içerecek. Yani herhangi bir tutorial‘deki gibi bir bilgi seti beklemeyiniz 🙂

Şimdi malumunuz genelde bir program, bir problemi çözmek için yazılır. Problem demişken mevzu ille de negatif olacak diye bir şey yok tabi. Sadizmi de bir kenara koyuyorum. Anlatmak istediğim şu ki her programın bir amacı var. Bu amaç belli nedenlerle tetiklenir ki buna da problem diyelim. Her program, problemi farklı bakış açılarıyla modeller. C bunu yapısal bakış açısıyla modellemeye uygundur, C++ nesne yönelimli modellemeye uygundur, SequenceL misal fonksiyonel modellemeye uygundur falan filan.

Bu demek değil ki, C ile nesne yönelimli modelleme yapamayız, C++ ile yapısal modelleme yapamayız. Yaparız de bazen lezzetli olmaz. Bazen de çiçek gibi olur; evine, iş yerine koy seyret.

Neyse konuya dönecek olursak her program mantıken bir problemi ama öyle ama böyle modeller. İş o ki, programlama yapan adam bu modellemeyi kaliteli yapsın. Gel gelelim bu iş, hakikaten zor iş. Niye? Çünkü çok fazla parametreyi düşünmek gerekiyor ve bu parametrelerden bazıları birbirleriyle çatışıyor. Misal kodu daha efektif yapmak çoğu zaman anlaşılırlığı azaltır. Taşınabilirliği artırmak için kodun boyutu artar falan fişman. Madem bu kadar çok parametre var, nasıl state of art yazılım yapacağız? Hem de gömülü yazılım? Cevabı kısaca şu: güzel pratikleri takip ederek ve trade-off’ların (bir şeyi kazanmak için bir şeylerden fedakarlık etmeyi gerektiren durumların) FARKINDA olarak.

Sözün kısası yazının bu kısmında, güzel pratiklerin bir nevi besmelesi anlatılacak. Hemen her problemin modellenmesinde değişkenler kullanılır. Değişkenleri doğru kullanmak ve doğru isimlendirmek, anlaşılırlığa çok büyük katkı sağlıyor. Pek bir şeye de zarar vermiyor. Yani değişkenleri doğru kullanıp, doğru adlandırdığımızda hiç bir şey kaybetmeden bayağı bir kâra geçiyoruz. Bolca sevap kazanıyoruz 🙂

 

C dili için genelde şöyle bir tablo yapılır:

Tip Boyut(inş cnm yha) Değer aralığı(inş cnm yha)
char 1 byte -128 to 127 or 0 to 255
unsigned char 1 byte 0 to 255
signed char 1 byte -128 to 127
int 2 ya da 4 byte -32,768 to 32,767 or -2,147,483,648 to 2,147,483,647
unsigned int 2 ya da 4 byte 0 to 65,535 or 0 to 4,294,967,295
short 2 byte -32,768 to 32,767
unsigned short 2 byte 0 to 65,535
long 4 byte -2,147,483,648 to 2,147,483,647
unsigned long 4 byte 0 to 4,294,967,295

Yukarıdaki tablo şerdir, güvenilmezdir, kemdir. Zaten boyut kısmında inş cnm yazmaktadır.

Bu görmüş olduğunuz tipler C dilinin temel tipleri malumunuz. Şimdi size bir önerim olacak. Bu veri tiplerini gömülü sistemlerde ASLA KULLANMAYIN. Aklınızdan iki soru geçti hemen… Birincisi “niye?”, ikincisi “ne kullancaz o zaman?”. İkisine de nedenleriyle cevap vermeye çalışacağım.

Niye?

Çünkü standard veri tiplerinin boyutu platform bağımlıdır. Ne demek istiyorum? Örneğin X mikrokontrolöründe int veri tipi 2 byte iken, Y mikrokontrolöründe int veri tipi 4 byte olabilir. Yine Z mikrokontrolöründe int veri tipi 8 byte olabilir ve eşşeğin kulağına su kaçırabilir. Örneğin sizin bilgisayarlarınızda muhtemelen int 8 byte olacaktır. Bunun sebebi işlemcinin adres yolunun genişliğinin platformdan platforma farklı olabilmesidir. Bu kavram “void*” veri tipinde daha detaylı anlatılacaktır. Ancak şu söylenebilir ki 32-bit’lik platformlarda int veri tipi 32 bit yer kaplar ki bu da malumunuz 4 byte’a tekabül eder. Benzer şekilde 64 bitlik sistemlerde int 8 byte yer kaplar. Tabi bunlar, normalde olması gereken durumdur, derleyici; Ali Cengiz oyunları ile burada başka işler yapabilir. Sözün kısası ölçmeden veri tiplerinin boyutunu bilemezsniz. Bu sebeple int, long, short gibi değişkenler “platform safe (platform-ül emin)” değildir. Peki efendim nasıl ölçücez?

Ahanda böyle:

İsterseniz kodu BURADAN da derleyip çalıştırabilirsiniz.

Neyse derdimizi anlattık. Bu arada bir değişkenin limitlerini bilmiyorsanız yardımcı kütüpanelerle onlara da ulaşabilirsiniz. Misal float için şöyle işler de yapılabilir:

Gömülü sistemlerde, belleğin bilinçli şekilde kullanılabilmesi çok önemlidir. Programın bellek kullanımının platformdan platforma değişmemesi ise, bir çok hatanın önüne geçmemizi sağlar. Örneğin iki adet gömülü sistemin haberleştiğini düşünün. Birinde(X) int 32 bit olsun diğerinde(Y) ise 64 bit olsun. X’den Y’ye olan haberleşmede aynı kodları kullanmak istediğimizde Y’de daha az değer okumaya başlarız. Aynı sayıda değer okumaya kalkarsak da yanlış değer okuruz. Keza X’de bir struct’ı olduğu gibi sd carda yazarsak, ve Y’de o sd cardı aynı struct ile okumaya kalkarsak, eğer struct; int, float gibi platform safe olmayan değişkenler içerirse verileri yanlış okuruz. Misal aşağıdaki struct’ın kaç byte yer kaplayacağını soralım:

Kaç byte kaplar? Cevap aşağıda…

ÖNEMLİ BİLGİ
Platformdan platforma kaç byte kaplayacağı değişir. Ama misal 2+4+4+8+8 = 26 Byte kaplaması şaşırtmazdı. Yine de platform bilinmiyorsa net bit şey söylenemez.

Bu gibi bir çok sebeple, öntanımlı veri tiplerini gömülü sistemlerde kullanmamaya gayret etmekte fayda var. Ama tabi yeri gelir, ne yaptığımızı bilerek, bedelini göze alarak ve sisteme zarar vermesini önleyerek kullanabiliriz.

Neyse… Niye sorusunun cevabını verdik gibi. Şimdi diğer soruya geçelim 🙂

Nasıl?

Ne kullanacağız öyleyse? Bu sorunun cevabını iç dünyamızda aramayacağız. Cevap C’nin standard kütüphanelerinden birinde gizli. Anlı şanlı “stdint.h” !!! Evet bu mübarek kütüphane, bir çok problemi tarihin tozlu sayfalarına gömüyor.

Cengaver gibi stdint tablosuna hemen bir göz atalım.

Degisken İşareti Bits Bytes Minimum Değer Maksimum Değer
int8_t Signed 8 1 −27 = −128 27 − 1 = 127
uint8_t Unsigned 8 1 0 28 − 1 = 255
int16_t Signed 16 2 −215 = −32,768 215 − 1 = 32,767
uint16_t Unsigned 16 2 0 216 − 1 = 65,535
int32_t Signed 32 4 −231 = −2,147,483,648 231 − 1 = 2,147,483,647
uint32_t Unsigned 32 4 0 232 − 1 = 4,294,967,295
int64_t Signed 64 8 −263 = −9,223,372,036,854,775,808 263 − 1 = 9,223,372,036,854,775,807
uint64_t Unsigned 64 8 0 264 − 1 = 18,446,744,073,709,551,615

Önceki tabloda, yani int,char gibi öntanımlı tiplerin yer aldığı tabloda, boyut kısmında “inş cnm” vardı. Sıkıyorsa burada da olsun… Olmaz çünkü bu değişkenlerin hepsinin boyutu SABİT. Hangi platforma giderseniz gidin, uint32_t, işaretsiz olan 32 bitlik bir değişkendir. İşte bu! Yani biz bu değişkenleri kullanarak bir yazılım ortaya koyduğumuzda, yarın öbür gün o kodu başka platforma taşısak da yazılımımızın bellek kullanımı aynı olacak. Buna ne denir? Çiçek çiçek…

ÖNEMLİ BİLGİ
Gömülü yazılımlarda, yalnızca stdint kütüphanesinden değişkenleri kullanmak bir nevi sevaptır.

O çok sevilen char bile, platformdan platforma işaret değiştirebilmekte. Velhasılkelam bunların hiç birine güven olmaz. İyisi mi stdint kullanın. Hiç zararı yok desem yeridir, ama sayısız faideleri mevcuttur.

Bununla birlikte, ikinci sorumuzu da yanıtladık. Şimdi geldik değişkenlere.

DEĞİŞKENLER

Şimdi değişken dediğimiz nane, bellek adreslerinin güzelce etiketlenmesinden başka bir şey değildir. Misal int i=32 diye bir değişken yarat…. Şeytana uymayın int filan yok 🙂 Baştan alalım. Misal şöyle bir değişken yaratalım.

Derleyici biz bu değişkeni yarattığımızda ne yapar? Gider ram’e şunu der: “Abi müsaitse ben bi 4 byte yer alayım içine de arkadaşlar bi zahmet 1988=0x000007c4 yazsınlar.” Bunu duyan ram cevap verir “Abi bakalım varsa itin olsun.” Bunu aslında ram’im kendisi değil de yöneticisi (işletim sistemi, vs) söyler. Ve neticesinde atıyorum 0x10208032 adresine sizin 1988 sayısı 4 byte olarak yerleşir. Big endian veya little endian olmasına göre verinin belleğe yerleşim sırası değişebilir. değişken uint32_t yerine int32_t olsa yine aynı muhabbet olacaktı 🙂 Ama int16_t olsa idi veri 0x07C4 olarak belleğe yerleşecekti. Neyse.

Bu değişkenin değişmeyen çeşidi de var ilginçtir. Misal aşağıdaki değişken, değişmeyen bir değişkendir ve değeri derleme aşamasında bir kez belirlenir ve o değişkenin değeri bir daha asla değişmez.

Değişkenin başına const koyduğumuz zaman derleyiciye deriz ki, bak bilader bu değişkenin değeri asla değişmeyecek. Dolayısıyla sen bunu zırt pırt değişmeyecek bir bellekte saklayabilirsin. Derleyici de gider o değişkeni ram’e değil flash’a ya da hard diske ya da genel adıyla NVM’ye(non-volatile memory) koyar. Hmm demek ki değişken demekle aslında bellekteki bir veri gurubunu etiketlemekten söz ediyoruz. Nitekim belleğin adresinden ziyade değişkenin ismini akılda tutmak daha kolay 🙂

Peki kardeşim bu değişkenin değeri değişeni var değişmeyeni var. Yeri değişmeyeni istersek ne yapacağız biz? O zaman da değişkene durduğun yerde dur diyeceğiz. Nasıl mı? Böyle:

Şimdi gelelim başka bir mevzuya. Peki ya bizim değişkenimiz öyle ansızın değişebilmeliyse? Ram’e erişmek görece çok hızlı olsa da sonuçta işlemcinin dışındaki bir bellekten söz ediyoruz ve haberleşme için bir miktar zamanı paşa paşa kaybediyoruz. Öyle bir imkan olsa ki çok acil durumlar için daha hızlı bir çözüm olsa. Var efendim, işlemcinin register’ları var. Biz derleyiciye şunu diyebiliriz. “Abicim eğer yer varsa lütfen bi zahmet bunu işlemcinin register’inda sakla. Yoksa da optimize filan etme kolayda tut lazım olunca hemen çekelim.” Derdinizi anlatmayı bilirseniz derleyici dileğinizi yerine getirir. Bunun için ona şunu yazmalısınız:

Bunları karıştırmak mümkün. Misal static const dediğinizde iki isteğiniz aynı anda gerçekleştirilir. Ancaak birbiriyle çatışan istekler aynı anda kullanılamaz. Mesela const volatile diyemezsiniz 🙂 İşin temel mantığı budur. Bunu bildikten sonra bu operasyonların felsefi yorumlarını yapmak hakikaten zor değil. Misal static olan bir değişkenin yeri bellekte değişmediğinden, o değişken son değerini her zaman korur. Bütün C tutorial’larnda static değişkenin fonksiyon içinde yalnızca bir kez initialize edildiğini herkes anlatır. İşte onun altına yatan nedeni de burda buldunuz. Artık aynı mantıkla hepsini yorumlamak mümkün 🙂 Misal şu bilgi de her yerde var; bir c dosyasında global olara tanımlanmış statik bir değişken yalnız o dosyada kullanılabilir. Neden? Çünkü derleyici her  dosyayı bir obje olarak derler. Sonra da objeler linker dayı tarafından birleştirilir. Bir objede bir değişkenin yeri sabit tutuluyorsa o bellek alanı, sinemada rezerve edilen koltuğa benzer. Başkasına satmazlar 🙂 Neyse işte bu bilgi setiyle tüm bu yorumları yapmak mümkün.

Bir de extern ön eki var ama kendisini hiç mi hiç sevmem. Kullanımı bence kötü mimarinin habercisidir. Uzaklardaki, başka dosyalardaki değişkene erişmek için de, fonksiyona erişmek için de header dosyası kullanımı daha mantıklıdır. Zamanı gelince ona da daha detaylı değineceğim.

Bizim yazdığımız kod derlendiğinde kodun işletilecek olan kısmı ile, değeri sabit olan değişkenler flash’a atılır. Değişecek değişkenler, fonksiyon stack’leri ise ram’de tutulur. Burda dikkat edilmesi gereken konu fonksiyonun kendisinin genelde flash’ta tutulduğudur. Ama fonksiyon argümanları filan ram’den kopyalanabilir. Neticede fonksiyon da bellek içindeki bir veri. C dilinde fonksiyonlar dahil olmak üzere her şey değişken çeşidirir demek esasen mümkün. Fonksiyon farklı olarak işletilebiliyor. Yani bellek içindeki veri, işlemci için anlamlı bir operasyon koduna (opcode’a) tekabül ediyor. Ama sonuçta değişken mi? Paşa paşa değişken 🙂 Sadece daha pahalı bir değişken 🙂 O zaman benzer kurallar fonksiyonlar için de geçerli 😉

Neyse bu kısmı da çeşitli ibretlerle atlattık. Gelelim bir diğer önemli kısıma. İsimlendirme….

Sen bellekten yer ayırmışsın, adını düzgün koymadıktan sonra neye yarar? Bir programda eğer, devreye bağlı olan LED’in açık mı kapalı mı olduğunu anlatan değişkenin adı fiveVFlag ise ühüüüü yandı onu okuyan, yandı onu geliştirmeye çalışan… Ama asıl onun adını  fiveVFlag koyan yandı çünkü kulakları çok çınlar. isCommErrorLedOn koysan değişkenin adını eline mi yapışır. Bu yazının başında bir meşhur söz paylaşmıştım. Dayı ne güzel demiş… Kodu öyle yazmak lazım ki, insan anlasın insan… Kodu okuyan kimse, kodlama yapan kişinin niyetini çok açık şekilde anlamalıdır. Ancak anlamalıdır derken, anlamasını sağlama sorumluluğu kodu yazandadır. Hatta sorumluluk öylesine kodu yazandadır ki, okuyanın işini kolaylaştıran hemen her şeyi düşünmek zorundadır kodu yazan.  Aynı zamanda kod self-documenting olmalıdır yani dökümana gerek kalmaksızın işlevini açıkça anlatabilmelidir. Kod, konuyla alakası olmayan biri tarafından bile anlaşılabilmelidir. Sadece başkası için düşünmeyin, yazdığınız koda 2 sene sonra bakmanız gerekirse siz de tam olarak sıfırdan başlayacaksınız. Her zaman bunun korkusu ile ve bilinci ile kod yazmakta sayısız faideler var. Şimdi bunları biraz somutlaştıralım.

Macar Notasyonu

Macar notasyonu (Hungarian Notation) der ki değişkenin ismini öyle güzel yaz ki hem derdini anlatsın hem de tipini. İyi demiş ne güzel demiş ama Macar Notasyonu bile çoğu kişi tarafından farklı farklı yorumlanmış, temeli aynı kalmak kaydıyla farklı estetiklerle uygulanmıştır. İnternette arattığınızda Macar notasyonu ile ilgili çok farklı örneklere rastlayacaksınız. Ama dediğim gibi amacı hep aynıdır, tip bilgisini vermek ve açıklayıcı olmak. Misal:

Buna rağmen notasyon seçimde tecrübeler der ki, çalışılan platform çok çok önemlidir. Mesela çalışma ortamı Visual Studio olacaksa, ve hep öyle kalacaksa tip bilgisini değişken ismine gömmek ahmakçadır. Çünkü kodu yazan kişi değişkeni çağırırken tipi zaten hintBox’da otomatik görünmektedir. Keza o değişkeni başka bir değişkene atayacakken değişkenin adını yazmaya başladığında değişkenin tipi zaten hintBox’da çıkmaktadır. O zaman ne gereği var? Bir de değişkenin tipini değişkenin başına koymak auto code completition (otomatik kod tamamlama) olan geliştirme ortamlarında sorun yaratacaktır. Çünkü siz ip adresini kullanmak istediğinizde onun string olmasından daha çok ip adresi olmasıyla ilgileneceğinizden değişkeni yazmaya ipAddress diye başlayacaksınız. Eğer ortamınız VisualStudio ise yine yırttınız ama Eclipse’in bu günkü bir versiyonu ise yandınız çünkü değişken ipAddress ile değil de str ile başladığı için auto code completition yapılamayacak değişken bulunamayacaktır. Ama Keil uVision gibi taş gibi, ama taş devrinden kalmış izlenimi veren dümdüz ama delikanlı bir IDE ile çalışıyorsanız tip bilgisini değişkene ekleseniz iyi edersiniz 🙂

Benim isimlendirme stilimi bu yazı dizisi boyunca yeterince tanıyacağınızdan şimdi detaylıca anlatmıyorum 🙂 Siz de kendinizinkini oluşturmakta özgürsünüz, ya da bir standardı kullanabilirsiniz [ki belki daha iyi bir seçim olabilir, tartışılır] ama her durumda kafanızdakini Blale anlatırcasına açıkça koda aktarmakla yükümlüsünüz.

Efendim değişken derken ben fonksiyonları da ayırmadığımı söylemiştim. Fonksiyon, asıl işi yapacak olan yazılım bloğu olduğundan çok doğru isimlendirilmeli, çok doğru yaratılmalıdır. Çoğu zaman fonksiyon isimleri, hatalı kodları ele vermektedir. Nasıl ki yazılan değişken neyi temsil ettiğini açıkça anlatmalı ise, fonksiyon da yapacağı işi açıkça anlatmalıdır. Aynı zamanda her fonksiyon yalnızca ama yalnızca bir işi yapmalıdır. Bunu ilerde fonksiyonlar kısmında daha detaylı anlatacağım ama misal şu fonksiyonun mesela isminde hayır olmadığından kendinin de hayırlı bir iş yapmadığı açıktır:

Arkadaş bir kere bu fonksiyonun adı o kadar saçma ki, C dilini ve bu hayali mikrodenetleyiciyi bilen hemen her kes bu kodun iki sayıyı toplayıp seri porta yazdığını görebilir. İyi de niye yazdı? Fonksiyonun adının bunu anlatması gerekir. Ki yazımı da çok kötü zaten ya neyse. Hadi biraz düzeltelim madem.

Artık her fonksiyon adına yanaşır şekilde bir iş yapıyor ve kodun iki sayıyı toplayıp seri porttan yollamakla hangi problemi çözdüğünü anlayabiliyoruz. Ayrıca belli ki auto code completition olmayan bir yerde kodu yazmışız. Bir diğer önemli nokta ise kodun dili. Kodun yarısı İngilizce yarısı Türkçe olmamalı. Hangisini seçtiyseniz ona sadık kalın. Normalde C dilinin anahtar sözcükleri İngilizce olduğundan ben tamamını İngilizce yazmayı tercih ediyorum. Ancak İngilizce bilmeyen arkadaşlar da anlatmak istediğim şeyi anlayabilsin diye bu defa Türkçe yazdım. Union kısmını anlamadıysanız şimdi ona çok takılmayın. İbret alınacak başka noktalara bakalım. İlk fonksiyonun adının ne kadar amaçsız olduğu konusunda anlaştığımızı sanıyorum. Bir diğer hata da isminden de anlaşıldığı üzere iki işi birden yapmaya çalışması. İşte bu tekrar kullanılabilirliği öldürüyor. Üstteki koddaki adam seri porta başka bir şey yazmak istediğinde kod tekrarı yapacak ve birinde hata yaparsa belki hatayı bulamayacak. Alttaki kodda ise bilgisayaraByteGonder fonksiyonu çiçek gibi çalıştığından çağırıldığı her yerde hataya mahal vermeksizin çalışmaya devam edecek. Ayrıyeten bilgisayara bilgi gönderecek her fonksiyon onu tekrar tekrar kullanabilecek. Bu nedenle fonksiyon isminde and, or, if filan var ise bilin ki o fonksiyon çok çirkin bir fonksiyondur. Ayrıca isimlendirme, dilin anlatamadığını anlatmayı hedeflemelidir. Değişkenin adını uzun yazmakta bir sakınca yoktur. Derleyici zaten derleyince onu adrese dönüştürecektir. Bir harfle isimlendirilen değişken de, uzunca ismi olan değişken de tipi aynı olduğu takdirde aynı miktarda yer kaplayacaktır.

Güzel kodlama pratikleri üzerinde ileriki konularda da konuşacağız. Şimdilik görüşmek üzere 🙂

 

Önceki Sayfa   Sonraki Sayfa

One thought on “Gömülü C – 3 : Veri Tipleri, Değişkenler ve İsimlendirme

Leave a Reply