Gömülü C – 5 : Operatörler

Önceki yazılarda, bir C programı hakkında feyizli bir takım konular üzerinde konuşmuştuk. Merhaba dünya yazılımının anatomisinden sonra ibretlik veri tiplerini, değişkenler ile onlara atanacak sabitleri, ardından da alımlı kodun olmazsa olmazı yorum satırlarını incelemiştik. Bu defa ise, C dilindeki bir diğer önemli başlık olan operatörleri inceleyeceğiz.

Dünyada çeşit çeşit operatör vardır ancak ne mutlu bize ki C dilindeki operatörler çeşitleri sınırlı sayıda. 🙂 Neyse, “operatörlere özgü bir yazı” olması, ilk bakışta biraz garip guraba gelebilir ama, operatörler özellikle gömülü programlamada “çok bilinçli şekilde kullanılmak zorunda” olduğundan değer verilmesi gereken önemli bir konu. Bizim de niyetimiz operatörlerin hak ettiği saygınlığı kazanmasına yardımcı olabilmek.

Bazen aynı amaç, farklı operatörler ile gerçeklenebilir. Hatta genelde durum böyledir. Bu aşamada, hangisini seçmenin daha akıllıca olacağına karar vermenizi sağlayacak birtakım ince noktalara burada değinmeye çalışacağım. Operatörlerin doğru kullanımında dahi bazı kazan/kaybet (trade-off) durumları olduğundan, yine ibretlerle dolu bir yazının şanlı başlangıcında olduğumuzu hissediyorum. Haydi hayırlısı…

Aritmetik Operatörler

C dilinde aritmetik operatörler, bizleri çocukluğumuza, ilkokul sıralarına götürür 🙂

Not: Operatörlerin uygulandığı değişken veya sabitlere genel olarak operand denebilir. A=10, B=20 için;

Operatör Tanım Örnek
+ iki operandı toplar A + B = 30
Soldaki operanddan sağdaki operandı çıkarır. A – B = -10
* operandları çarpar A * B = 200
/ soldaki operandı sağdaki operanda böler B / A = 2
% Mod operatörü, bölmeden sonra kalanı verir B % A = 0
++ Bir artırma operatörü A++ = 11
Bir azaltma operatörü A– = 9

Aritmetik operatörleri etkin şekilde öğrenmenin en iyi yolu, bu operatörleri kullanarak programlar yazmak ve bunları çalıştırarak sonuçları görmektir. Benim derdim, operatörlerin ne iş yaptığını anlatmak olmamalı diye düşünüyorum. Herhangi bir C dersinde/kitabında bu zaten alenen anlatılıyor. Aşağıdaki örnek yazılımı yazmamın ardından asıl anlatılması gerken ince noktalara geçeceğiz.

Yukarıdaki kod çalıştırıldığında, çıktısı aşağıdaki gibi olur:

Toplama: 30.000000 Cikarma : -10.000000 Carpma: 200.000000 Bolme: 0.500000 Mod: 10
++ sonra kullanilinca i:30
bir sonraki adimda i:31
++ once kullanilinca i:31
bir sonraki adimda i:31

Önemli bir nokta olarak i++ yazdırıldığında, önce i’nin değerinin yazdırılıp ardından bir artırıldığında dikkat ediniz. Aynı şekilde ++i yazdırıldığında, önce i’nin değerinin bir artırıldığında ardından bu değerin yazdırıldığına çok dikkat ediniz. İnananlar için burada büyük ibretler var. ++ opratörünü bu bilinçle kullanmakta faydalar var.

Aritmetik operatörlerle ilgili önemli bir diğer nokta da öncelik sırası kavramı. Öncelik sırasına takılmamak ve kodu okunur kılmak adına, ardışıl aritmetik işlemlerde parantez kullanaraktan sorunu teğet geçmekte fayda var. Örneğin 13+227-12/3 ile ne demek istediğimi anlayamayabilirsiniz, demek istediğimi yanlış diyor da olabilirim ama 12+(227)-(12/3) ile ne demek istediğimiz çok açık olur. C dilinde malum her zaman parantezin içi öncelikli oluyor. Hem hataları önlemek için hem de toplum içerisinde anlaşılabilir olmak için bu alışkanlığı edinmekte faiderler, feyizler var.

Aritmetik operatörlerle ilgili bir diğer önemli nokta da, yerli yersiz kullanılmamaları. Kimi zaman (hatta çoğu zaman), bir işi yapmanın birden fazla yolu oluyor. Örneğin 8 bitlik iki sayıyı alıp yanyana koyup 16 bitlik tek bir sayı elde etmek istediğimiz yerler oluyor. Bunu yapmak aşağıdaki şekillerde mümkün:

