İçeriğe geç

Menü

Berk Akademi
Yorumlar Google yorumları Birebir ders başvurusu Ücretsiz ön görüşme Ana Sayfa

C++ "multiple definition of `main'" Hatası Nasıl Çözülür?

cpp-multiple-definition-of-main-hatasi
Bu yazıda neler var?
  1. Bu hata ne anlıyor? Derleme ve bağlama aşamaları
  2. Aynı hedefe ikinci bir main() nasıl girer? Sık nedenler
  3. Üç aşamalı teşhis: g++ -c, nm ve bağlama
  4. Çözüm: fazladan main()'i kaldır ya da test dosyasını ayrı hedef yap
  5. Header ve source ayrımı: başlıkta tanım yazınca da "multiple definition" çıkar
  6. Çok dosyalı projede hızlı kontrol listesi
  7. Sık Sorulan Sorular

C++'ta multiple definition of `main' hatası, aynı çalıştırılabilir hedefe giren dosyalardan birden fazlasında main() tanımı bulunduğunda çıkar. Çözüm için fazladan main()'i kaldır ya da ayrı çalışması gereken dosyayı kendi hedefi olarak, ayrı bir komutla derle. Bir programın giriş noktası olan main() aynı çalıştırılabilir hedef içinde tekil olmalıdır.

Bu hata ne anlıyor? Derleme ve bağlama aşamaları

g++ ile tek komutta program üretmek üç ayrı iş gibi düşünülebilir:

  1. Derleme: Her .cpp dosyası ayrı ayrı makine koduna çevrilir ve bir nesne dosyası (.o) oluşur. Derleyici o anda yalnızca tek dosyaya bakar.
  2. Bağlama: Bağlayıcı (linker) nesne dosyalarını tek bir çalıştırılabilir dosyada birleştirir. Bir fonksiyonun nerede tanımlandığı burada çözülür.
  3. Çalıştırma: İşletim sistemi programı başlatır ve main()'den çalışmaya başlar.

Hata ikinci aşamada çıkar. Her dosya kendi içinde geçerli bir main() içerdiği için derleme sorunsuz geçer. Bağlayıcı nesne dosyalarını birleştirirken main sembolünü iki kez bulur ve hangisini giriş noktası yapacağına karar veremez. Bu yüzden mesaj bir sözdizimi hatası gibi görünmez; tanımların çakışmasından söz eder.

Hatalı iki dosyalı örnek

İki dosyanın ikisinde de main() var:

// ana.cpp
#include <iostream>

int main() {
    std::cout << "Ana programn";
    return 0;
}

// deneme.cpp
#include <iostream>

int main() {
    std::cout << "Denemen";
    return 0;
}

İkisini tek hedefte birleştiren komut:

g++ ana.cpp deneme.cpp -o program

GNU ld kullanan bir sistemde çıktı tipik olarak şuna benzer:

/usr/bin/ld: /tmp/ccXXXXXX.o: in function `main':
deneme.cpp:(.text+0x0): multiple definition of `main'; /tmp/ccYYYYYY.o:ana.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

Tam metin araç zincirine, platforma ve bağlayıcının biçimine göre değişir. Geçici dosya adları, yol ve satır sırası sende farklı görünebilir. Değişmeyen parçalar multiple definition of `main', first defined here ve en sonda bağlayıcının hata koduyla çıktığını bildiren satırdır.

first defined here ifadesi, bağlayıcının sembolü ilk gördüğü dosyayı gösterir. Yukarıdaki komutta ana.cpp önce verildiği için ilk tanım odur. multiple definition satırının başında yer alan dosya ise çakışan ikinci tanımı taşır, yani burada deneme.cpp. İki dosyadan hangisinin gerçekten gereksiz olduğunu mesaj söylemez; bunu projenin amacına bakarak sen karar verirsin.

Aynı hedefe ikinci bir main() nasıl girer? Sık nedenler

Aynı hedefe ikinci bir main() nasıl girer? Sık nedenler

İki dosyayı bilerek aynı komuta yazmak nadirdir. Hata çoğunlukla ikinci main()'in fark edilmeden hedefe girmesiyle çıkar. Üç sık neden var.

1. Unutulan deneme ya da test dosyası

Bir fonksiyonu denemek için klasöre deneme.cpp açıp içine kısa bir main() yazarsın, sonra dosyayı silmeyi unutursun. Klasördeki bütün kaynakları derleyen komut onu da hedefe katar:

g++ *.cpp -o program

Joker karakter (*) o klasördeki tüm .cpp dosyalarını komuta yazar. Yani aklında olmayan dosya da bağlamaya girer.

2. IDE projesine eklenen ikinci dosya

Çoğu geliştirme ortamı proje içindeki bütün kaynak dosyaları tek bir çalıştırılabilir hedef olarak derler. Projeye "yeni dosya ekle" ile ikinci bir dosya ekleyip içine yeni bir main() yazarsan, ortam bunu ayrı bir program saymaz, mevcut hedefe ekler. Ayrı çalışacak her main() için ayrı proje ya da ayrı hedef gerekir.

3. .cpp dosyasını #include ile içeri almak

Başlık dosyası yerine kaynak dosyasını içeri aldığında, aynı tanım iki kez derlenir:

// yardimci.cpp
int topla(int a, int b) {
    return a + b;
}

// ana.cpp
#include "yardimci.cpp"   // tanım burada metin olarak kopyalanır

int main() {
    return topla(2, 3);
}

Bu iki dosyayı g++ ana.cpp yardimci.cpp -o program ile derlediğinde topla hem ana.cpp'nin içinde hem yardimci.cpp'nin kendisinde bulunur. Bağlayıcı bu kez multiple definition of `topla(int, int)' benzeri bir mesaj verir. Aynı mantık main() için de geçerlidir: main() içeren bir .cpp dosyasını #include ile başka dosyaya alırsan, o dosya ayrıca derlendiğinde de ikinci kez girmiş olur.

