Aynı sıcaklık ölçümü bir projede üç dilden geçebilir. Karttaki mikrodenetleyici sensörü C ile okur, robotun kontrol bilgisayarı veriyi C++ ile işler, test bankosundaki masaüstü uygulaması sonucu C# ile grafiğe döker. C, C++ ve C# farkları tam bu iş bölümünde görünür. Üç dilin sözdizimi birbirine benzer, ama donanıma ne kadar yaklaştıkları, belleği kimin yönettiği ve kodun hangi ortamda çalıştığı birbirinden çok farklıdır.
Kısa cevap şu: C donanıma en yakın dildir ve belleği tamamen programcıya bırakır. C++, C’nin üzerine sınıf, şablon (template) ve RAII gibi soyutlamalar ekler ama yine doğrudan makine koduna derlenir. C# ise .NET çalışma ortamında çalışan, belleği çöp toplayıcıyla yöneten bir dildir. Adındaki benzerliğe rağmen C++’ın yeni sürümü değildir.
C, C++ ve C# farkları tek tabloda
Aşağıdaki tablo üç dili, dil seçerken belirleyici olan beş ölçüte göre karşılaştırıyor.
| Ölçüt | C | C++ | C# |
|---|---|---|---|
| Programlama yaklaşımı | Prosedürel | Prosedürel, nesne yönelimli, şablonlar | Nesne yönelimli ağırlıklı |
| Bellek yönetimi | Manuel (malloc / free) | RAII, akıllı işaretçiler | Çöp toplayıcı (GC) |
| Derleme | Doğrudan makine kodu | Doğrudan makine kodu | Ara dil (IL) ve JIT, istenirse Native AOT |
| Çalışma ortamı | Gerekmez | Gerekmez | .NET çalışma ortamı |
| Tipik kullanım | Mikrodenetleyici, işletim sistemi çekirdeği, sürücü | Oyun motoru, robotik, simülasyon | Masaüstü, web, test ve HMI araçları |
Satırlar birbirinden bağımsız değildir. Çöp toplayıcı bir çalışma ortamı ister, çalışma ortamı da bellek ister. C ve C++ böyle bir ortam taşımadığı için 2 KB SRAM’li bir Arduino Uno’da, işletim sistemi bile olmadan çalışır. C#’ın aynı karta sığmamasının nedeni dilin sözdizimi değil, arkasındaki çalışma ortamıdır.
Üç dil nasıl akraba, donanıma ne kadar uzak
C, 1970’lerin başında Bell Labs’te Dennis Ritchie tarafından UNIX işletim sistemini yazmak için geliştirildi. UNIX çekirdeği 1973’te C ile yeniden yazıldı. Böylece sistem yeni bir donanıma assembly ile baştan yazılmadan, yeniden derlenerek taşınabildi. C’nin bugün gömülü sistemlerde bu kadar yaygın olmasının kökü bu tasarımdadır: dil, işlemcinin yaptığı işi gizlemez.
C++, Bjarne Stroustrup’un 1979’da “C with Classes” adıyla başladığı çalışmadan doğdu. C++ adı 1983’te kullanılmaya başlandı. C ile C++ farkı ince bir çizgide başlar: C++, C’nin neredeyse tamamını kapsar ama onun birebir üst kümesi değildir. Stroustrup’un kendi SSS sayfasında verdiği örneklerden biri, malloc sonucunu tür dönüşümü yazmadan bir int işaretçisine atamaktır. Bu satır C’de geçerlidir ama C++’ta derlenmez, çünkü C++ void* türünden başka bir işaretçi türüne örtük dönüşüme izin vermez.
C# ise bu soyağacının dışından gelir. Microsoft’un .NET platformu için geliştirdiği dilin ilk sürümü 2002’de Visual Studio .NET ile yayımlandı. Microsoft’un kendi dokümantasyonuna göre bu ilk sürüm Java’ya çok benziyordu. Süslü parantez ve noktalı virgül C ailesinden gelir, ama çalışma modeli C++’tan çok Java’ya yakındır.

