Czym dokładnie jest język programowania? Co pozwala nam pisać w takim języku?

26

Dobra, jestem nowy w programowaniu i przyznaję, że to dość abstrakcyjne pytanie.

Język naturalny, którym mówimy na co dzień, istnieje, ponieważ ludzie mogą się zrozumieć. W jaki sposób komputery mogą zrozumieć mój kod napisany w określonym języku?

Powiedzmy, że pan A tworzy nowy język. Jak to jest akceptowane przez maszyny? Czy twórca musi komunikować się z maszyną za pomocą języka maszynowego, aby utworzyć nowy język? Jakie są gwarancje, że możemy napisać w języku, gdy maszyna jest dobrze zrozumiana?

Erica Xu
źródło
1
Co pozwala nam pisać w takim języku? - „Mózgi: nowy cudowny wypełniacz głowy!” - Spike Milligan.
Stephen C
6
Nieco szerokie, ale dobre pytanie. Zbyt wiele osób po prostu używa języków, nie zastanawiając się, jak działają. Dobrze, że jesteś ciekawy.
riwalk
4
To jest ogólne pytanie referencyjne , na które Wikipedia w łatwy i trywialny sposób odpowiedziała .
Aaronaught

Odpowiedzi:

39

Możesz podsumować całą odpowiedź na zestaw pytań słowem „kompilator” . Kompilator to specjalny program, którego funkcją jest pobieranie kodu źródłowego jako danych wejściowych, stosowanie reguł językowych określonych przez projektanta języka, aby dowiedzieć się, co oznacza kod, i tworzyć kod o takim samym znaczeniu w innym języku jako wyniku. Jest to ogólnie kod maszynowy lub inna forma kodu bajtowego („kod maszynowy” dla maszyn wirtualnych), chociaż istnieją wyspecjalizowane kompilatory, które tłumaczą kod na inne języki wysokiego poziomu. Są jednak poza zakresem tego pytania.

Nie wszystkie języki mają kompilator. Niektóre z nich mają interpreter , który robi to samo, co kompilator, z wyjątkiem tego, że zamiast tworzyć kod maszynowy po ustaleniu, co oznacza program, po prostu wykonuje program natychmiast. Ale podstawowe zasady parsowania (czytania) kodu i określania, co to znaczy, są takie same.

Odpowiedzi na pytania bardziej szczegółowe niż te dotyczyłyby teorii kompilatora, która jest bardzo szerokim tematem. Jeśli jesteś zainteresowany tym tematem, powinieneś zacząć od przeczytania artykułu w Wikipedii dla „kompilatora” i sprawdzenia linków z niego, a jeśli masz konkretne pytania, możesz je tutaj zadać.

