Ana işlev yerine main adlı global değişkene sahip bir program nasıl çalışabilir?


97

Aşağıdaki programı düşünün:

#include <iostream>
int main = ( std::cout << "C++ is excellent!\n", 195 ); 

Windows 7 işletim sisteminde g ++ 4.8.1 (mingw64) kullanarak, program derler ve iyi çalışır, yazdırır:

C ++ mükemmeldir!

konsola. mainbir işlevden ziyade global bir değişken olarak görünür; bu program fonksiyon olmadan nasıl çalışabilir main()? Bu kod C ++ standardına uygun mu? Programın davranışı iyi tanımlanmış mı? -pedantic-errorsSeçeneği de kullandım ancak program hala derleniyor ve çalışıyor.


11
@ πάνταῥεῖ: Neden dil avukatı etiketi gerekli?
Destructor

14
Bunun talimatın 195işlem kodu RETolduğunu ve C çağırma kuralında arayanın yığını temizlediğini unutmayın.
Brian

2
@PravasiMeet "o zaman bu program nasıl çalışır" - bir değişken için başlatma kodunun çalıştırılması gerektiğini düşünmüyor main()musunuz ( fonksiyon olmasa bile ? Aslında, tamamen ilgisizler.)
The Paramagnetic Croissant

4
Programın olduğu gibi (64-bit linux, g ++ 5.1 / clang 3.6) ayrıldığını bulanlardanım. Bununla birlikte , program yasal olarak kötü biçimlendirilmiş olsa da, bunu değiştirerek int main = ( std::cout << "C++ is excellent!\n", exit(0),1 );(ve dahil <cstdlib>ederek) bunu düzeltebilirim .
Mike Kinghan

11
@Brian Böyle açıklamalar yaparken mimariden bahsetmelisiniz. Tüm dünya bir VAX değildir. Veya x86. Ya da her neyse.
dmckee --- eski moderatör kedi

Yanıtlar:


85

Neler olup bittiğiyle ilgili sorunun temeline geçmeden önce, programın kusur raporu 1886'ya göre kötü biçimlendirildiğini belirtmek önemlidir : main () için dil bağlantısı :

[...] Global kapsamda main değişkenini bildiren veya main adını C dil bağlantısıyla (herhangi bir isim alanında) bildiren bir program bozuk biçimlidir. [...]

En son clang ve gcc sürümleri bunu bir hata yapar ve program derleme yapmaz ( gcc canlı örneğine bakın ):

error: cannot declare '::main' to be a global variable
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^

Öyleyse neden eski gcc ve clang sürümlerinde tanılama yoktu? Bu kusur raporu, 2014'ün sonlarına kadar önerilen bir çözüme bile sahip değildi ve bu nedenle, bu vaka, ancak çok yakın zamanda açıkça kötü biçimlendirilmişti, bu da bir teşhis gerektiriyordu.

İhlalde beri bu tanımsız davranış olur gibi Bundan önce, öyle görünüyor -acak bölümünden taslak C gereksinimi ++ standardı 3.6.1 [basic.start.main] :

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

Tanımsız davranış tahmin edilemez ve teşhis gerektirmez. Davranışı yeniden oluştururken gördüğümüz tutarsızlık, tipik tanımsız davranıştır.

Öyleyse kod aslında ne yapıyor ve neden bazı durumlarda sonuç üretiyor? Bakalım elimizde ne var:

declarator  
|        initializer----------------------------------
|        |                                           |
v        v                                           v
int main = ( std::cout << "C++ is excellent!\n", 195 ); 
    ^      ^                                   ^
    |      |                                   |
    |      |                                   comma operator
    |      primary expression
global variable of type int

Biz mainbir olan int genel ad beyan ve başlatılmakta olan, değişken statik depolama süresi vardır. Bir arama denemesi yapılmadan önce ilklendirmenin gerçekleşip gerçekleşmeyeceği tanımlanmış bir uygulama mainama gcc bunu aramadan önce yapıyor gibi görünüyor main.

Kod virgül operatörünü kullanır, soldaki işlenen atılmış bir değer ifadesidir ve burada yalnızca aramanın yan etkisi için kullanılır std::cout. Virgül operatörünün sonucu, bu durumda 195değişkene atanan prvalue olan doğru işlenendir main.

Biz görebilirsiniz dışarı sergej noktaları oluşturulan montaj gösterileri coutstatik başlatma sırasında denir. Tartışma için daha ilginç nokta, canlı godbolt seansına bakın :

main:
.zero   4

ve ardından:

movl    $195, main(%rip)

Olası senaryo, programın maingeçerli kodun orada olmasını bekleyen sembole atlaması ve bazı durumlarda segment hatası vermesidir . Bu durumda, geçerli makine kodunu değişkende saklamanın , kod yürütülmesine izin veren bir segmentte bulunduğumuzu varsayarsak, uygulanabilir programamain yol açmasını bekleriz . Biz görebilirsiniz bu 1984 IOCCC giriş yapar sadece .

Görünüşe göre bunu C ile yapmak için gcc alabiliriz ( canlı görün ):

const int main = 195 ;

Değişken mainçalıştırılabilir bir konumda bulunmadığından dolayı sabit değilse seg-hatası verir, bu yorum bana bu fikri verdi.

Ayrıca , bu sorunun C'ye özgü bir versiyonuna verilen FUZxxl yanıtına da bakın .


Neden uygulama da herhangi bir uyarı vermiyor. (-Wall & -Wextra kullandığımda hala tek bir uyarı vermiyor). Neden? @Mark B'nin bu soruya verdiği cevap hakkında ne düşünüyorsunuz?
Destructor

IMHO, derleyici, mainayrılmış bir tanımlayıcı olmadığı için uyarı vermemelidir (3.6.1 / 3). Bu durumda, VS2013'ün bu vakayı ele almasının (bkz. Francis Cugler'in cevabı), ele alınmasında gcc & clang'dan daha doğru olduğunu düşünüyorum.
cdmh

@PravasiMeet Gcc'nin önceki sürümlerinin neden bir teşhis vermediğine dair cevabımı güncelledim.
Shafik Yaghmour

2
... ve gerçekten, OP'nin programını Linux / x86-64'te g ++ 5.2 ile test ettiğimde (bu programı kabul ediyor - sanırım "en son sürüm" hakkında şaka yapmıyordunuz), tam olarak beklediğim yerde çöküyor olur.
zwol

1
@Walter Bunların kopyaları olduğuna inanmıyorum, eski çok daha dar soru soruyor. Açıkça, çoğu SO sorusunu eski soruların bazı versiyonlarına indirgeyebildiğimiz için bana pek mantıklı gelmediğinden, benim için daha indirgemeci bir görüşe sahip bir grup SO kullanıcısı var, ancak bu durumda SO çok kullanışlı olmayacaktır.
Shafik Yaghmour

20

3.6.1 / 1'den itibaren:

Bir program, programın belirlenmiş başlangıcı olan main adlı global bir işlevi içerecektir. Bağımsız bir ortamda bir programın bir ana işlevi tanımlaması gerekip gerekmediği tanımlanır.

Bundan, g ++ 'nın ana işlevi olmayan bir programa (muhtemelen "bağımsız" deyimle) izin verdiği görülüyor.

Sonra 3.6.1 / 3'ten:

Bir program içinde main işlevi kullanılmayacaktır (3.2). Main bağlantısı (3.5), uygulama tanımlıdır. Main'in satır içi veya statik olduğunu bildiren bir program hatalı biçimlendirilmiştir. Main adı başka türlü rezerve edilmez.

Bu yüzden burada, isimli bir tamsayı değişkeninin olmasının tamamen iyi olduğunu öğreniyoruz main.

Son olarak, çıktının neden yazdırıldığını merak ediyorsanız, başlatma işlemi int main, virgül operatörünü coutstatik init'te yürütmek için kullanır ve ardından başlatma işlemini yapmak için gerçek bir integral değer sağlar.


7
mainBaşka bir şeye yeniden adlandırırsanız bağlantının başarısız olduğunu not etmek ilginçtir : (.text+0x20): undefined reference to main ''
Fred Larson

1
Programınızın bağımsız olduğunu gcc'ye belirtmeniz gerekmiyor mu?
Shafik Yaghmour

9

gcc 4.8.1, aşağıdaki x86 derlemesini oluşturur:

.LC0:
    .string "C++ is excellent!\n"
    subq    $8, %rsp    #,
    movl    std::__ioinit, %edi #,
    call    std::ios_base::Init::Init() #
    movl    $__dso_handle, %edx #,
    movl    std::__ioinit, %esi #,
    movl    std::ios_base::Init::~Init(), %edi  #,
    call    __cxa_atexit    #
    movl    $.LC0, %esi #,
    movl    std::cout, %edi #,
    call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)   #
    movl    $195, main(%rip)    #, main
    addq    $8, %rsp    #,
    ret
main:
    .zero   4