Üç aşamalı teşhis: g++ -c, nm ve bağlama

Hatanın hangi dosyalardan geldiğini tahmin etmek yerine derleme ile bağlamayı ayır ve her aşamayı ayrı çalıştır. Aşağıdaki örnekte ana.cpp ve deneme.cpp dosyalarının ikisinde de main() var.

  1. Her dosyayı tek başına derle.
    g++ -c ana.cpp
    g++ -c deneme.cpp
    Beklenen sonuç: hata yok, çıktı sessiz. Klasörde ana.o ve deneme.o oluşur. Derleme aşaması her dosyaya ayrı baktığı için bu noktada çakışma görünmez.
  2. Nesne dosyalarında main sembolünü ara.
    nm ana.o | grep main
    nm deneme.o | grep main
    Linux'ta beklenen çıktı iki dosyada da şuna benzer:
    0000000000000000 T main
    T harfi, sembolün kod bölümünde tanımlı ve dışarıya açık olduğunu gösterir. Büyük harf sembolün global olduğunu, küçük harf çoğunlukla yerel olduğunu anlatır. Yani iki dosya da main'i tanımlıyor. Adres sütunu ve biçim platforma göre değişir. macOS gibi bazı sistemlerde sembol başında alt çizgiyle _main görünebilir.
  3. Nesne dosyalarını bağla.
    g++ ana.o deneme.o -o program
    Beklenen sonuç, kelimesi kelimesine aynı olmasa da şuna benzer bir hatadır:
    /usr/bin/ld: deneme.o: in function `main':
    deneme.cpp:(.text+0x0): multiple definition of `main'; ana.o:ana.cpp:(.text+0x0): first defined here
    collect2: error: ld returned 1 exit status

Sonucu nasıl yorumlarsın?

