Main () yöntemi C'de nasıl çalışır?


96

Ana yöntemi yazmak için iki farklı imza olduğunu biliyorum -

int main()
{
   //Code
}

veya komut satırı argümanını işlemek için, bunu şöyle yazıyoruz:

int main(int argc, char * argv[])
{
   //code
}

Gelen C++Bence bir yöntem aşırı, ama biliyorum Cnasıl derleyici bu iki farklı imzalar başedebilir mainfonksiyonu?


14
Aşırı yükleme , aynı programda aynı ada sahip iki yönteme sahip olmayı ifade eder . mainTek bir programda C(veya gerçekten, böyle bir yapıya sahip hemen hemen her dilde) yalnızca bir yönteme sahip olabilirsiniz .
Kyle Strand

13
C'nin yöntemleri yoktur; işlevleri vardır. Yöntemler, nesne yönelimli "genel" işlevlerin arka uç uygulamasıdır. Program, bazı nesne argümanlarına sahip bir işlevi çağırır ve nesne sistemi, türlerine göre bir yöntem (veya belki bir dizi yöntem) seçer. Kendiniz simüle etmediğiniz sürece C bunlardan hiçbirine sahip değildir.
Kaz

4
Program giriş noktaları hakkında derin bir tartışma için - özellikle değil main- John R. Levines'ın klasik kitabı "Bağlayıcılar ve Yükleyiciler" i öneriyorum.
Andreas Spindler

1
C'de, ilk form int main(void)değil int main()( int main()formu reddeden bir derleyici hiç görmedim ).
Keith Thompson

1
@harper: ()Form eskimiş ve buna izin verildiği bile belli değil main(uygulama özellikle izin verilen bir form olarak belgelemediği sürece). C standardı (bkz. 5.1.2.2.1 Program başlangıcı), ()forma tam olarak denk olmayan formdan bahsetmez (). Detaylar bu yorum için çok uzun.
Keith Thompson

Yanıtlar:


133

C dilinin bazı özellikleri işe yarayan hackler olarak başladı.

Ana ve değişken uzunluklu bağımsız değişken listeleri için birden çok imza bu özelliklerden biridir.

Programcılar, bir işleve fazladan argümanlar iletebildiklerini ve verilen derleyicilerde kötü bir şey olmadığını fark ettiler.

Çağrı kuralları şu şekildeyse durum budur:

  1. Çağıran işlev argümanları temizler.
  2. En soldaki argümanlar yığının tepesine veya yığın çerçevesinin tabanına daha yakındır, böylece sahte argümanlar adreslemeyi geçersiz kılmaz.

Bu kurallara uyan bir arama kuralları kümesi, çağıranın bağımsız değişkenleri açtığı ve sağdan sola itildiği yığın tabanlı parametre geçişidir:

 ;; pseudo-assembly-language
 ;; main(argc, argv, envp); call

 push envp  ;; rightmost argument
 push argv  ;; 
 push argc  ;; leftmost argument ends up on top of stack

 call main

 pop        ;; caller cleans up   
 pop
 pop

Bu tür çağrı kurallarının söz konusu olduğu derleyicilerde, iki tür mainveya hatta ek türlerin desteklenmesi için özel bir şey yapılmasına gerek yoktur . mainargümansız bir fonksiyon olabilir, bu durumda yığına itilen öğelerden habersizdir. İki bağımsız değişkenlerin bir fonksiyonu varsa, o zaman bulur argcve argviki üstteki yığın öğeler olarak. Ortam işaretçisine (ortak bir uzantı) sahip platforma özgü üç bağımsız değişkense, bu da işe yarayacaktır: üçüncü bağımsız değişkeni yığının tepesinden üçüncü öğe olarak bulacaktır.

Ve böylece tüm durumlarda sabit bir çağrı çalışır ve programa tek bir sabit başlatma modülünün bağlanmasına izin verir. Bu modül, şuna benzer bir fonksiyon olarak C'de yazılabilir:

/* I'm adding envp to show that even a popular platform-specific variant
   can be handled. */
extern int main(int argc, char **argv, char **envp);

void __start(void)
{
  /* This is the real startup function for the executable.
     It performs a bunch of library initialization. */

  /* ... */

  /* And then: */
  exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}

Diğer bir deyişle, bu başlangıç ​​modülü her zaman üç argümanlı main çağırır. Main hiçbir argüman almazsa veya sadece int, char **, çağıran kurallar nedeniyle hiçbir argüman almadığı gibi iyi çalışır.