Not coutdeğil de, başlatma sırasında denir mainfonksiyonu!

.zero 4konumdan başlayarak 4 (0-başlatılmış) bayt bildirir main, burada [!] değişkenininmain adıdır .

mainSembol programının başlangıcı olarak yorumlanır. Davranış platforma bağlıdır.


1
Brian'ın işaret 195 ettiği retgibi bazı mimarilerde opcode olduğunu unutmayın . Yani sıfır talimat demek doğru olmayabilir.
Shafik Yaghmour

@ShafikYaghmour Yorumunuz için teşekkürler, haklısınız. Assembler direktiflerini karıştırdım.
sergej

8

Bu kötü biçimlendirilmiş bir programdır. Cygwin64 / g ++ 4.9.3 test ortamımda çöküyor.

Standarttan:

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.


Sanırım bahsettiğim kusur raporundan önce, bu sadece tanımsız bir davranıştı.
Shafik Yaghmour

@ShafikYaghmour, standardın kullanıldığı her yerde uygulanacak genel prensip bu mu?
R Sahu

Evet demek istiyorum ama farkın iyi bir tanımını göremedim. Bu tartışmadan anlayabildiğim kadarıyla , hatalı biçimlendirilmiş NDR ve tanımlanmamış davranış, muhtemelen eşanlamlıdır, çünkü her ikisi de bir teşhis gerektirmez. Bu, biçimsiz ve UB'nin farklı olduğu, ancak kesin olmadığı anlamına gelir.
Shafik Yaghmour

3
C99 bölüm 4 ("Uygunluk") bunu açık hale getirir: "Bir kısıtlamanın dışında görünen 'yapılacak' veya 'olmayacak' gerekliliği ihlal edilirse, davranış tanımsızdır." C ++ 98 veya C ++ 11'de eşdeğer bir ifade bulamıyorum, ancak komitenin orada olmasını istediğinden kesinlikle şüpheleniyorum. (C ve C ++ komitelerinin gerçekten oturup iki standart arasındaki tüm terminolojik farklılıkları
gidermesi gerekiyor

7

Bunun işe yaradığına inanmamın nedeni, derleyicininmain() işlevi derlediğini bilmemesidir, bu nedenle atama yan etkileri ile genel bir tamsayı derler.

Nesne biçimi bu, çeviri-birim halinde derlenmiş bir tefrik edebilir değildir fonksiyon sembolü ve değişken sembol .

Böylece bağlayıcı (değişken) ana sembole mutlu bir şekilde bağlanır ve ona bir işlev çağrısı gibi davranır. Ancak, çalışma zamanı sistemi genel değişken başlatma kodunu çalıştırana kadar olmaz .

Örneği çalıştırdığımda yazdırdı ama sonra segment hatasına neden oldu . Ben o 's varsayalım çalıştırma sistemi bir yürütme çalıştı int değişkeni bir sanki işlevi .


4

Bunu VS2013 kullanarak bir Win7 64bit işletim sisteminde denedim ve doğru bir şekilde derliyor ancak uygulamayı oluşturmaya çalıştığımda bu mesajı çıktı penceresinden alıyorum.

1>------ Build started: Project: tempTest, Configuration: Debug Win32 ------
1>LINK : fatal error LNK1561: entry point must be defined
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

2
FWIW, bu bir bağlayıcı hatası, hata ayıklayıcıdan bir mesaj değil. Derleme başarılı oldu, ancak bağlayıcı main()bir tür değişkeni olduğu için bir işlev bulamadıint
cdmh

Cevabınız için teşekkürler, bunu yansıtacak şekilde ilk cevabımı yeniden ifade edeceğim.
Francis Cugler

-1

Burada zor işler yapıyorsun. Asıl (bir şekilde) tamsayı olarak ilan edilebilir. Mesajı yazdırmak için liste operatörünü kullandınız ve sonra ona 195 atadınız. Aşağıda birinin söylediği gibi, C ++ ile rahat etmediği doğrudur. Ancak derleyici herhangi bir kullanıcı tanımlı isim bulamadığı için şikayet etmedi. Main'in sistem tanımlı bir işlev olmadığını, kullanıcı tanımlı işlevi ve programın çalıştırmaya başladığı şeyin Main () değil, ana modül olduğunu unutmayın. Yine main (), yükleyici tarafından kasıtlı olarak çalıştırılan başlatma işlevi tarafından çağrılır. Sonra tüm değişkenleriniz başlatılır ve bu şekilde çıktı alırken. Bu kadar. Main () olmadan program tamam, ancak standart değil.

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.