İki nesne dosyasında da T main görüyorsan sorun bağlama aşamasındadır ve fazladan bir main() vardır. İkisinde de görünmüyorsa başka bir hatayla uğraşıyorsun demektir. undefined reference to `main' hatası bunun tersidir: hiçbir nesne dosyasında main yoktur, bağlayıcı giriş noktasını bulamaz. "Multiple definition" fazlalığı, "undefined reference" eksikliği bildirir.

Çözüm: fazladan main()'i kaldır ya da test dosyasını ayrı hedef yap

Çözüm: fazladan main()'i kaldır ya da test dosyasını ayrı hedef yap

Kural şu: her çalıştırılabilir hedefte tek main() olur. İki yol var.

(a) Deneme dosyasına ihtiyacın yoksa dosyayı sil ya da bağlama komutundan çıkar. Komutta deneme.cpp yoksa ikinci main da programa girmez.

(b) Deneme ya da test dosyasının kendi main()'i gerekiyorsa onu ayrı bir çalıştırılabilir dosya olarak derle. Ortak kodu ise ayrı bir .cpp dosyasına taşı:

src/
  hesap.h      (topla bildirimi)
  hesap.cpp    (topla tanımı, main yok)
  ana.cpp      (kendi main'i)
  test.cpp     (kendi main'i)

Ortak kod hesap.h ve hesap.cpp dosyalarında durur. ana.cpp ve test.cpp ise #include "hesap.h" yazar. Bir .cpp dosyasını #include ile başka dosyaya eklemek, kodu iki yerde birden tanımlayıp bu hatayı geri getirebilir. Paylaşılacak şeyi header'a koy, tanımı tek bir .cpp dosyasında tut.

Derleme komutları:

g++ ana.cpp hesap.cpp -o program
g++ test.cpp hesap.cpp -o test
./program
./test

Beklenen sonuç: hata yok, iki ayrı çalıştırılabilir dosya oluşur. Örnek çıktılar:

Toplam: 5
Test gecti

Her komutta yalnızca bir dosyada main() var, hesap.cpp ise ikisinde de ortak olarak kullanılıyor. Bu yüzden bağlayıcı çakışma görmez.

CMake ya da bir IDE kullanıyorsan aynı mantık geçerlidir: her çalıştırılabilir hedef ayrı tanımlanır. CMake'te bu, ayrı add_executable satırlarıdır:

add_executable(ana_program src/ana.cpp src/hesap.cpp)
add_executable(hesap_testi src/test.cpp src/hesap.cpp)

Bu iki satır, kaynak dosya listelerini ayrı tuttuğu için iki hedefin birbirinin main()'ine karışmasını önler.

Header ve source ayrımı: başlıkta tanım yazınca da "multiple definition" çıkar

Aynı hata ailesi main() dışındaki fonksiyonlarda da görülür. Kök neden aynıdır: bir fonksiyonun tanımı, çalıştırılabilir dosyayı oluşturan nesne dosyalarından birden fazlasında bulunur. Başlık dosyasına (.h) yalnızca bildirim yazılır. Tanım ise tek bir .cpp dosyasında durur.

Aşağıdaki başlık dosyası fonksiyonun tanımını içerdiği için hatalıdır:

// hesap.h (hatalı: tanım başlıkta)
#ifndef HESAP_H
#define HESAP_H

int topla(int a, int b) {
    return a + b;
}

#endif

main.cpp ve olcum.cpp dosyalarının ikisi de #include "hesap.h" satırını içeriyor olsun. g++ main.cpp olcum.cpp -o program komutu şuna benzer bir çıktı verir:

/usr/bin/ld: olcum.o: in function `topla(int, int)':
olcum.cpp:(.text+0x0): multiple definition of `topla(int, int)'; main.o:main.cpp:(.text+0x0): first defined here
collect2: error: ld returned 1 exit status

Önişlemci her .cpp dosyasında başlığı ayrı ayrı metne ekler. Böylece iki nesne dosyasının ikisi de topla fonksiyonunun kendi kopyasını taşır. Bağlayıcı bu iki kopyayı görünce hata verir.

Çözüm 1: Tanımı .cpp dosyasına taşı

// hesap.h
#ifndef HESAP_H
#define HESAP_H

int topla(int a, int b);   // yalnız bildirim

#endif

// hesap.cpp
#include "hesap.h"

int topla(int a, int b) {
    return a + b;
}

Derlerken hesap.cpp dosyasını da komuta eklemelisin: g++ main.cpp olcum.cpp hesap.cpp -o program. Tanım artık yalnızca hesap.o içinde olduğundan çakışma kalmaz.

Çözüm 2: Fonksiyonu inline yap

// hesap.h
#ifndef HESAP_H
#define HESAP_H

inline int topla(int a, int b) {
    return a + b;
}

#endif

C++ kurallarına göre inline fonksiyon, birden fazla çeviri biriminde bulunabilir. Tek koşul, tanımların birebir aynı olmasıdır. Bu nedenle kısa yardımcı fonksiyonlar ve şablonlar başlıkta durabilir. Burada inline anahtar sözcüğünün asıl işi, derleyiciye "satır içi aç" demek değil, tanımın birden fazla dosyada bulunmasına izin vermektir.

Include guard bu hatayı neden çözmez?

#ifndef ile yazdığın include guard, başlığın aynı .cpp dosyası içinde iki kez eklenmesini engeller. Ancak her .cpp dosyası ayrı bir derleme birimidir ve guard her birinde baştan işler. Bu yüzden guard, farklı dosyalardaki kopyaları engellemez.

Son bir ayrım: multiple definition tanımın fazla olduğunu, undefined reference to main ise hiç main() bulunmadığını söyler. İkisi zıt yönlü hatalardır.

Çok dosyalı projede hızlı kontrol listesi

  • Derleme komutundaki ya da IDE hedefindeki .cpp dosyalarına bak. Hedefte olmaması gereken bir deneme ya da test dosyası var mı?
  • Proje klasöründe grep -n "main" *.cpp komutunu çalıştır. Aynı hedefe giren dosyalardan yalnızca birinde int main() görünmeli.
  • #include "dosya.cpp" yazma. Kaynak dosyaları #include ile değil, derleme komutuyla birleştir.
  • Başlıkta yalnızca bildirim, inline fonksiyon, şablon ve sınıf tanımı bulundur. Sıradan fonksiyon gövdelerini .cpp dosyasına yaz.
  • Deneme ve test kodlarını ayrı bir hedefte tut, ayrı bir çalıştırılabilir dosya olarak derle.
  • Hata alırsan önce g++ -c ile her dosyayı ayrı derle. Derleme geçip hata bağlama aşamasında çıkıyorsa sorun çoğunlukla tanım sayısındadır.

Derleyicinin işi kodunu nesne dosyasına çevirmek, bağlayıcının işi bu nesne dosyalarını tek programda birleştirmektir. Bu ayrımı bilince hata mesajları da daha kolay okunur: error: ve satır numarası görüyorsan derleme aşamasındasın, ld returned 1 exit status ve first defined here görüyorsan bağlama aşamasındasın. Giriş noktası kavramı başka dillerde de karşına çıkar, bu yüzden aynı mantık orada da işine yarar.

Sık Sorulan Sorular

Aynı projede birden fazla main() olması hiç mümkün değil mi?

Mümkün, ama her main() ayrı bir çalıştırılabilir hedefe ait olmalı. Bir klasörde birden fazla .cpp dosyası ve her birinde main() bulunabilir. Her biri ayrı bir komutla, ayrı bir program olarak derlenirse hata çıkmaz. Hata, bunlar aynı çalıştırılabilir dosyaya birlikte bağlanınca çıkar.

"first defined here" satırı neyi gösterir?

Bağlayıcının aynı sembolü ilk gördüğü nesne dosyasını ve konumu gösterir. Hata mesajının öbür yarısı, çakışan ikinci tanımı taşıyan dosyayı verir. İki dosyadan birindeki tanımı kaldırman ya da dosyalardan birini hedeften çıkarman gerekir.

#include ile .cpp dosyası eklemek neden multiple definition hatasına yol açar?

#include dosyanın içeriğini olduğu gibi metne yapıştırır. Bir .cpp dosyasını eklersen onun fonksiyon tanımları da eklediğin dosyanın içine girer. O .cpp dosyasını ayrıca derleme komutuna da yazdıysan aynı tanım iki nesne dosyasında bulunur ve bağlayıcı hata verir.

Bu hatayı gördüğünde önce kaç tanım olduğuna, sonra bu tanımların hangi dosyalarda bulunduğuna bak. Çoğu durumda çözüm, fazla tanımı kaldırmak ya da dosyayı doğru hedefe taşımaktır.

Bu içerik aradığın cevabı verdi mi?
Yanıtın, hangi yazıları geliştirmemiz gerektiğini anlamamıza yardımcı olur.
Bu içeriğin üretilmesinde yapay zeka araçlarından destek alınmıştır.

Bu konudan sonra ne okuyabilirsin?

Tüm yazılar

İlgili Eğitimler

Berk Keskin, yazılım geliştirici ve eğitmen
Yazar

Berk Keskin Kimdir?

Yazılıma 12 yaşında başladı; İzmir Ekonomi Üniversitesi'ni bölüm birincisi ve yüksek şeref öğrencisi olarak tamamladı. Bugün yalnızca eğitim vermekle kalmıyor, sektörde aktif olarak yazılım projeleri geliştiriyor ve gerçek dünya deneyimini birebir derslerine taşıyor. Ezberden uzak, mühendislik zihniyetini merkeze alan sürdürülebilir öğrenme sistemleri tasarlayarak sorgulayan, üreten ve problem çözebilen yeni nesil yazılımcılar yetiştiriyor.

Sektörel Deneyim & Projeler

  • Ticarify Entegrasyon Yazılım logosu CEO Ticarify Entegrasyon YazılımPazaryerleri ve e-ticaret sitelerine otomatik e-fatura kesimi, sipariş ve kargo takibi hizmetleri sunan e-Dönüşüm platformunun API mimarisini ve yazılım ekibini yönetmektedir.
  • Benim Düğünüm logosu CEO Benim DüğünümDijital etkinlik ve anı paylaşım platformu.
  • Siberdizayn logosu Yazılım Ekibi Lideri SiberdizaynYüksek anlık oyuncu trafiğine sahip oyun kontrol panelleri ve sunucu altyapıları geliştiren yazılım ekibine liderlik etmektedir.
  • MEDYOGRAFYA 360° Dijital Çözümler logosu Dijital Strateji Lideri MEDYOGRAFYA 360° Dijital ÇözümlerŞirketlerin dijital çözümlerde uzun vadede nasıl ilerlemesi gerektiği ve dijital dönüşüm süreçlerinin yönetilmesine destek olmaktadır.
  • İzmir Ekonomi Üniversitesi logosu Danışma Kurulu Üyesi İzmir Ekonomi ÜniversitesiMezun olduğu üniversitesinde, Bilgisayar Programcılığı bölümünün akademik müfredatını güncel sektör ihtiyaçlarına göre şekillendirmek adına Danışma Kurulu'nda görev almaktadır.
WhatsApp Hemen Ara