Programınızda bu tür bir şey yapacak olsaydınız, bu taşınamaz ve ISO C tarafından tanımlanmamış bir davranış olarak kabul edilir: bir işlevi bir şekilde bildirmek ve çağırmak ve başka bir şekilde tanımlamak. Ancak bir derleyicinin başlangıç ​​hilesi taşınabilir olmak zorunda değildir; taşınabilir program kurallarına göre yönlendirilmez.

Ancak, çağırma kurallarının bu şekilde çalışamayacak şekilde olduğunu varsayalım. Bu durumda, derleyicinin mainözel olarak işlem yapması gerekir . mainFonksiyonu derlediğini fark ettiğinde , örneğin üç argüman çağrısıyla uyumlu bir kod üretebilir.

Yani şunu yazıyorsun:

int main(void)
{
   /* ... */
}

Ancak derleyici onu gördüğünde, esasen bir kod dönüşümü gerçekleştirir, böylece derlediği işlev daha çok şuna benzer:

int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
   /* ... */
}

isimlerin __argc_ignoretam anlamıyla var olmaması dışında . Kapsamınıza bu tür isimler girilmez ve kullanılmayan argümanlar hakkında herhangi bir uyarı olmaz. Kod dönüşümü, derleyicinin kodu, üç bağımsız değişkeni temizlemesi gerektiğini bilen doğru bağlantıyla yayınlamasına neden olur.

Başka bir uygulama stratejisi, derleyicinin veya belki de bağlayıcının __startişlevi (veya her ne denirse) özel olarak oluşturması veya en azından önceden derlenmiş birkaç alternatiften birini seçmesidir. Desteklenen biçimlerden hangisinin kullanıldığına ilişkin bilgi nesne dosyasında saklanabilir main. Bağlayıcı bu bilgiye bakabilir mainve programın tanımıyla uyumlu bir çağrı içeren başlangıç ​​modülünün doğru sürümünü seçebilir . C uygulamaları genellikle yalnızca az sayıda desteklenen biçime sahiptir, mainbu nedenle bu yaklaşım uygulanabilirdir.

C99 dili için derleyiciler main, bir dereceye kadar, işlev bir returnifade olmadan sona ererse , davranışın return 0yürütülmüş gibi görünmesini sağlamak için her zaman özel olarak ele almak zorundadır . Bu yine bir kod dönüşümü ile ele alınabilir. Derleyici, çağrılan bir işlevin mainderlendiğini fark eder . Daha sonra vücudun sonuna ulaşılabilecek durumda olup olmadığını kontrol eder. Eğer öyleyse, birreturn 0;


34

mainC ++ 'da bile HİÇBİR aşırı yükleme yoktur . Ana işlev, bir programın giriş noktasıdır ve yalnızca tek bir tanım bulunmalıdır.

Standart C için

Barındırılan bir ortam için (bu normal olanıdır), C99 standardı şunları söyler:

5.1.2.2.1 Program başlatma

Program başlangıcında çağrılan işlev adlandırılır main. Uygulama, bu işlev için hiçbir prototip bildirmez. Bir dönüş türü ile intve parametresiz olarak tanımlanmalıdır :

int main(void) { /* ... */ }

veya iki parametreli (burada argcve olarak anılacaktır argv, ancak herhangi bir isim kullanılabilir, çünkü bildirildikleri işlev için yereldirler):

int main(int argc, char *argv[]) { /* ... */ }

veya eşdeğer; 9) veya başka bir uygulama tanımlı şekilde.

9) Böylece, intolarak tanımlanan bir typedef adı ile değiştirilebilir intveya türü argvolarak yazılabilir char **argv, vb.

Standart C ++ için:

3.6.1 Ana işlev [basic.start.main]

1 Bir program, programın belirlenmiş başlangıcı olan main adlı global bir işlevi içermelidir. [...]

2 Bir uygulama , ana işlevi önceden tanımlamaz. Bu fonksiyon aşırı yüklenmeyecektir . Geri dönüş türü int türünde olacaktır, ancak aksi takdirde türü uygulama tanımlı olacaktır. Tüm uygulamalar, aşağıdaki main tanımlarının her ikisine de izin verecektir:

int main() { /* ... */ }

ve

int main(int argc, char* argv[]) { /* ... */ }

