2015-04-24 11 views
13

Ten kod pochodzi z Hacker's Delight. Mówi, że jest to najkrótszy taki program w C i ma długość 64 znaków, ale go nie rozumiem:Jak ten program się powiela?

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);} 

Próbowałem go skompilować. Kompiluje się z 3 ostrzeżeniami i bez błędów.

+8

3 ostrzeżenia i bez błędu oznacza udaną kompilację dlaczego go nie uruchamiasz? – Cestarian

+2

@Cestarian Pytanie nie jest * czym * to robi - jest * jak * to robi? Stąd też tytuł. – Barry

+4

To nie jest tak naprawdę najkrótszy program. Rzeczywisty najkrótszy ma długość 0 bajtów. Możesz skompilować skompilowany plik 0-bajtowy c do pliku wykonywalnego. Uruchomienie tego exe powoduje wydrukowanie 0 bajtów, co stanowi cały kod źródłowy oryginalnego programu. –

Odpowiedz

7

Program ten opiera się na założeniach, że

  • typ powrót main jest typ parametru int
  • funkcja jest domyślnie int i
  • argument a="main(a){printf(a,34,a=%c%s%c,34);}" zostaną najpierw ocenione.

To wywoła niezdefiniowane zachowanie. Kolejność oceny argumentów funkcji nie jest gwarantowana w C.
Choć program ten działa w następujący sposób:

Wyrażenie przypisaniea="main(a){printf(a,34,a=%c%s%c,34);}" przydzieli ciąg "main(a){printf(a,34,a=%c%s%c,34);}" do a i wartość wyrażenia przypisania byłoby "main(a){printf(a,34,a=%c%s%c,34);}" zbyt wg standardu --C11 C: 6,5.16

Operator przypisania zachowuje wartość w obiekcie oznaczonym lewym operandem. Wyrażenie przyporządkowaniema wartość lewego argumentu po zadania [...]

Mając na uwadze powyższy semantycznej operatora przypisania program zostanie rozszerzona podczas

main(a){ 
     printf("main(a){printf(a,34,a=%c%s%c,34);}",34,a="main(a){printf(a,34,a=%c%s%c,34);}",34); 
} 

ASCII 34 to ". Projektanci i odpowiadająca jej argumenty:

%c ---> 34 
%s ---> "main(a){printf(a,34,a=%c%s%c,34);}" 
%c ---> 34 

lepsza wersja byłaby

main(a){a="main(a){a=%c%s%c;printf(a,34,a,34);}";printf(a,34,a,34);} 

Jest 4 postać dłużej, ale przynajmniej następujące K & R C.

5

To zależy od kilku dziwactw języka C i (jak sądzę) niezdefiniowanego zachowania.