Üç dil de güncellenmeye devam ediyor. Eylül 2026 itibarıyla C’nin yürürlükteki standardı C23 (ISO/IEC 9899:2024), C++’ınki ise C++23’tür (ISO/IEC 14882:2024). C++26’nın teknik çalışması Mart 2026’da tamamlandı. C# tarafında güncel sürüm, .NET 10 ile gelen C# 14. Sahadaki projeler ise çoğu zaman daha eski bir standardı hedefler. Linux çekirdeği bile genellikle GCC ile, C11’in GNU lehçesinde (-std=gnu11) derlenir. Bir gömülü projeye girerken ilk bakılacak şey dilin en yeni özelliği değil, derleyicinin hangi standardı desteklediğidir.
Bellek yönetimi: manuel, RAII ve çöp toplayıcı
C# ile C++ farkı, hatta üç dil arasındaki fark en çok bellekte belirginleşir. Çünkü bellek modeli hem karşılaşılan hata türlerini hem de zamanlamayı belirler.
C’de malloc ile alınan bellek free ile geri verilir. Unutulursa bellek sızar. İki kez verilirse ya da verildikten sonra kullanılırsa program tanımsız davranışa girer. Bu yüzden gömülü C’de dinamik bellek çoğu zaman hiç kullanılmaz ve tamponlar derleme anında sabit boyutla ayrılır. Otomotivde yaygın olan MISRA C kuralları da standart kütüphanenin bellek ayırma fonksiyonlarını yasaklar.
C++’ta kaynak, onu tutan nesnenin ömrüne bağlanır. Buna RAII (Resource Acquisition Is Initialization) denir. std::vector kapsamdan çıktığında belleğini bırakır, std::lock_guard tuttuğu kilidi açar, std::unique_ptr sahip olduğu nesneyi siler. Bırakma işlemi kapanan süslü parantezde, yani önceden bilinen bir anda olur. Çöp toplayıcı olmadığı için GC kaynaklı bir duraklama da yaşanmaz.
C#’ta belleği .NET’in çöp toplayıcısı yönetir. Referansı kalmayan nesneleri bulur ve belleklerini geri alır. Belleği bırakmayı unutmak ya da bırakılmış belleğe erişmek gibi hatalar büyük ölçüde ortadan kalkar. Bunun bedeli zamanlamada ödenir. Çöp toplamanın ne zaman çalışacağına çalışma ortamı karar verir ve toplama başlamadan önce yönetilen iş parçacıkları durdurulur. Test bankosunun ekranını güncelleyen bir uygulamada bu duraklama fark edilmez. Her PWM periyodunda motor akımını düzelten bir kontrol döngüsünde ise kabul edilemez.
Bir ayrıntı daha var: GC belleği toplar, ama seri port veya dosya gibi işletim sistemi kaynaklarının nasıl kapatılacağını bilmez. C#’ta bu kaynaklar using bloğuyla ya da Dispose çağrısıyla açıkça bırakılır. RAII’yi bilen biri için C#’taki en tanıdık yapı budur.
Derleme ve çalışma ortamı: makine kodu mu, ara dil mi
C ve C++ derleyicisi kaynak kodu doğrudan hedef işlemcinin makine koduna çevirir. Mikrodenetleyici projelerinde bu iş bilgisayarda bir çapraz derleyiciyle (cross compiler) yapılır. Çıkan dosya karta yüklenir ve orada işletim sistemi olmadan çalışabilir.
“C platform bağımsızdır” cümlesi bu yüzden eksiktir. C’nin taşınabilirliği kaynak kod düzeyindedir. Aynı kod her işlemci için ayrıca derlenir ve çıkan makine kodu yalnız o mimaride çalışır. Tip boyutları bile değişir: Arduino Uno’da int 16 bit, Arduino Due’da 32 bittir. Due’da sorunsuz çalışan bir int sayaç, aynı kod Uno’ya yüklendiğinde 32.767’yi geçer geçmez taşar. Taşınabilir C kodunda uint16_t, uint32_t gibi boyutu sabit tiplerin kullanılmasının nedeni budur.
C#’ta derleme iki aşamalıdır. C# derleyicisi kodu önce işlemciden bağımsız bir ara dile (CIL, kısaca IL) çevirir. Program çalışırken .NET’in JIT derleyicisi her metodu ilk çağrıldığında o makinenin koduna dönüştürür. Bu sayede platforma özgü bir kütüphaneye bağlı olmayan bir uygulama Windows, Linux ve macOS’ta, x64 ya da ARM64 işlemcide aynen çalışır. Tek koşul, makinede uygun .NET çalışma ortamının bulunmasıdır.
.NET 7’den beri bir seçenek daha var: Native AOT. Uygulama yayımlanırken doğrudan makine koduna derlenir ve çalışırken JIT kullanılmaz. Böylece açılış hızlanır ve uygulama .NET kurulu olmayan bir makinede de çalışır. Yine de çalışma ortamı ortadan kalkmaz, küçültülmüş hâli uygulamanın içine gömülür. Native AOT, C#’ı C’ye dönüştürmez. Yalnızca dağıtımını C’ninkine yaklaştırır.
“Hangisi daha hızlı” sorusunun cevabı da buradan çıkar. İyi yazılmış C ve C++ kodunun zamanlaması en öngörülebilir olanıdır. Modern .NET de hızlıdır ve çoğu masaüstü ya da sunucu uygulamasında darboğaz dil olmaz. Pratikteki fark çoğu zaman ham hızda değil, bellek ayak izinde ve zamanlamanın garanti edilip edilemediğindedir.
Aynı iş üç dilde: sensör verisinin hareketli ortalaması
ADC’den okunan ham değer gürültülüdür. Bu gürültüyü azaltmanın en basit yollarından biri, son birkaç ölçümün ortalamasını almaktır. Aşağıda aynı hareketli ortalama üç dilde yazıldı. Mantık aynı, farklar dillerin karakterinden geliyor.
C ile: statik tampon ve sabit boyutlu tipler
#include <stdint.h>
#define ORNEK_SAYISI 8
static uint16_t tampon[ORNEK_SAYISI];
static uint8_t indeks;
static uint32_t toplam;
uint16_t ortalamaya_ekle(uint16_t yeni)
{
toplam -= tampon[indeks];
tampon[indeks] = yeni;
toplam += yeni;
indeks = (indeks + 1) % ORNEK_SAYISI;
return (uint16_t)(toplam / ORNEK_SAYISI);
}
Tampon derleme anında RAM’de yerini alır ve heap hiç kullanılmaz. Toplamın neden 32 bit olduğunu da programcı hesaplar. Uno’nun 10 bitlik ADC’si en fazla 1023 değerini verir, bu yüzden sekiz ölçümün toplamı 16 bite rahatça sığar. ADC çözünürlüğü ya da pencere uzunluğu arttığında ise aynı toplam 16 bitte taşar ve filtre hiçbir uyarı vermeden yanlış sonuç üretir.
Kodun bir sınırı da var. Durum dosya düzeyindeki değişkenlerde tutulduğu için fonksiyon yalnız tek bir sensöre hizmet eder. İkinci bir sensör için bu değişkenleri bir struct içine toplamak ve fonksiyona işaretçiyle vermek gerekir.
C++ ile: sensör başına bir nesne
#include <stdint.h>
template <uint8_t N>
class HareketliOrtalama {
public:
uint16_t ekle(uint16_t yeni) {
toplam_ -= tampon_[indeks_];
tampon_[indeks_] = yeni;
toplam_ += yeni;
indeks_ = (indeks_ + 1) % N;
return static_cast<uint16_t>(toplam_ / N);
}
private:
uint16_t tampon_[N] = {};
uint8_t indeks_ = 0;
uint32_t toplam_ = 0;
};
HareketliOrtalama<8> sicaklik;
HareketliOrtalama<16> basinc;
C’deki “struct ve işaretçi” çözümü burada sınıfın kendisidir. Her nesne kendi tamponunu taşır, pencere uzunluğu N derleme anında belirlenir ve heap yine kullanılmaz. Bu tür soyutlamalar derlenmiş kodda genellikle C sürümüne çok yakın bir sonuç verir. C++’ın gömülü tarafta tutunmasının nedeni de budur. Aynı sınıf bir Arduino taslağında da derlenir. Sınıflar çoğaldıkça sorumlulukları nasıl ayıracağınız ayrı bir tasarım konusudur. SOLID prensipleri bu soruya cevap arayan ilkelerdir.
C# ile: kısa kod, belleği GC yönetir
using System.Collections.Generic;
using System.Linq;
public class HareketliOrtalama
{
private readonly Queue<double> olcumler = new();
private readonly int n;
public HareketliOrtalama(int n) => this.n = n;
public double Ekle(double yeni)
{
olcumler.Enqueue(yeni);
if (olcumler.Count > n)
olcumler.Dequeue();
return olcumler.Average();
}
}
Burada ne tampon boyutu ne de taşma hesabı var. İşi Queue ve LINQ’in Average() metodu yapar, arkadaki belleği GC yönetir. Veri tipi de double, çünkü C# tarafı çoğu zaman ham ADC değerini değil, bir karttan ya da PLC’den gelmiş ölçeklenmiş değeri (örneğin °C) işler. Bu kod bir bilgisayarda ya da Raspberry Pi gibi Linux çalıştıran bir kartta çalışır. Uno’ya yüklenemez.
Üç sürüm arasında bir davranış farkı da var. C ve C++ sürümleri tampon dolana kadar boş hücreleri sıfır sayar, bu yüzden ilk yedi çıktı gerçek değerin altında kalır. C# sürümü ise yalnız gelen ölçümlerin ortalamasını alır. Gerçek bir projede başlangıç davranışı bilinçli olarak seçilmelidir. Düşük değer alarmı olan bir sistemde ilk ölçümler yanlış alarm üretebilir.
Üç dilin kullanım alanları
C, donanımın hemen üstündeki katmanın dilidir. Mikrodenetleyici firmware’i, üreticilerin donanım kütüphaneleri, gerçek zamanlı işletim sistemleri ve işletim sistemi çekirdekleri çoğunlukla C ile yazılır. STMicroelectronics’in STM32 HAL sürücüleri katı ANSI C ile geliştirilir, FreeRTOS ise C kaynak dosyaları olarak dağıtılır. Linux çekirdeği de C ile yazılır, ancak artık Rust desteği de içerir.
C++, performans ile soyutlamanın birlikte gerektiği yerde öne çıkar. Unreal Engine’de oyun kodu C++ ile yazılır. ROS 2’nin çekirdek ekibinin bakımını yaptığı istemci kütüphaneleri C++ (rclcpp) ve Python (rclpy) içindir. Görüntü işlemede yaygın kullanılan OpenCV de C++ ile yazılmıştır. Arduino’nun programlama dili de C++ tabanlıdır: taslak küçük bir ön işlemden geçtikten sonra C++ derleyicisine verilir.
C#’ın alanı daha çok bilgisayar tarafındadır:
- Windows masaüstü uygulamaları: Windows Forms ve WPF. .NET çok platformlu olsa da bu iki arayüz çatısı yalnız Windows’ta çalışır.
- Web ve API: ASP.NET Core.
- Mühendislik araçları: test bankosu arayüzleri, veri toplama ve raporlama yazılımları, OPC UA (endüstriyel cihazlar arasında veri alışverişi standardı) istemcileri. OPC Foundation’ın .NET için yayımladığı resmi OPC UA referans uygulaması C# ile yazılmıştır.
- Oyun ve simülasyon: Unity motorunun kendisi C/C++ ile yazılmıştır, geliştiricinin yazdığı oyun mantığı ise C# betikleridir. Eski kaynaklardaki “Unity C# ile geliştirilir” ifadesi bu ayrımı atlar.
- Mobil ve çok platformlu arayüz: .NET MAUI. Eski yazılarda önerilen Xamarin’in desteği 1 Mayıs 2024’te sona erdi. Xamarin.Forms’un devamı olan .NET MAUI, Android, iOS, macOS ve Windows’u tek bir kod tabanından hedefler.
Hangi dili önce öğrenmeli
C, C++ ve C# farklarını bilmek, hangi dille başlanacağı sorusunu tek başına cevaplamaz. Cevap bölümden çok hedefe bağlıdır:
- Hedef elektronik kart, mikrodenetleyici ya da gömülü sistemse C ile başlamak doğal yoldur. İşaretçiler, bit işlemleri ve
volatilegibi kavramlar, datasheet’teki register tablosunu koda çevirirken gerekir. - Hedef robotik, görüntü işleme ya da performans kritik bir masaüstü yazılımıysa doğrudan C++ ile başlanabilir. Arduino projeleri bu yolun ilk basamağı olabilir.
- Hedef test ve ölçüm yazılımı, operatör arayüzü, veri toplama ya da web API’siyse en hızlı sonucu C# verir.
Mekatronik ve elektrik-elektronik projelerinde iki dil sık sık yan yana durur: kartta C veya C++, bilgisayarda C# ya da Python. Sensör verisini buluta taşıyan nesnelerin interneti (IoT) projeleri bu ikiliğin tipik örneğidir.
Bir istisna var. Endüstriyel otomasyon hedefleniyorsa bu üç dilin hiçbiri ilk adım değildir. PLC’ler IEC 61131-3 dilleriyle (ladder, structured text gibi) programlanır. C bilgisi structured text’i okurken işe yarar, ama ladder ve giriş-çıkış mantığını ayrıca öğrenmek gerekir. Bu yolun sırası PLC öğrenmeye nereden başlamalı yazısında anlatılıyor.
Sık sorulan sorular
C öğrenmeden C++ öğrenilir mi?
Evet. Stroustrup da C’yi C++’ın önkoşulu saymaz. Ona göre iki dilin ortak kısmını C++ üzerinden öğrenmek daha kolaydır, çünkü C++’ın tip sistemi daha sıkıdır ve tip hatalarının daha çoğunu derleyici yakalar. Gömülü tarafta ise C’den kaçış yoktur: üretici kütüphanelerinin, register tanımlarının ve örnek kodların çoğu C ile yazılmıştır. C++ ile başlayan biri, bu kodları okuyacak kadar C’yi yolda öğrenir.
C# mı C++ mı daha zor?
Çoğu kişi için C++ daha zordur. Zorluğun büyük kısmı sözdiziminden değil, dilin hatalara verdiği tepkiden gelir. C++’ta bir dizinin sınırı dışına yazmak çoğu durumda derleme hatası vermez. Program bazen çalışır, bazen de alakasız bir yerde çöker. Dilin “tanımsız davranış” (undefined behavior) dediği durum budur. C#’ta aynı hata çalışma anında IndexOutOfRangeException olarak hemen yakalanır. Buna başlık dosyaları, bağlayıcı (linker) hataları ve derleme sistemi de eklenince C++’ın öğrenme eğrisi uzar. C#’ta dilin kendisi hızlı öğrenilir. Zaman alan kısım .NET kütüphaneleri ve arayüz çatılarıdır.
C# ile Arduino programlanır mı?
Arduino Uno’nun kendisi C# çalıştıramaz, çünkü 2 KB SRAM’li bir mikrodenetleyicide .NET çalışma ortamına yer yoktur. Yaygın yöntem iş bölümüdür. Arduino, C++ taslağıyla sensörü okur ve değeri seri porttan gönderir. Bilgisayardaki C# uygulaması da veriyi System.IO.Ports kütüphanesindeki SerialPort sınıfıyla alır, gösterir ve kaydeder. C#’ı doğrudan kart üzerinde çalıştırmak isteyenler için ESP32 ve STM32 gibi daha güçlü kartları hedefleyen .NET nanoFramework projesi var. Ancak gömülü projelerde bu yol, C ve C++ kadar yaygın değildir.
Sahada üç dil aynı projede buluşur