C ++ standardı açıkça "Bu [ana işlev], int türünde bir dönüş türüne sahip olmalıdır, ancak aksi takdirde türü uygulama tanımlıdır" der ve C standardıyla aynı iki imzayı gerektirir.

Bir de barındırılan bir ortamda (aynı zamanda C kütüphaneleri destekler AC ortam) - İşletim Sistemi çağırır main.

Bir de Barındırılmayan ortamda (gömülü uygulamalar için tasarlanmıştır One) her zaman ön işlemci direktifleri gibi kullanarak programın giriş noktasını (veya çıkış) değiştirebilirsiniz

#pragma startup [priority]
#pragma exit [priority]

Önceliğin isteğe bağlı bir tam sayı olduğu durumlarda.

Pragma başlangıcı işlevi ana işlevden (öncelikli) önce yürütür ve pragma çıkışı ana işlevden sonraki işlevi yürütür. Birden fazla başlangıç ​​yönergesi varsa, öncelik hangisinin önce yürütüleceğine karar verir.


4
Sanmıyorum, bu cevap aslında derleyicinin durumu nasıl ele aldığı sorusuna cevap veriyor. @Kaz'ın verdiği cevap bana göre daha fazla fikir veriyor.
Tilman Vogel

4
Bu cevabın soruyu @ Kaz'dan daha iyi yanıtladığını düşünüyorum. Asıl soru, operatör aşırı yüklemesinin meydana geldiği izlenimi altındadır ve bu yanıt, bazı aşırı yükleme çözümü yerine derleyicinin iki farklı imzayı kabul ettiğini göstererek çözer. Derleyici ayrıntıları ilginçtir ancak soruyu cevaplamak için gerekli değildir.
Waleed Khan

1
Bağımsız ortamlar için ("barındırılmayan"), sadece bazı # pragmalardan çok daha fazlası var. Donanımdan bir sıfırlama kesintisi var ve bu gerçekten programın başladığı yer. Buradan, tüm temel kurulumlar yürütülür: kurulum yığını, kayıtlar, MMU, bellek eşleme vb. Ardından, başlangıç ​​değerlerinin NVM'den statik depolama değişkenlerine kopyalanması (.data segmenti) ve hepsinde "sıfırlama" gerçekleşir. sıfıra ayarlanması gereken statik depolama değişkenleri (.bss segmenti). C ++ 'da, statik depolama süresi olan nesnelerin yapıcıları çağrılır. Ve tüm bunlar yapıldıktan sonra, ana çağrılır.
Lundin

8

Aşırı yüklemeye gerek yoktur. Evet, 2 versiyon vardır, ancak aynı anda yalnızca biri kullanılabilir.


5

Bu, C ve C ++ dilinin garip asimetrilerinden ve özel kurallarından biridir.

Kanımca sadece tarihsel nedenlerle var ve arkasında ciddi bir mantık yok. Bunun mainbaşka nedenlerle de özel olduğunu unutmayın (örneğin mainC ++ 'da özyinelemeli olamaz ve adresini alamazsınız ve C99 / C ++' da son returnifadeyi atlamanıza izin verilir ).

Ayrıca, C ++ 'da bile aşırı yükleme olmadığını unutmayın ... ya bir programın birinci biçimi ya da ikinci biçimi vardır; ikisine birden sahip olamaz.


returnC'deki ifadeyi de ihmal edebilirsiniz (C99'dan beri).
dreamlax

C’de arayabilir main()ve adresini alabilirsiniz; C ++, C'nin uygulamadığı sınırları uygular.
Jonathan Leffler

@JonathanLeffler: haklısın, sabitsin. C99 spesifikasyonlarında bulduğum main ile ilgili tek komik şey, dönüş değerini ihmal etme olasılığına ek olarak, standart IIUC olarak ifade edildiğinden, yinelemede negatif bir değer argcgeçiremeyeceğinizdir (5.1.2.2.1, argcve argvyalnızca ilk çağrı için geçerlidir main).
6502

4

Olağandışı olan şey main, birden fazla şekilde tanımlanabilmesi değil, sadece iki farklı yoldan biriyle tanımlanabilmesidir.

mainkullanıcı tanımlı bir işlevdir; uygulama bunun için bir prototip bildirmez.

Aynı şey fooveya için de geçerlidir bar, ancak işlevleri bu adlarla istediğiniz şekilde tanımlayabilirsiniz.