Po pierwsze definiuje funkcję main. Prawidłowe jest zadeklarowanie funkcji bez typu zwracanego lub typów parametrów i będą one uważane za int. Właśnie dlatego część main(a){ działa.

Następnie wywołuje printf z 4 parametrami. Ponieważ nie ma on prototypu, przyjmuje się, że zwraca on parametry int i akceptuje parametry int (chyba że kompilator domyślnie deklaruje inaczej, tak jak robi to Clang).

Pierwszy parametr jest uznawany za int i na początku programu jest argc. Drugi parametr to 34 (który jest ASCII dla znaku podwójnego cudzysłowu). Trzeci parametr to wyrażenie przypisania, które przypisuje ciąg formatu do a i zwraca go. Polega na konwersji wskaźnik-do-int, która jest legalna w C. Ostatni parametr jest kolejnym cudzysłowem w postaci numerycznej.

W czasie wykonywania specyfikatory formatu %c są zastępowane cytatami, zmienna %s jest zastępowana łańcuchem formatu, a następnie otrzymuje się oryginalne źródło.

O ile mi wiadomo, kolejność oceny argumentów jest niezdefiniowana. Ta instrukcja działa, ponieważ zadanie a="main(a){printf(a,34,a=%c%s%c,34);}" jest oceniane przed przekazaniem a jako pierwszego parametru do printf, ale z tego co wiem, nie ma reguły, aby go wymusić. Ponadto nie działa to na platformach 64-bitowych, ponieważ konwersja wskaźnika na konwersję spowoduje obcięcie wskaźnika do wartości 32-bitowej. W rzeczywistości, chociaż widzę, jak działa na niektórych platformach, nie działa na moim komputerze z moim kompilatorem.

+0

Tak, istnieje UB ze względu na kolejność oceny. Jest też więcej UB, ponieważ typ 'a' (' int') różni się od typu oczekiwanego przez konwersję '% s' (' char * '). –

+0

@zneak dziękuję teraz rozumiem ten program i jesteś naprawdę dobry w c – PDP

+0

@JerryCoffin; Czym jest "więcej" lub "mniej" UB? – haccks

2

Program ma postać z założeniem, że służy do drukowania własnego kodu. Zwróć uwagę na podobieństwo literału ciągu do całego kodu programu. Idea jest taka, że ​​literał będzie używany jako ciąg formatu w formacie printf(), ponieważ jego wartość jest przypisana do zmiennej a (chociaż na liście argumentów) i że zostanie również przekazana jako ciąg do wydrukowania (ponieważ wyrażenie przypisania wylicza wartość który został przydzielony). 34 to kod ASCII dla znaku podwójnego cudzysłowu ("); używając go unika się ciągów formatów zawierających znaki literowe z ukrytymi znakami cudzysłowu.

Kod opiera się na nieokreślonym zachowaniu w postaci porządku oceny argumentów funkcji. Jeśli są one oceniane w porządku listy argumentów, wówczas program prawdopodobnie nie powiedzie się, ponieważ wartość a byłaby wówczas używana jako wskaźnik do ciągu formatu przed faktycznym przypisaniem mu poprawnej wartości.

Dodatkowo, rodzaj a domyślnych do int, i nie ma gwarancji, że int jest wystarczająco szeroka, aby posiadać wskaźnik obiektu bez obcinania go.

Ponadto standard C określa tylko dwie dozwolone podpisy dla main(), a podpis nie jest wśród nich.

Co więcej, typ printf() wywnioskowany przez kompilator pod nieobecność prototypu jest niepoprawny. Nie ma żadnej gwarancji, że kompilator wygeneruje sekwencję wywołującą, która na nią działa.

4

To działa w oparciu o wiele dziwactw, które C pozwala ci zrobić, a niektóre niezdefiniowane zachowania działają na twoją korzyść. W kolejności:

main(a) { ... 

Rodzaje Zakłada się int czy nieokreślony, więc jest to równoznaczne z:

int main(int a) { ... 

Nawet main ma przyjąć wartość 0 lub 2 argumenty, a to jest niezdefiniowane zachowanie , może to być dozwolone jako ignorowanie brakującego drugiego argumentu.

Następnie ciało, które wyrzucę. Zauważ, że a jest int zgodnie main:

printf(a, 
     34, 
     a = "main(a){printf(a,34,a=%c%s%c,34);}", 
     34); 

Kolejność oceny argumentów jest niezdefiniowana, ale jesteśmy powołując się na 3. argumentu - Przypisanie - Pierwsze oceniana jako pierwszy. Opieramy się również na niezdefiniowanym zachowaniu możliwości przypisania char * do int. Należy również zauważyć, że 34 to wartość ASCII wynosząca ". Zatem zamierzone oddziaływanie programu jest:

int main(int a, char**) { 
    printf("main(a){printf(a,34,a=%c%s%c,34);}", 
      '"', 
      "main(a){printf(a,34,a=%c%s%c,34);}", 
      '"'); 
    return 0; // also left off 
} 

, które po ocenie, produkuje:

main(a){printf(a,34,a="main(a){printf(a,34,a=%c%s%c,34);}",34);} 

który był oryginalny program. Tada!

Powiązane problemy