Bazı platformlarda toplama, çarpma gibi aritmetik işlemler atomik değildir, yani bu operatörlerin işletilmesi 1 saat darbesi süresinden (1 cycle) fazla sürer. Halbuki her işlemcide AND,OR işlemleri tek cycle’da yapılır. Buna göre eğer yukarıdaki kodu MSP430 serisi mikrokontrolörler için yazıyorsanız, yöntem 0 sizi canınızdan bezdirir çünkü eğer HW multiplicator olmayan bir modelde çarpma 130 cycle kadar sürer, HW multiplicator olan bir modelde bile çarpma 8 cycle sürer. Daha bunun toplaması ve ataması da var. Halbuki Yöntem 3’te tüm olay hepi topu 3 cycle’da biter. Yöntem 4’ün ise çok sanatsal bir amacı var, umarım anlamışsınızdır ancak anlamadıysanız dahi ileride detaylıca anlatacağım zaten.

Neyse demek ki yerli yersiz aritmetik operatör kullanmamak lazımmış. Gerçi Arm Cortex M3 mimarisinde aritmetik operatörler de 1 cycle sürüyor (non-fp için). Orada elinizi korkak alıştırmayabilirsiniz ama yine de gün gelir o kodu 8 bitlik bir mikrokontrolöre taşımanız gerekirse başınız ağrır. En güzeli, en baştan çiçek gibi bilinçle yazmak 🙂

İlişkisel Operatörler/ Karşılaştırma Operatörleri

İlişkisel operatörler sağ ve solundaki operandlar belirli bir mantıksal ilişkiye göre karşılaştırılır ve eğer ilişki doğru ise doğru döndürülür, yanlışsa yanlış. 😀

Operatör Tanım Örnek
== İki operand birbirine eşitse, sonuç doğru(true,1) olur (A == B)
!= İki operand birbirine eşit değilse, sonuç doğru(true,1) olur (A != B)
> Soldaki operand sağdakinden büyük ise, sonuç doğru(true,1) olur (A > B)
< Soldaki operand sağdakinden küçük ise, sonuç doğru(true,1) olur (A < B)
>= Soldaki operand sağdakinden büyük ya da eşit ise, sonuç doğru(true,1) olur (A >= B)
<= Soldaki operand sağdakinden küçük ya da eşit ise, sonuç doğru(true,1) olur (A <= B)

Bu kısımla ilgili verilecek ibretlik mesajı önceki kısımlarda vermiştik. Ancak tekrar bir hatırlatma yapmak gerekirse;karşılaştırma operatörleri genellikle if,while gibi koşul/döngü işlemlerinde kullanılır ve doğal olarak en çok hata da buralarda yapılır. Örneğin sık yapılan hatalardan biri if(age == 18)  yazacakken yanlışlıkla if(age=18) yazmaktır. Bu durumda age değişkenini 18 ile karşılaştırmak yerine, age değişkenine 18 atamış ve dönüş olarak true almış olursunuz. If içinde atama yapmak mümkün olduğundan derleyici buna hata vermez, uyarı verebilir. Bu hatayı hiç yapmamanın yolu if(18 == age) formatını kullanmaktır. Yani sabiti sola, değişkeni sağa almaktır. Bu durumda yanlışlıkla if(18 = age) yazsanız bile, derleyici size sabite değişken atanmaz diye hata verir. Bu arada 18 yazdım ama, örnek anlaşılsın diyerekten sayıyı doğrudan yazdım 🙂 Yoksa MAGIC_NUMBER kullanmak yok 😉 Önerdiğim formatta yazacak olursam eğer if(ageOfDriverCandidate == AGE_LIMIT_TO_DRIVE) yerine if(AGE_LIMIT_TO_DRIVE == ageOfDriverCandidate) yazmakta fayda var. Ya da bu hususta sık hata yapıyorsanız, bir makro yaratarak, ve onu bir kez test ederek bu sorunu tarihin tozlu sayfalarına gömebilirsiniz :

 

Mantıksal Operatörler

Bazen birden fazla koşul aynı anda değerlendirilmek istenebilir. Bu durumda mantıksal ilişkiye göre (ve,veya,değil) koşullar değerlendirilebilir.

Operatör Tanım Örnek
&& Mantıksal VE(AND) operatörü çağırılır. Eğer iki taraf da sıfırdan farklı ise, sonuç doğru(true) olur. (A && B)
|| Mantıksal VEYA(OR) operatörü çağırılır. Eğer iki taraftan herhangi biri sıfırdan farklı ise, sonuç doğru(true) olur. (A || B)
! Mantıksal DEĞİL(NOT) operatörü çağırılır. Operandın mantıksal tersi alınır; operand 1 ise sonuç 0, operand 0 ise sonuç 1 olur. !(A && B)

Mantıksal operatörler birleştirilerek kullanılabilir. Ancak insan aklının almayacağı uzunlukta bir koşul söz konusu ise, o koşul ya indirgenmeli, ya da yorum satırları ile açıklanmalıdır. İnsan aklı derinlik olarak ortalama 7 seviye ilişkiye kadar hafızada tutabilir. Fazlası olmaz 🙂 if içinde 9 tane koşul yazmamakta fayda var.

Bitsel/İkili (Bitwise/Binary) Operatörler