Aradaki fark, mainyalnızca kendi kodunuz tarafından değil, uygulama (çalışma zamanı ortamı) tarafından çağrılır. Uygulama, sıradan C işlevi çağrısı anlambilimiyle sınırlı değildir, bu nedenle birkaç varyasyonla başa çıkabilir (ve gerekir) - ancak sonsuz sayıda olasılığın üstesinden gelmek zorunda değildir. int main(int argc, char *argv[])Formu ve komut satırı argümanları sağlar int main(void)C veya int main()C ++ işlem komut satırı argümanları gerekmez basit programlar için sadece bir kolaylık.

Derleyicinin bunu nasıl ele aldığına gelince, uygulamaya bağlıdır. Çoğu sistem muhtemelen iki formu etkili bir şekilde uyumlu hale getiren çağrı kurallarına sahiptir ve maintanımlanmış bir parametre olmadan gönderilen herhangi bir argüman sessizce göz ardı edilir. Aksi takdirde, bir derleyicinin veya bağlayıcının mainözel olarak işlem yapması zor olmaz . Sisteminizde nasıl çalıştığını merak ediyorsanız , bazı montaj listelerine bakabilirsiniz.

Ve C ve C ++ 'daki pek çok şey gibi, ayrıntılar da büyük ölçüde tarihin ve dillerin tasarımcıları ve onların öncülleri tarafından alınan keyfi kararların bir sonucudur.

Hem C hem de C ++ 'nın diğer uygulama tanımlı tanımlara izin verdiğini unutmayın main- ancak bunları kullanmak için nadiren iyi bir neden vardır. Ve yönelik uygulamaları ayaklı (hayır OS ile örneğin gömülü sistemler), program giriş noktası uygulama tanımlı olduğunu ve mutlaka bile çağrılmaz main.


3

mainBağlayıcı tarafından karar bir başlangıç adresi için sadece bir isim mainvarsayılan adıdır. Bir programdaki tüm işlev adları, işlevin başladığı başlangıç ​​adresleridir.

İşlev bağımsız değişkenleri yığından / yığından itilir / atılır, bu nedenle işlev için belirtilmiş herhangi bir bağımsız değişken yoksa, yığın üzerinde itilen / atılan / açılan hiçbir bağımsız değişken yoktur. Main'in hem argümanlarla hem de argümansız çalışabilmesi budur.


2

Aynı işlevin iki farklı imzası main () yalnızca istediğinizde resme gelir, yani programınızın kodunuzun herhangi bir gerçek işlemesinden önce veriye ihtiyacı varsa bunları kullanarak bunları iletebilirsiniz -

    int main(int argc, char * argv[])
    {
       //code
    }

argc değişkeni aktarılan veri sayısını saklar ve argv, konsoldan aktarılan değerlere işaret eden char için bir işaretçiler dizisidir. Aksi takdirde birlikte gitmek her zaman iyidir

    int main()
    {
       //Code
    }

Bununla birlikte, her durumda, bir programda bir ve yalnızca bir main () olabilir, çünkü bu, bir programın yürütülmeye başladığı tek noktadır ve dolayısıyla birden fazla olamaz. (umarım layıktır)


2

Daha önce de benzer bir soru sorulmuştu: Neden parametresi olmayan bir işlev (gerçek işlev tanımına kıyasla) derlenir?

En üst sıradaki cevaplardan biri şuydu:

C'de func(), herhangi bir sayıda argüman iletebileceğiniz anlamına gelir . Hiçbir argüman istemiyorsanız, o zaman şunu beyan etmelisiniz:func(void)

Öyleyse, sanırım nasıl mainilan edilir (eğer "bildirildi" terimini uygulayabilirseniz main). Aslında şöyle bir şey yazabilirsiniz:

int main(int only_one_argument) {
    // code
}

ve yine de derlenecek ve çalışacaktır.


1
Mükemmel gözlem! Görünüşe göre bağlayıcı için oldukça bağışlayıcı main, çünkü henüz bahsedilmeyen bir konu var: daha fazla argüman main! "Unix (ama Posix.1 değil) ve Microsoft Windows" eki char **envp(DOS'un buna da izin verdiğini hatırlıyorum, değil mi?) Ve Mac OS X ve Darwin, başka bir "rasgele işletim sistemi tarafından sağlanan bilgi" char * işaretçisi ekledi. wikipedia
usr2564301

0

Bunu geçersiz kılmanıza gerek yok çünkü bir seferde sadece bir tane kullanılacak. Evet main function'ın 2 farklı versiyonu var

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.