Mason Wheeler
źródło
11
+1 - Dodałbym również, że kiedy piszesz nowy język, musisz napisać kompilator lub tłumacz w innym języku. Późniejsze wersje kompilatora lub interpretera można następnie napisać we wcześniejszych wersjach języka i skompilować ze starszym kompilatorem. Pierwszy asembler został napisany w kodzie maszynowym. Pierwszy kompilator C został napisany w asemblerze (najprawdopodobniej) itp.
Scott Whitlock
1
Zmieniłbym definicję kompilatora. Nie wszystkie emitują kod maszynowy. Zwłaszcza w dzisiejszych czasach przy tak wielu kompilatorach emitujących „kod pośredni”, takich jak MSIL. Istnieją nawet kompilatory, które emitują JavaScript!
Neil N
3
Z wahaniem stwierdzę, że kompilatory produkują kod maszynowy z definicji, nawet gdy tłumaczą to początkującym. To tak, jakby powiedzieć, że funkcje zwracają liczby rzeczywiste, bezcelowe uproszczenie. Wszystko budowy kompilatora trzyma przy produkcji kodu, który nie jest dla komputera faktycznie zbudowany z krzemu, ale tylko określonej abstrakcyjnie (czy to VM lub język wysokiego poziomu, nie bez powodu mówi się średnia C definiuje abstrakcyjne maszynę i tam jest kompilatorem od bardzo niskiego poziomu LLVM IR do friggin 'JavaScript). Początkujący muszą to zdobyć, im szybciej, tym lepiej.
2
Uproszczenie stosowane przez większość książek na temat kompilatorów polega na tym, że kompilator stosuje reguły językowe do konwersji z języka źródłowego na język docelowy jako danych wyjściowych. (Kompilacja do C nie jest rzadkością, na przykład szczególnie w przypadku kursu wprowadzającego).
JasonTrue
4
@delnan, jeszcze więcej - każdy język jest kodem maszynowym , dla własnej abstrakcyjnej maszyny. Bez względu na poziom znajomości języka.
SK-logic
11

Jak zauważyłeś, ludzie komunikują się między sobą w „naturalnym” języku, takim jak angielski, francuski, niemiecki. Nazywa się je naturalnymi, ponieważ naturalnie je nabywamy, a nie celowo je wymyślamy (wyjątek stanowi esperanto).

Język formalny to taki, który został wymyślony w jakimś celu. Język programowania, taki jak na przykład C, jest językiem formalnym wymyślonym w celu programowania komputerów.

Wszystkie języki można opisać za pomocą gramatyki. Hierarchię gramatyczną opisał Noam Chomsky w 1956 roku. Składa się z następujących poziomów:

Gramatyka typu 0 (gramatyka nieograniczona). Są najbardziej ogólne i odpowiadają maszynie Turinga. W związku z tym problem decydowania o tym, czy dany ciąg znaków jest częścią nieograniczonej gramatyki, jest nierozstrzygalny.

Gramatyki typu 1 (gramatyki kontekstowe). Prawie wszystkie języki naturalne, takie jak angielski, są wrażliwe na kontekst. Przykładem wrażliwości kontekstu w języku angielskim są dwa wyrażenia: „Czas leci jak strzała”. i „Owoce lecą jak banan”. Zasadniczo komputerom trudno jest zrozumieć języki kontekstowe.

Gramatyki typu 2 (bezkontekstowe). Języki bezkontekstowe są teoretyczną podstawą składni większości języków programowania.

Gramatyki typu 3 (gramatyki zwykłe). Rodzinę języków regularnych można uzyskać za pomocą wyrażeń regularnych. Zwykłe języki są powszechnie używane do definiowania wzorców wyszukiwania i struktury leksykalnej języków programowania.

Gramatyki typu 2 (bezkontekstowe) i typu 3 (zwykłe) są najczęściej obsługiwane przez komputery, ponieważ analizatory składni dla nich można skutecznie zaimplementować.

BNF (Backus Normal Form lub Backus – Naur Form) to technika notacji gramatyki bezkontekstowej, często używana do opisania składni języków używanych w informatyce.

Na przykład identyfikator można opisać jako:

<identifier> ::= <letter> { <letter> | <digit> }

co oznacza, że ​​musi zaczynać się od litery i może zawierać dodatkowe litery lub cyfry.

Wcześniej litera jest definiowana jako „a” | „b” | „c” itp., a cyfra jest definiowana jako „0” do „9” przy użyciu tego samego rodzaju notacji.

Instrukcja AC „for” może być zdefiniowana jako:

 <for_statement> ::=
    'for' '(' <expression> ';' <expression> ';' <expression> ')' <statement> 

Analizatory leksykalne i parsery (pierwsze etapy kompilatora lub interpretera) są następnie konstruowane w celu przyjęcia określonej gramatyki opisanej przez BNF dla określonego języka. Analizatory leksykalne są zwykle używane do oddzielania różnych tokenów języka (takich jak słowo kluczowe, identyfikator lub liczba), a analizator składni służy do ustalenia, w jaki sposób tokeny działają razem, na przykład w jaki sposób budowana jest instrukcja „for” .

tcrosley
źródło
+1 świetny opis. Ale nie dziwię się, że nie została zaakceptowana jako odpowiedź. O to myślałem, że OP pyta, ale na podstawie wybranej przez nich odpowiedzi wydaje się, że chcieli czegoś znacznie wyższego.
Matthew Rodatus
5

Najpierw zdefiniujmy „język” pod względem tego, czym on jest. Język wymaga najpierw słownictwa (listy słów definiujących pojęcia będące przedmiotem komunikacji), a następnie składni („elementarz” lub zestaw reguł określających strukturę komunikacji).

Na tym najbardziej podstawowym poziomie C # nie różni się tak bardzo od angielskiego. Tym, co czyni C # „językiem programowania”, jest jego intencja, a zatem jego konstrukcja; jest zaprojektowany do przetworzenia na indywidualne polecenia niskiego poziomu. Jako takie, predefiniowane słownictwo jest ograniczone, składnia jest bardzo rygorystycznie egzekwowana, a cały język jest zaprojektowany do używania w bardzo dobrze znany sposób przez jego „odbiorców” (komputer; dokładniej kompilator, który będzie trawił kod źródłowy na „język pośredni” prostych poleceń, które następnie mogą zostać przetłumaczone na kod maszynowy przez „środowisko wykonawcze”). Nie piszesz prozy ani poezji w C #; każesz komputerowi wykonać pracę w najbardziej jednoznaczny możliwy sposób.

W przypadku komputerów tak, narzędzie, zwykle nazywane kompilatorem, jest potrzebne, aby wziąć to, co piszesz w kodzie i przekonwertować na instrukcje, których może użyć komputer. Informatyka, podobnie jak większość technologii, jest z natury iteracyjnym, „warstwowym” procesem. Kiedy komputery zostały wynalezione po raz pierwszy, zostały one zaprogramowane poprzez ręczne wprowadzenie instrukcji binarnych. Instrukcje te zostały ujednolicone dla każdego procesora na szesnastkowe „kody maszynowe”; różnica polega tylko na tym, jak cyfry binarne są pogrupowane w celu wyświetlenia ludziom. Następnie w kodzie asemblera lista poleceń i niektóre podstawowe identyfikatory, takie jak nazwy rejestrów, zostały zastąpione ich kodami szesnastkowymi podczas pisania programów; ASM nadal można przekonwertować 1: 1 na natywny kod maszynowy. Skok kwantowy polegał na „imperatywnym” programowaniu 3. generacji, który w zasadzie przyjmuje bardziej zrozumiałe dla człowieka, abstrakcyjne pojęcia, takie jak zmienne i pętle logiczne, i przetwarza je w natywne instrukcje, używając wzorców opartych na słowach kluczowych i składni. Wczesne języki, takie jak COBOL, FORTRAN, Pascal i C, mogą być nadal „tłumaczone” przez człowieka na określony język maszynowy (zwykle 8086 ASM). Potem przyszła rewolucja programowania obiektowego, które jest w zasadzie dodatkowymi regułami składniowymi, które definiują kod jako koncepcyjnie enkapsulowany w „obiektach” o pewnej kombinacji stanu i logiki. przez człowieka na określony język maszynowy (zwykle 8086 ASM). Potem przyszła rewolucja programowania obiektowego, które jest w zasadzie dodatkowymi regułami składniowymi, które definiują kod jako koncepcyjnie enkapsulowany w „obiektach” o pewnej kombinacji stanu i logiki. przez człowieka na określony język maszynowy (zwykle 8086 ASM). Potem przyszła rewolucja programowania obiektowego, które jest w zasadzie dodatkowymi regułami składniowymi, które definiują kod jako koncepcyjnie enkapsulowany w „obiektach” o pewnej kombinacji stanu i logiki.

Obecnie jesteśmy na dobrej drodze do „4. generacji” języków, które są językami pisanymi w celu zdefiniowania komunikacji z innymi programami zamiast bezpośrednio na maszynie. Szeroko zdefiniowane, obejmuje języki „znaczników”, takie jak XML / HTML, języki „skryptowe”, takie jak JavaScript i SQL, oraz większość języków „piaskownicy”, takich jak Java i .NET Framework (które kompilują się w IL, która jest następnie interpretowana przez środowisko wykonawcze, które wyodrębnia dane specyficzne dla maszyny i platformy). Można również powiedzieć, że obejmuje on sferę funkcjonalnych języków programowania, które są DUŻO zależne od środowiska wykonawczego, aby zapewnić abstrakcję nie tylko szczegółów specyficznych dla maszyny, ale także szczegółów specyficznych dla operacji. Te języki czwartej generacji są mniej lub bardziej niemożliwe do przetłumaczenia przez człowieka na natywne instrukcje maszynowe, chodzi o to, że nie byłoby to warte zachodu; siłą tych języków jest wielowarstwowy proces, w którym są one używane, aby ostatecznie powiedzieć komputerowi, co ma robić na niskim poziomie.

KeithS
źródło
Dzięki. Rzut oka na historię ewolucji języka programowania.
Erica Xu
2
@KeithS: Możesz przeformatować ostatni akapit, aby był bardziej czytelny.
Ivan Vučica,
4

To dobre pytanie. Prawidłowa odpowiedź stanowi dobrą połowę tego, co nazywa się „informatyką”.

Na początek poleciłbym przejrzeć semantykę denotacyjną i operacyjną , a następnie przeczytać tę książkę . Zapewni ci mniej więcej solidne zrozumienie, czym jest język programowania i jak można go formalnie zdefiniować.

Jeśli powyższe jest trochę zbyt akademickie, możesz zacząć od Petzolda, „Kod” , a następnie wrócić do semantyki.

Logika SK
źródło
1
Naprawdę oczekujesz, że 18-letni noob przeczyta ciężką teorię tylko po to, by odpowiedzieć na to pytanie?
Job
2
@Job, zgodnie z jego poprzednim pytaniem, dostaje dawki Schematu (i prawdopodobnie SICP) na uniwersytecie. W takim razie powinno być w porządku z odrobiną semantyki. W każdym razie nie ma właściwej odpowiedzi na to pytanie bez ciężkiej teorii.
SK-logic
+1 za wzmiankę o „kodzie”. Ta książka powinna być wymagana do czytania dla każdego ucznia CS na poziomie podstawowym.
Daniel Pryden
4

Jeśli napiszesz program w języku programowania, inny program przekształci symbole w programie na symbole zrozumiałe dla komputera. Czasami wymaga to kilku kroków. Na przykład w C:

  1. Użytkownik pisze program w języku wysokiego poziomu (C), który nie jest rozumiany przez CPU, ale jest bezpośrednio rozumiany przez programistę (mamy nadzieję!).

  2. Kompilator konwertuje C na język Assmebly, który nie jest bezpośrednio rozumiany przez CPU, ale łatwo go przekonwertować na coś innego.

  3. Asempler przekształca asembler w sekwencję kodów binarnych, które są bezpośrednio rozumiane przez CPU. Niektóre kompilatory pomijają powyższy krok (krok 2) i tworzą skompilowany plik binarny bezpośrednio z kodu źródłowego.

Aby zagwarantować, że komputer zrozumie twój program, kompilator lub interpreter wyświetli błąd i zwykle zatrzyma się, jeśli napotka coś, czego nie można skompilować, na przykład błąd składniowy. Jeśli twój program nie może zostać skompilowany, nigdy nie będzie mógł przejść do etapu, w którym twój program spróbuje go uruchomić i zawiedzie, ponieważ go nie „zrozumiał”.

Aby utworzyć nowy język, najpierw projektujesz swój język wysokiego poziomu, a następnie musisz znaleźć sposób odwzorowania symboli nowego języka na komendy języka asemblera, które rozumie Twój procesor.

FrustratedWithFormsDesigner
źródło
2
Nie całkiem; nowoczesne kompilatory nie wykonują kroku 2 i po prostu wytwarzają kod binarny bezpośrednio. Ale asembler i kod binarny i tak są prawie równoważne; możesz rozmontować (przekonwertować kod binarny z powrotem na zestaw) z bardzo wysoką wiernością.
MSalters,