Yazının başındaki senaryoda asıl mühendislik işi dillerin içinde değil, aralarındaki sınırdadır. Karttaki C kodu sıcaklığı 16 bitlik bir değişkende ham ADC değeri olarak gönderir. C# uygulaması ise onu santigrat derece olarak göstermek ister. Şu üç konu baştan yazılı olarak kararlaştırılmalıdır: ölçeklemenin hangi tarafta yapılacağı, bayt sırasının (endianness) iki tarafta aynı kabul edilip edilmediği ve bir veri paketinin nerede başlayıp bittiği. Bunlar kararlaştırılmazsa iki taraf da kendi içinde doğru çalışır, ama ekranda yanlış değer görünür. C tarafında bir struct‘ı olduğu gibi bayt dizisi olarak göndermek de risklidir. Derleyiciye ve işlemciye göre alanların arasına dolgu (padding) baytları eklenebilir ve karşı taraf veriyi kaymış okur.
Dil seçmek kadar arayüzü tanımlamak da ayrı bir beceridir: hangi değer, hangi birimde, kaç bayt, hangi sırayla. Bu zincirin ilk halkasını, yani sensörü okuyup anlamlı bir değere çeviren kodu elde denemek isterseniz EngiCenter’ın Ücretsiz ve Etkileşimli Arduino El Kitabı iyi bir başlangıç noktasıdır. Kitap analog ve dijital pinleri, sensörlerle devre kurmayı, I2C, PWM ve millis() gibi konuları gerçek devre şemaları ve kod bloklarıyla anlatıyor.