Esasen bitsel operatörlerin ciğerini BURADA, çiçek gibi açıkladık. Hatta elektronik olarak nasıl gerçeklendiğini de konuştuk. Ancak tabi ki olayı bir de yazılım perspektifinde incelemekte sayısız feyizler var. Zira gömülü sistemlerde kimi zaman bir bitin bile hesabı yapılır. Hesabının yapılmadığı yerde bile, kullanılan mikrokontrolörün bir registerındaki bir biti 1 ya da 0’a çekmek için bitsel operatörler kullanılabilir. Bu söylediğim ilk aşamada bir anlam ifade etmemiş olabilir, ancak ileride değineceğimiz üzere mikrokontolördeki donanımı kullanabilmek için, genişliği genelde işlemcinin adres yolu genişliği kadar olan (8/16/32 bit), ayırılmış özel bellek alanlarına (register) bir takım veriler yazmak gerekir. Belleği verimli kullanmak için bir register içindeki her bitin ayrı ayrı işlevi olur. Bu nedenle registerlara bitsel olarak erişmek gerekir. Burada da genellikle bitsel operatörler kullanılır. Yani mevzu, o kadar önemli ki, ateş ediyor =)

Misal A = 60; ve B = 13; olsun. Bunların ikili tabanda gösterimi şöyle olur:

A = 0011 1100

B = 0000 1101

Buna göre

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

Operatör Tanım Örnek
& Bitsel VE Operatörü (A & B) = 12, yani 0000 1100
| Bitsel VEYA Operatörü (A | B) = 61, yani  0011 1101
^ Bitsel XOR Operatörü (A ^ B) = 49, yani  0011 0001
~ Tümleyen operatörü. Operandın 1 olan bitlerini 0, 0 olan bitlerini de 1 yapar. (~A ) = -61, yani  1100 0011
<< Sola kaydırma operatörü. Soldaki operandın bitleri, sağ operand kadar sola kaydırılır ve her kaydırmada en sola 0 eklenir. (Karışık oldu, örnek üzerinden görmekte fayda var) A << 2 = 240 yani 1111 0000
>> Sağa kaydırma operatörü. Soldaki operandın bitleri, sağ operand kadar sağa kaydırılır ve her kaydırmada en sağa 0 eklenir.(Karışık oldu, örnek üzerinden görmekte fayda var) A >> 2 = 15 yani 0000 1111

Şimdi gelelim fasülyenin faydalarına 🙂 Yani her yerde yazmayan önemli bilgilere…

& (VE) operatörü ile maskeleme yapılarak bir sayının 1 olan bitlerini 0’a çekmek yani temizlemek mümkündür. Misal:

 

| (VEYA) operatörü ile maskeleme yapılarak bir sayının 0 olan bitlerini 1’e çekmek yani set etmek! (kurmak) mümkündür. Misal:

^ (XOR) operatörü candır! Bu operatör ile maskeleme yaparak bir sayının herhangi bir bitini (ya da bitlerini) terslemek mümkündür.

Ek olarak kaydırma operatörünün, cyclic olup olmadığı her platformda mutlaka test edilmelidir. Normalde cyclic olmaz, ama test etmekte fayda var =)

Yazdığım örneklerde zaten kaydırma operatörünün kullanımı da var. Bir de ^=, +=, |= gibi örneklerden atama operatörünün diğer bitsel ve aritmetik operatörlerle birleştirilebildiğini de göstermiş olduk diye düşünüyorum.

Bu gün bu yazımı Mustafa Keser üstadımızın sözü ile sonlandırıyorum.

“Eveeeeet, bir proğramın daha sonuna geldiiik. Tekrar görüşmek üzere efeem.”

 

Önceki Sayfa   Sonraki Sayfa

 

2 thoughts on “Gömülü C – 5 : Operatörler

  1. M.Fatih BAYRAM

    Merhaba Özen Hocam,

    Yazılarınızı sırayla özümseye özümseye okuyorum. Yazılarınızın devamını da bekliyoruz. Ülkemizde gömülü sistemlerde sizin gibi değerli insanların olması bize gurur veriyor. Umarım sizin gibi bir çok üstada da örnek olur. Yazıların anlatımı da çok güzel. Çok teşekkür ederiz.

    Gözüme birkaç yer takıldı.
    *Bir azaltma operatörü tek eksi ile değilde 2 eksi ile olmalı
    *Yöntem4 de union içinde “uint8_t au8Data[2]; “olarak tanımlamışsınız ancak programın devamında u8Data olarak kullanılmış
    *| (VEYA) operatörü ile maskeleme kısmında 2. yöntemde araya ve operatörü girmiş.
    *rockie out rookie in 🙂

    1. Merhaba Fatih Bey,

      Öncelikle geri bildirimleriniz için ve yorumlarınız için çok teşekkür ederim. Bulduğunuz hataları paylaştığınız için ayrıca teşekkür ederim. İlki dışında diğerlerini düzelttim. İlkinde de yazıda — (iki eksi ile) yazdım ancak font dolayısıyla tek eksi gibi gözüküyor. Onun için kısa zamanda yazıların fontunu değiştireceğim 🙂

      Yeni yazıları da olabildiğince hızlı şekilde eklemeye çalışacağım 🙂

Leave a Reply to M.Fatih BAYRAM Cancel reply