AT&T Bell Laboratories
Murray Hill, NJ 07974
USAdmr@research.att.com
HOPL-1/4/93/MA,USA
©1993 ACM 0-8979 1-57 1-2/93/0004/0201...$1.50
ABSTRAKT
Programovací jazyk C byl vytvořen na počátku 70. let 20. století jako jazyk pro implementaci systému pro vznikající operační systém Unix. Byl odvozen z beztypového jazyka BCPL a vyvinul typovou strukturu; vytvořen na malém stroji jako nástroj pro zlepšení skromného programovacího prostředí se stal jedním z dominantních jazyků současnosti. Tento článek se zabývá jeho vývojem.Úvod
Tento článek se zabývá vývojem programovacího jazyka C, vlivy, které na něj působily, a podmínkami, za nichž vznikal. Pro stručnost vynechávám úplný popis samotného jazyka C, jeho rodiče B [Johnson 73] a jeho prarodiče BCPL [Richards 79] a místo toho se soustředím na charakteristické prvky jednotlivých jazyků a jejich vývoj.
C vznikal v letech 1969-1973 souběžně s počátečním vývojem operačního systému Unix; nejtvořivější období nastalo v roce 1972. Další vlna změn nastala v letech 1977-1979, kdy se demonstrovala přenositelnost systému Unix. Uprostřed tohoto druhého období se objevil první široce dostupný popis jazyka: The C Programming Language, často nazývaný "bílá kniha" nebo "K&R" [Kernighan 78]. Nakonec byl jazyk v polovině 80. let oficiálně standardizován výborem ANSI X3J11, který provedl další změny. Až do počátku 80. let, ačkoli existovaly kompilátory pro různé strojové architektury a operační systémy, byl tento jazyk spojován téměř výhradně s Unixem; v poslední době se jeho používání rozšířilo mnohem více a dnes patří mezi jazyky nejčastěji používané v celém počítačovém průmyslu.
Historie: prostředí
Konec 60. let byl pro výzkum počítačových systémů v Bell Telephone Laboratories bouřlivým obdobím [Ritchie 78]. (Ritchie 84]. Společnost se stahovala z projektu Multics [Organick 75], který začal jako společný podnik MIT, General Electric a Bell Labs; v roce 1969 dospělo vedení BellLabs, a dokonce i výzkumníci, k názoru, že sliby Multics by mohly být naplněny až příliš pozdě a příliš draze. Ještě předtím, než byl stroj GE-645 Multics odstraněn z areálu, začala neformální skupina vedená především Kenem Thompsonem zkoumat alternativy.
Thompson chtěl vytvořit pohodlné výpočetní prostředí zkonstruované podle vlastního návrhu s využitím všech dostupných prostředků. Jeho plány, jak je patrné z retrospektivy, zahrnovaly mnoho inovativních aspektů systému Multics, včetně explicitního pojetí procesu jako místa řízení, poplatně strukturovaného souborového systému, příkazového interpretu jako programu na uživatelské úrovni, jednoduché reprezentace textových souborů a obecného přístupu k zařízením. Zpočátku navíc on i my ostatní odkládali další průkopnický (i když ne originální) prvek Multics, totiž psaní téměř výhradně v jazyce vyšší úrovně. PL/I, implementační jazyk Multics, nám nebyl příliš po chuti, ale používali jsme i jiné jazyky, včetně BCPL, a litovali jsme, že jsme přišli o výhody psaní programů v jazyce nad úrovní assembleru, jako je snadnost psaní a srozumitelnost. V době jsme nepřikládali velký význam přenositelnosti; zájem o ni se objevil později. Thompson se potýkal s hardwarovým prostředím, které bylo stísněné a spartánské i na tehdejší dobu:DEC PDP-7, na kterém v roce 1968 začínal, byl stroj s 8K 18bitovými slovy paměti a bez užitečného softwaru. Přestože chtěl používat jazyk vyšší úrovně, napsal původní systém Unix v assembleru PDP-7. Zpočátku ani neprogramoval na samotném PDP-7, ale místo toho používal sadu maker pro assembler GEMAP na stroji GE-635. Postprocesor pak vytvářel papírové pásky čitelné pro PDP-7.
Tyto pásky byly přenášeny ze stroje GE do PDP-7 k testování, dokud nebylo dokončeno primitivní unixové jádro, editor, assembler, jednoduchý shell (příkazový interpretr) a několik utilit (jako unixové příkazy rm, cat, cp). Po tomto okamžiku byl operační systém samonosný: programy bylo možné psát a testovat bez použití papírové pásky a vývoj pokračoval na samotném PDP-7.
Thompsonův assembler pro PDP-7 předčil svou jednoduchostí dokonce i assembler firmy DEC: vyhodnocoval výrazy a emitoval odpovídající bity. Neexistovaly žádné knihovny, žádný zavaděč ani editor odkazů: assembleru byl předložen zdrojový kód programu a výstupní soubor s pevným názvem, který vznikl, byl přímo spustitelný. (Tento název, a.out, vysvětluje trochu unixové etymologie; je to výstupní soubor assembleru. Dokonce i poté, co systém získal linker a prostředky pro explicitní specifikaci jiného jména, byl zachován jako výchozí spustitelný výsledek kompilace.)
Nedlouho poté, co Unix poprvé běžel na PDP-7, v roce 1969, Doug Mcllroy vytvořil první jazyk vyšší úrovně nového systému: implementaci McClureova TMG {McClure 65]. TMG je jazyk pro psaní kompilátorů (obecněji TransMoGrifikátorů) ve stylu shora dolů, rekurzivně sestupném který kombinuje bezkontextovou syntaktickou notaci s procedurálními prvky. Mcllroy a Bob Morris použili TMG k napsání raného kompilátoru PL/I pro Multics.
Popuzen Mcllroyovým výkonem při reprodukci TMG, Thompson rozhodl, že Unix - v tom čase snad ještě bez jména - potřebuje jazyk pro systémové programování. Po rychle zmařeném pokusu o Fortran vytvořil místo toho vlastní jazyk, který nazval B. B může být považován za C bez typů; přesněji řečeno BCPL vtěsnaný do 8K bajtů paměti a filtrováno Thompsonovým mozkem. Jeho jméno nejspíše představuje zkratku BCPL, ačkoli alternativní teorie tvrdí, že pochází z Bonu [Thompson 69], nesouvisejícího jazyka vytvořeného Thompsonem v době Multicsu. Bon byl zase pojmenován buď po jeho ženě Bonnie, nebo (podle citace v encyklopedické příručce) po náboženství, jehož rituály zahrnují mumlání magických formulí.
Původ: jazyky
Jazyk BCPL navrhl Martin Richards v polovině šedesátých let, když navštěvoval MIT, a počátkem sedmdesátých let byl použit pro několik zajímavých projektů, mimo jiné pro operační systém OS6 v Oxfordu [Stoy 72] a pro části zásadní práce na Alto v Xerox PARC [Thacker 79]. Seznámili jsme se s ním, protože systém MIT CTSS (Corbato 62], na kterém Richards pracoval, byl použit pro vývoj Multlics. Původní BCPI. Kompilátor byl přenesen jak do Multicsu, tak do systému GE-635 GECOS Ruddem Canadayem a dalšími v Bell Labs [Canaday69]; v posledních fázích života Multics v Bell Labs a bezprostředně poté byl jazykem volby skupiny lidí, kteří se později zapojili do Unixu.
BCPL, B a C pevně zapadají do tradiční procedurální rodiny typické pro Fortran a Algol 60. Jsou orientovány zejména na systémové programování, jsou malé a kompaktně popsané a dají se překládat jednoduchými překladači. Jsou "blízké stroji" v tom smyslu, že abstrakce, které zavádějí, jsou snadno zakotveny v konkrétních datových typech a operacích poskytovaných běžnými počítači a spoléhají se na knihovní rutiny pro vstup a výstup a další interakce s operačním systémem. S menším úspěchem také využívají knihovní procedury ke specifikaci zajímavých řídicích konstrukcí, jako jsou koroutiny a uzávěry procedur.
Zároveň jsou jejich abstrakce na dostatečně vysoké úrovni, takže s opatrností lze dosáhnout přenositelnosti mezi stroji.
BCPL, B a C se syntakticky liší v mnoha detailech, ale obecně jsou si podobné. Programy se skládají z posloupnosti globálních deklarací a deklarací funkcí (procedur). Procedury mohou být v BCPL vnořené, ale nesmí odkazovat na nestatické objekty definované v procedurách, které je obsahují. B a C se vyhýbají tomuto omezení tím, že zavádějí přísnější omezení: žádné vnořené procedury. Každý z těchto jazyků (s výjimkou nejstarších verzí jazyka B) uznává oddělenou kompilaci a poskytuje prostředky pro zahrnutí textu z pojmenovaných souborů.
Některé syntaktické a lexikální mechanismy jazyka BCPI jsou elegantnější a pravidelnější než mechanismy jazyků B a C. Například deklarace procedur a dat v jazyce BCPL mají jednotnější strukturu a poskytují úplnější sadu konstrukcí pro smyčky. Ačkoli programy BCPL jsou z hlediska pojmu dodávány z neohraničeného proudu znaků, chytrá pravidla umožňují, aby většina středníků byla vypuštěna za příkazy, které končí na hranici řádku. B a C tuto vymoženost vynechávají a většinu příkazů ukončují středníkem. Navzdory těmto rozdílům se většina příkazů a operátorů BCPL přímo přenáší do odpovídajících příkazů B a C.
Některé strukturální rozdíly mezi BCPL a B vyplývají z omezení mezipaměti. Například deklarace BCPL mohou mít tvar
let P1 be command | ať P1 je příkaz |
and p2 be command | a P2 je příkaz |
and P3 be command | a p3 je příkaz |
kde text programu reprezentovaný příkazy obsahuje celé procedury. Dílčí deklarace spojené pomocí "and"" ["a"] se vyskytují současně, takže jméno P3 je známo uvnitř procedury P1. Podobně může BCPL zabalit skupinu deklarací a příkazů do výrazu, který vyjadřuje hodnotu, například
E1 := valof $( deklarace ; příkazy; výsledky E2 $) + 1
Překladač BCPL snadno zpracovával takové konstrukce tak, že před vytvořením výstupu uložil a analyzoval parsovanou reprezentaci celého programu v paměti. Omezení paměti kompilátoru B vyžadovala jednoprůchodovou techniku, při níž byl výstup generován co nejdříve, a syntaktická změna, která to umožnila, byla přenesena do jazyka C.
Některé méně příjemné aspekty BCPL vděčí za své vlastní technologické problémy a byly vědomě vyloučeny při návrhu jazyka B. BCPL například používá mechanismus 'global vector' pro komunikaci mezi samostatně kompilovanými programy. V tomto schématu program explicitně spojuje jméno každé externě viditelné procedury a datového objektu s číselným offsetem v globálním vektoru; propojení se provádí v kompilovaném kódu pomocí číselných offsetů. B se této nepříjemnosti zpočátku vyhnul tím, že trval na tom, aby byl celý program předložen kompilátoru najednou. Pozdější implementace B a všechny implementace C používají konvenční linker k řešení externích jmen vyskytujících se v souborech kompilovaných samostatně, místo aby břemeno přiřazování offsetů přenášely na programátora.
Další hříčky při přechodu od BCPL k B byly zavedeny jako otázka vkusu a některé zůstávají kontroverzní, například rozhodnutí používat jediný znak = pro přiřazení namísto : =. Podobně B používá /**/ pro uzavírání komentářů, kdežto BCPL používá //, aby ignoroval text až do konce řádku. Je zde patrné dědictví PL/I. (C++ vzkřísilo konvenci komentářů BCPL.) Fortran ovlivnil syntaxi deklarací: deklarace v B začínají specifickým výrazem jako auto nebo static, následovaným seznamem jmen. Jazyk C tenhle styl nejenže přebral ale dozdobil jej použitím specifikátorů typů na začátek deklarací.
Ne každý rozdíl mezi jazykem BCPL zdokumentovaným v Richardsově knize (Richards79] a jazykem B byl záměrný; vycházeli jsme ze starší verze BCPL [Richards 67]. Například endcase které vyskakuje z BCPL switchon konstrukce, nebylo v jazyce přítomno, když jsme se ho učili v šedesátých letech, a tak přetížení klíčového slova break z příkazu switch v jazycích B a C vděčíme spíše divergentnímu vývoji než vědomé změně. Na rozdíl od všudypřítomných změn syntaxe, ke kterým došlo při vytváření jazyka B, zůstal základní sémantický obsah typové struktury jazyka BCPL jeho pravidla vyhodnocování výrazů nedotčen. Oba jazyky jsou beztypové, respektive mají jediný datový typ, "slovo" nebo "buňka", což je vzorek s pevnou délkou bitů. Paměť v těchto jazycích se skládá z lineárního pole takových buněk a význam obsahu buňky závisí na použité operaci. Například operátor + jednoduše sečte své operandy pomocí strojové instrukce sčítání celých čísel a ostatní aritmetické operace si stejně tak nejsou vědomy skutečného významu svých operandů. Protože paměť je lineární pole, je možné interpretovat hodnotu v buňce jako index v tomto poli a BCPL k tomuto účelu nabízí operátor. V původním jazyce se psal rv a později !, zatímco B používá unární . Pokud je tedy p buňka obsahující index (nebo adresu či ukazatel na) jiné buňky,p odkazuje na obsah buňky, na kterou se ukazuje, buď jako na hodnotu ve výrazu, nebo jako na cíl přiřazení.
Protože ukazatele v BCPL a B jsou pouze celočíselné indexy v paměťovém poli, aritmetika na nich je smysluplná: pokud je p adresa buňky, pak p+1 je adresa další buňky. Tato konvence je základem sémantiky polí v obou jazycích. Když v BCPL napíšeme
nechť V = vec 10
nebo v B,
auto V[10];
výsledek je stejný: je alokována buňka s názvem V, pak je vyčleněna další skupina 10 sousedících buněk a paměťový index první z nich je umístěn do V. Podle obecného pravidla je v B výraz
*(V+i)
sčítá V a i a odkazuje na i-té místo za V. Jak BCPL, tak B přidávají speciální notaci, která má takový přístup k poli zprostředkovat; v B je ekvivalentní výraz
V[i]
a v BCPL
V!i
Tento přístup k polím byl i v té době neobvyklý; C jej později osvojí ještě méně konvenčním způsobem.
Žádný z BCPL, B ani C nepodporuje v jazyce výrazně znaková data; každý z nich pracuje s řetězci podobně jako s vektory celých čísel a doplňuje obecná pravidla několika konvencemi. V obou BCPLa B označuje řetězcový literál adresu statické oblasti inicializované znaky řetězce, zabalenými do buněk, V BCPL obsahuje první zabalený bajt počet znaků v řetězci; v B není žádný počet a řetězce jsou ukončeny speciálním znakem, který B píše jako '*e' . Tato změna byla provedena částečně proto, aby se zabránilo omezení délky řetězce způsobenému udržováním počtu v 8 nebo 9bitovém slotu, a částečně proto, že udržování počtu se podle našich zkušeností zdálo méně pohodlné než používání terminátoru.
S jednotlivými znaky v řetězci BCPL se obvykle manipulovalo tak, že se řetězec rozprostřel do jiného pole, jeden znak na buňku, a později se znovu zabalil; B poskytoval odpovídající rutiny, ale lidé častěji používali jiné knihovní funkce, které přistupovaly k jednotlivým znakům v řetězci nebo je nahrazovaly.
Další historie
Poté, co fungovala TMG verze jazyka B, přepsal Thompson B v sobě (zaváděcí krok). Během vývoje neustále bojoval s paměťovými omezeními: každý přídavek jazyka nafoukl kompilátor tak, že se do něj sotva vešel, ale každý přepis využívající výhod funkce jeho velikost zmenšil. Například B zavedl zobecněné operátory přiřazení, které používaly x=+y pro přidání y k x. Zápis pocházel z jazyka Algol 68 (Wijngaarden 75] prostřednictvím Mcllroye, který jej začlenil do své verze TMG. (V B a raných verzích C se operátor psal =+ místo += ; tato chyba, opravená v roce 1976, byla vyvolána svůdně snadným způsobem zpracování prvního tvaru v lexikálním analyzátoru jazyka B.
Thompson šel o krok dále a vymyslel operátory ++ a --, které inkrementují pořadí; jejich prefixová nebo postfixová pozice určuje, zda ke změně dojde před nebo po zápisu hodnoty operandu. Tyto operátory nebyly v prvních verzích B, ale objevily se až později. Lidé se často dohadují, že byly vytvořeny pro použití režimů automatické inkrementace a automatické dekrementace adres, které poskytoval počítač DEC PDP-11, na němž se C a Unix staly poprvé populárními. To je z historického hlediska nemožné, protože v době vývoje B žádný PDP-11 neexistoval. PDP-7 však měl několik paměťových buněk s automatickou inkrementací, které měly tu vlastnost, že nepřímý odkaz na paměť přes ně inkrementoval buňku. Tato vlastnost pravděpodobně Thompsonovi takové operátory navrhla; zobecnění, aby byly prefixové i postfixové, bylo jeho vlastní. Ve skutečnosti nebyly buňky s automatickým nárůstem použity přímo při implementaci operátorů a silnější motivací pro inovaci bylo pravděpodobně jeho pozorování, že překlad ++x je menší než překlad x=x+1.
Překladač B na PDP-7 negeneroval strojové instrukce, ale 'vláknový kód' [Bell 72], interpretační schéma, v němž výstup překladače tvoří posloupnost adres fragmentů kódu, které provádějí elementární operace. Tyto operace typicky především probíhají na jednoduchém zásobníkovém stroji.
Na unixovém systému PDP-7 bylo v B napsáno jen několik věcí kromě samotného B, protože stroj byl příliš malý a příliš pomalý na to, aby dělal víc než experimenty, a přepsání operačního systému a utilit zcela do B byl příliš nákladný krok, než aby se zdál proveditelný. V určitém okamžiku Thompson zmírnil nedostatek adresového prostoru nabídkou překladače pro virtuální B, který umožňoval interpretovanému programu zabírat více než 8K bajtů stránkováním kódu a dat v rámci interpretu, ale byl příliš pomalý, než aby byl praktický pro běžné utility. Přesto se objevily některé utility napsané v B, včetně rané verze kalkulátoru s proměnnou přesností, který znají uživatelé Unixu (Mcllroy 79]. Nejambicióznějším počinem, který 1 podnikl, byl skutečný křížový kompilátor, který překládal instrukce jazyka B do strojových instrukcí GE-635, nikoliv do kódu s vlákny. Byl tedy malou tour de force: plnohodnotný kompilátor jazyka B, napsaný ve vlastním jazyce a generující kód pro 36 bitový mainframe, který běžel na 18 bitovém stroji se 4K slov uživatelského adresového prostoru. Tento projekt byl možný jen díky jednoduchosti jazyka B a jeho běhového systému.
Ačkoli jsme občas uvažovali o implementaci některého z hlavních jazyků té doby, jako byl Fortran, PL/I nebo Algol 68, takový projekt se nám zdál pro naše zdroje beznadějně velký: byly zapotřebí mnohem jednodušší a menší nástroje. Všechny tyto jazyky ovlivňovaly naši práci, ale bylo zábavnější dělat věci na vlastní pěst.
V roce 1970 se projekt Unix ukázal natolik slibný, že jsme mohli získat nový DEC PDP-11. Procesor patřil mezi první ze své řady dodané společností DEC a než dorazil jeho disk, uplynuly tři měsíce. Aby na něm běžely programy B s využitím vláknové techniky, stačilo napsat pouze fragmenty kódu pro operátory a jednoduchý assembler, který jsem napsal v B; brzy se dc stal prvním zajímavým programem, který byl na našem PDP-11 testován před jakýmkoli operačním systémem. Téměř stejně rychle, stále čekajíc na disk, překódoval Thompson jádro Unixu a z 24K bajtů paměti na několik základních příkazů v assembleru PDP-11. Stroj, nejstarší unixový systém PDP-11 používal 12K bajtů pro operační systém, @ nepatrný prostor pro uživatelské programy a zbytek jako disk RAM. Tato verze sloužila pouze k testování, nikoliv k reálné práci; stroj zaznamenával čas výčtem uzavřených jezdeckých túr na šachovnicích různých velikostí. Jakmile se objevil jeho disk, rychle jsme na něj přešli po transliteraci příkazů assembleru do dialektu PDP-7 a přenesení těch, které už byly v jazyce B.
V roce 1971 začalo mít naše miniaturní počítačové centrum své uživatele. Všichni jsme chtěli snadněji vytvářet zajímavý software. Používání assembleru bylo natolik nudné, že B byl i přes své výkonnostní problémy doplněn malou knihovnou užitečných obslužných rutin a byl používán pro stále více nových programů. Mezi nejvýznamnější výsledky tohoto období patřila první verze generátoru parseru yacc od Steva Johnsona [Johnson 79a].
Problémy B
Stroje, na kterých jsme nejprve používali BCPL a poté B, byly slovní a jediný datový typ těchto jazyků, cell, se pohodlně rovnal hardwarovému strojovému slovu. Příchod PDP-11 odhalil několik nedostatků sémantického modelu jazyka B. Za prvé, jeho mechanismy pro práci se znaky, zděděné s malými změnami z BCPL, byly neohrabané: používání knihovních procedur pro rozložení zabalených řetězců do jednotlivých buněk a následné přebalení nebo pro přístup k jednotlivým znakům a jejich nahrazení začalo být na stroji orientovaném na bajty nepohodlné, dokonce hloupé.
Za druhé, ačkoli původní PDP-11 neobsahoval aritmetiku s pohyblivou řádovou čárkou, výrobce slíbil, že bude brzy k dispozici. Operace s plovoucí desetinnou čárkou byly do BCPI v našich překladačích Multics a GCOS přidány definováním speciálních operátorů, ale tento mechanismus byl možný jen proto, že na příslušných strojích bylo jedno slovo dostatečně velké, aby obsahovalo číslo s plovoucí desetinnou čárkou; to na 16bitovém PDP-11 neplatilo.
Konečně model B a BCPL znamenal režii při práci s ukazateli: jazyková pravidla tím, že definovala ukazatel jako index v poli slov, nutila ukazatele reprezentovat jako indexy slov. Každý odkaz na ukazatel generoval za běhu konverzi z ukazatele na bajtovou adresu explikovanou hardwarem.
Ze všech těchto důvodů se zdálo, že je nutné vytvořit schéma psaní, které by se vypořádalo se znaky a bajtovou adresací a připravilo se na nadcházející hardware s plovoucí řádovou čárkou. Ostatní problémy, zejména typová bezpečnost a kontrola rozhraní, se tehdy nezdály tak důležité jako později.
Kromě problémů se samotným jazykem přinášel překladač jazyka B s technikou vláknového kódu programy o tolik pomalejší než jejich protějšky v assembleru, že jsme zavrhli možnost překódovat operační systém nebo jeho centrální nástroje v jazyce B.
V roce 1971 jsem začal rozšiřovat jazyk B o znakový typ a také jsem přepsal jeho překladač tak, aby generoval strojové instrukce PDP-11 místo vláknového kódu. Přechod z jazyka B na jazyk C byl tedy současný s vytvořením kompilátoru schopného vytvářet programy dostatečně rychlé a malé, aby mohly konkurovat jazyku assembler. Tento mírně rozšířený jazyk jsem nazval NB, což znamená "nový B".
Embryonální C
NB existoval tak krátce, že nebyl napsán jeho úplný popis. Dodával typy int a char, jejich pole a ukazatele na ně, deklarované stylem typizovaným
int i, j;
char c, d;
int iarray[10];int ipointer[];char carray[10];char cpointer[];
Sémantika polí zůstala přesně stejná jako v B a BCPL: deklarace iarray a carray vytvářejí buňky dynamicky inicializované hodnotou ukazující na první z posloupnosti 10celých čísel, resp. znaků. Deklarace pro ipointer a cpointer vynechávají velikost, aby se potvrdilo, že se nemá automaticky alokovat žádné úložiště. V rámci procedur byla interpretace ukazatelů v jazyce totožná s interpretací proměnných pole: deklarace ukazatele vytvářela buňku, která se od deklarace pole lišila pouze tím, že programátor musel přiřadit odkaz, místo aby nechal překladač alokovat prostor a inicializovat buňku.
Hodnoty uložené v buňkách navázaných na jména polí a ukazatelů byly strojové adresy, měřené v bajtech, odpovídajícího úložného prostoru. Proto indirekce prostřednictvím ukazatele neznamenala žádnou režii za běhu pro škálování ukazatele ze slova na bajtový offset. Na druhou stranu strojový kód pro indexování pole a aritmetiku ukazatele nyní závisel na typu pole nebo ukazatele: výpočet iarray[i] nebo ipointer+i znamenal škálování sčítance i podle velikosti objektu, na který se odkazovalo.
Tato sémantika představovala snadný přechod z B a experimentoval jsem s ní několik měsíců. Problémy se projevily, když jsem se pokusil rozšířit typový zápis, zejména o přidávání strukturovaných (záznamových) typů. Zdálo se, že struktury by se měly intuitivně mapovat na paměť ve stroji, ale ve struktuře obsahující pole nebylo dobré místo pro uložení ukazatele obsahujícího základ pole, ani žádný vhodný způsob, jak zařídit, aby byl inicializován. Například položky adresářů raných unixových systémů by se daly v jazyce C popsat jako
struct {
int inumber;
char name[14];
};
Chtěl jsem, aby struktura necharakterizovala pouze abstraktní objekt, ale také aby popisovala kolekci bitů, které lze z adresáře číst. Kam by mohl kompilátor schovat ukazatel na jméno, který sémantika vyžadovala? I kdyby se o strukturách uvažovalo abstraktněji a prostor pro ukazatele by se dal nějak skrýt, jak bych mohl vyřešit technický problém správné inicializace těchto ukazatelů při alokaci složitého objektu, třeba takového, který by specifikoval struktury obsahující pole obsahující struktury do libovolné hloubky?
Tohle řešení představovalo zásadní skok ve vývojovém řetězci mezi beztypovým BCPL a typizovaným C. Odstranilo materializaci ukazatele v úložišti a místo toho způsobilo vytvoření ukazatele při zmínce jména pole ve výrazu. Pravidlo, které přežívá v dnešním C, je, že hodnoty typu pole se při výskytu ve výrazech převádějí na ukazatele na první z objektů tvořících pole.
Tento vynález umožnil, aby většina existujícího kódu jazyka B fungovala i nadále, a to navzdory základnímu posunu v sémantice jazyka. Těch několik málo programů, které přiřadily nové hodnoty jménu pole, aby upravily jeho původ, což bylo možné v jazycích B a BCPL, ale nesmyslné v jazyce C, bylo snadno opraveno. Ještě důležitější je, že nový jazyk zachoval ucelený a použitelný (i když neobvyklý) výklad sémantiky polí a zároveň otevřel cestu k úplnější typové struktuře.
Druhou novinkou, která nejzřetelněji odlišuje jazyk C od jeho předchůdců, je tato úplnější typová struktura a zejména její vyjádření v syntaxi deklarací, NB nabízel základní typy int a char spolu s poli z nich a ukazateli na ně, ale žádné další způsoby kompozice. Bylo nutné zobecnění: při zadání objektu libovolného typu mělo být možné popsat nový objekt, který jich několik shromáždí do pole, získá je z funkce nebo je ukazatelem na ně.
Pro každý objekt takto složeného typu již existoval způsob, jak zmínit základní objekt: indexovat pole, volat funkci, použít operátor přesměrování na ukazatel. Analogická úvaha vedla k syntaxi deklarace jmen, která odráží syntaxi výrazů, v nichž se jména obvykle objevují. Tedy
int i, *pi, **ppi;
deklarují celé číslo, ukazatel na celé číslo, ukazatel (o ukazatel na celé číslo. Syntaxe těchto deklarací odráží pozorování, že i, *pi a **ppi dávají při použití ve výrazu typ int. Podobně,
int f(), *f(), (*f) ();
deklarují funkci vracející celé číslo, funkci vracející ukazatel na celé číslo a ukazatel na funkci vracející celé číslo;
int *api[10], (*pai) [10];
deklarují pole ukazatelů na celá čísla a ukazatel na pole celých čísel. Ve všech těchto případech se deklarace proměnné podobá jejímu použití ve výrazu, jehož typ je uveden v záhlaví deklarace.
Schéma typové skladby přijaté v jazyce C je značně poplatné jazyku Algol 68, i když možná nevzniklo v podobě, kterou by příznivci Algola schválili. Ústředním pojmem, který jsem převzal z Algolu, byla typová struktura založená na atomických typech (včetně struktur), složených do polí, ukazatelů (referencí) a funkcí (procedur). Vliv mělo i pojetí unionů a castů z Algolu 68, které se objevilo později.
Po vytvoření typového systému, související syntaxe a kompilátoru pro nový jazyk jsem měl pocit, že si zaslouží nový název; NB se mi zdál nedostatečně výrazný. Rozhodl jsem se držet se jednopísmenného stylu a nazval jsem jej C, přičemž jsem ponechal otevřenou otázku, zda název představuje postup abecedou nebo písmeny v BCPL.
Neonatální C
Rychlé změny pokračovaly i po pojmenování jazyka, například zavedení operátorů && a ||. V jazycích BCPI a B závisí vyhodnocování výrazů na kontextu:v rámci příkazů if a jiných podmíněných příkazů, které porovnávají hodnotu výrazu s nulou, vkládají jazyky speciální interpretaci operátorů and (&) a or (|). V běžném kontextu fungují bitově, ale v B výrazu
if (e1 & e2) ...
musí překladač vyhodnotit e1 a pokud je nenulové, vyhodnotit e2, a pokud je také nenulové, vypracovat příkaz závislý na if. Požadavek sestupuje rekurzivně na operátory && a || v rámci e1 a e2. Zkratková sémantika booleovských operátorů v takovém kontextu se zdála být žádoucí, ale přetěžování operátorů se obtížně vysvětlovalo a používalo. Na návrh Alana Snydera jsem zavedl operátory && a ||, aby byl mechanismus jednoznačnější.
Jejich opožděné zavedení vysvětluje nesrozumitelnost precedenčních pravidel jazyka C. V B se píše
if (a==b & c) ....
pro kontrolu, zda se a rovná b a c je nenulové; v takovém podmíněném výrazu je lepší, aby & mělo nižší prioritu než ==. Při převodu z B do C chce člověk v takovém příkazu nahradit & operátorem &&, aby převod nebyl tak bolestivý, rozhodli jsme se ponechat prioritu operátoru & stejnou vzhledem k == a pouze mírně oddělit prioritu && od &. Dnes se zdá, že by bylo vhodnější relativní priority & a == přesunout, a tím zjednodušit běžný idiom jazyka C: chceme-li testovat maskovanou hodnotu proti jiné hodnotě, musíme napsat
if ((a&mask) == b) ...
kde jsou vnitřní závorky nutné, ale snadno se na ně zapomene.
Kolem roku 1972-3 došlo k mnoha dalším změnám, ale nejdůležitější bylo zavedení preprocesoru, zčásti na naléhání Alana Snydera [Snyder 74], ale také jako uznání. V jeho původní verzi byla využitelnost mechanismů pro začleňování souborů dostupných v BCPL a PL/I. Jeho původní verze byla přespříliš jednoduchá a poskytovala pouze includované soubory a jednoduché náhrady řetězců: # include a #define bez parametrických maker. Brzy poté byla rozšířena, především Mikem Leskema poté Johnem Relserem, o makra s argumenty a podmíněnou kompilaci. Preprocesor byl původně považován za volitelný doplněk samotného jazyka. Několik let se dokonce ani nevyvolával, pokud zdrojový program neobsahoval na svém začátku speciální signál. Tento postoj přetrvával a vysvětluje jak neúplné začlenění syntaxe preprocesoru do zbytku jazyka, tak nepřesnost jeho popisu v raných referenčních příručkách.
Přenositelnost
Počátkem roku 1973 byly základy moderního jazyka C hotové. Jazyk a překladač byly natolik dokonalé, že nám umožnily v létě téhož roku přepsat jádro Unixu pro PDP-11 v jazyce C. V té době jsme měli k dispozici více jazykových verzí. (Thompson se v roce 1972 krátce pokusil vytvořit systém kódovaný v rané verzi jazyka C před strukturami, ale snahu vzdal). V tomto období byl také překladač přepsán pro další blízké stroje, zejména Honeywell 635 a IBM 360/370; protože jazyk nemohl žít izolovaně, byly vyvinuty prototypy pro moderní knihovny. Zejména Lesk napsal přenositelný I/O balíček [Lesk 72], který byl později přepracován na 'standardní I/O rutiny' v jazyce C. V roce 1978 jsme s Brianem Kernighanem The C Programming Language [Kernighan 78]. Přestože v ní nebyly popsány některé doplňky, které se brzy staly běžnými, sloužila tato kniha jako referenční jazyk až do přijetí formálního standardu o více než deset let později. Přestože jsme na této knize úzce spolupracovali, existovala jasná dělba práce: Kerninghan napsal téměr všechen výkladový materiál, zatímco já byl zodpovědný za přílohu zahrnující referenční manuál a kapitolu o propojení s Unixem.
Během let 1973-1980 se jazyk trochu rozrostl: typová struktura získala typy unsigned, long, union a enumeration a struktury se staly téměř objekty první třídy (chyběl jen zápis literálů). Neméně důležitý vývoj se objevil v jeho prostředí a doprovodných technologiích. Psaní jádra Unixu v jazyce C nám dalo dostatečnou důvěru v užitečnost a efektivitu tohoto jazyka, takže jsme začali překódovávat i systémové nástroje a utility a poté jsme nejzajímavější z nich přenesli na jiné platformy, Jak je popsáno v [Johnson 78a), zjistili jsme, že nejtěžší problémy při šíření unixových nástrojů nespočívají v interakci jazyka Clanguage s novým hardwarem, ale v přizpůsobení stávajícímu softwaru jiných operačních systémů. Steve Johnson tedy začal pracovat na pcc, kompilátoru jazyka C, který měl být snadno retargetovatelný na nové stroje [Johnson 78b], zatímco on, Thompson a já jsme začali přenášet samotný systém Unix na počítač Interdata 8/32.
Změny jazyka v tomto období, zejména kolem roku 1977, byly z velké části zaměřeny na úvahy o přenositelnosti a typové bezpečnosti ve snaze vypořádat se s problémy, které jsme předvídali a pozorovali při přesunu značného množství kódu na novou platformu Interdata. Jazyk C v té době stále vykazoval silné známky svého beztypového původu. Například ukazatele byly v raných příručkách jazyka nebo v dochovaném kódu jen stěží odlišeny od integrálních paměťových indexů; podobnost aritmetických vlastností znakových ukazatelů a celých čísel bez znaménka způsobila, že bylo těžké odolat pokušení je ztotožnit. Typy bez znaménka byly přidány proto, aby byla aritmetika bez znaménka dostupná, aniž by se pletla s manipulací s ukazateli. Podobně se v raných jazycích připouštělo přiřazování mezi celými čísly a ukazateli, ale od této praxe se začalo odrazovat; byl vynalezen zápis pro typové konverze (nazvaný 'casts' podle příkladu Algol 68), který explicitněji specifikuje typové konverze. Raný jazyk C, okouzlený příkladem PL/I, nevázal ukazatele struktur pevně ke strukturám, na které ukazovaly, a umožňoval programátorům psát ukazatel->člen téměř bez ohledu na typ ukazatele; takový výraz byl nekriticky chápán jako odkaz na oblast paměti označenou ukazatelem, zatímco jméno členu udávalo pouze offset a typ.
Ačkoli první vydání K&&R popsalo většinu pravidel, která dovedla typovou strukturu jazyka C do dnešní podoby, mnoho programů napsaných starším, uvolněnějším stylem přetrvávalo a zrovna tak i překladače, které jej tolerovaly. Aby Steve Johnson přiměl lidi věnovat více pozornosti oficiálním pravidlům jazyka, odhalit legální, ale podezřelé konstrukce a pomoci najít neshody rozhraní, které se nedají odhalit jednoduchými mechanismy pro samostatnou kompilaci, upravil svůj kompilátor pcc tak, aby vytvořil program lint (Johnson 79b], který prohledával sadu souborů a upozorňoval na pochybné konstrukce.
Růst v použití
Úspěch našeho experimentu s přenositelností na Interdata 8/32 brzy vedl k dalšímu experimentu Toma Londona a Johna Reisera na DEC VAX 11/780. Tento stroj se stal mnohem populárnější než Interdata a Unix a jazyk C se začaly rychle šířit jak v rámci AT&T, tak mimo ni. Ačkoli v polovině 70. let byl Unix používán v různých projektech v rámci Bell Systems i v malé skupině výzkumně zaměřených průmyslových, akademických a vládních organizací mimo naši společnost, jeho skutečný růst začal až po dosažení přenositelnosti. Za zmínku stojí zejména verze systému System III a System V vznikající divize Computer Systems společnosti AT&T, založené na práci vývojových a výzkumných skupin této společnosti, a řada verzí BSD Kalifornské univerzity v Berkeley, které vznikly na základě práce výzkumných organizací v Bel! Laboratories,
V průběhu 80. let se používání jazyka C široce rozšířilo a kompilátory se staly dostupnými téměř pro každou strojovou architekturu a operační systém; zejména se stal populárním jako programovací nástroj pro osobní počítače, a to jak pro výrobce komerčního softwaru pro tyto stroje, tak pro koncové uživatele, kteří se zajímají o programování. Na začátku desetiletí byl téměř každý kompilátor založen na Johnsonově pcc, v roce 1985 už existovalo mnoho nezávisle vyvíjených kompilátorů.
Standardizace
V roce 1982 bylo jasné, že C potřebuje formální standardizaci. Nejlepší přiblížení standardu, první vydání K&R, již nepopisovalo jazyk ve skutečném použití; zejména nezmiňovalo typy void nebo enum. Předznamenávalo sice novější přístup ke strukturám, ale teprve po jeho vydání jazyk podporoval jejich přiřazování, předávání do funkcí a z funkcí a pevné přiřazování jmen členů ke struktuře nebo svazu, který je obsahuje. Přestože překladače distribuované společností AT&T tyto změny začlenily a většina dodavatelů překladačů, které nebyly založeny na pcc, je rychle převzala, nezůstal žádný úplný, autoritativní popis jazyka.
První vydání K&R také nebylo dostatečně přesné v mnoha detailech jazyka a bylo stále nepraktičtější považovat pcc za "referenční překladač"; dokonale nezachycoval ani jazyk popsaný v K&R, natož jeho pozdější rozšíření. A konečně, počáteční používání jazyka C v projektech podléhajících komerčním a vládním smlouvám znamenalo, že bylo důležité imprimatur oficiálního standardu. Proto (na naléhání M. D. Mcllroye) založila ANSI v létě 1983 výbor X3J11 pod vedením CBEMA s cílem vytvořit normu C. V roce 1983 byl vytvořen výbor X3J11. Koncem roku 1989 vypracoval výbor X3J11 zprávu [ANSI 89] a následně byla tato norma přijata organizací ISO jako ISO/IEC 9899-1990.
Výbor X3J11 od počátku zaujímal opatrný a konzervativní postoj k rozšířením jazyka. K mé velké spokojenosti brali vážně svůj cíl: vyvinout jasnou, konzistentní a jednoznačnou normu pro programovací jazyk C, která kodifikuje společnou, existující definici jazyka C a která podporuje přenositelnost uživatelských programů v různých prostředích jazyka C. [ANSI 89] Výbor si uvědomil, že pouhým vyhlášením standardu se svět nezmění.
X3J11 zavedl pouze jednu skutečně důležitou změnu v samotném jazyce: začlenil typy formálních argumentů do typové signatury funkce pomocí syntaxe vypůjčené z C++ [Stroustrup 86]. Ve starém stylu byly externí funkce deklarovány takto:
double sin();
což říká pouze to, že sin je funkce vracející hodnotu double (tj. s plovoucí desetinnou čárkou s dvojnásobnou přesností). V novém stylu se to lépe vykresluje
double sin(double);
aby byl typ argumentu explicitní, a tím se podpořila lepší typová kontrola a vhodná konverze. I toto doplnění, ačkoli vedlo k výrazně lepšímu jazyku, způsobilo potíže. Výbor se oprávněně domníval, že jednoduše zakázat definice a deklarace funkcí ve starém stylu není možné, ale zároveň se shodl na tom, že nové tvary jsou lepší. Nevyhnutelný kompromis byl tak dobrý, jak jen mohl být, ačkoli definice jazyka je komplikovaná tím, že povoluje obě formy, a autoři přenositelného softwaru se musí potýkat s kompilátory, které ještě nejsou uvedeny na standard.
X3J11 také zavedl řadu menších doplňků a úprav, například typové kvalifikátory const a volatile a mírně odlišná pravidla pro povyšování typů. Nicméně proces standardizace nezměnil charakter jazyka. Zejména se standard jazyka C nesnažil formálně specifikovat sémantiku jazyka, a tak může dojít ke sporům o jemné body; přesto však úspěšně zohlednil změny v používání od původního popisu a je dostatečně přesný, aby na něm bylo možné založit implementace.
Jádro jazyka C tedy z procesu standardizace vyvázlo téměř bez úhony a standard vznikl spíše jako lepší, pečlivá kodifikace než jako nový vynález. Důležitější změny proběhly v okolí jazyka: v preprocesoru a knihovně. Preprocesor provádí substituci maker pomocí konvencí odlišných od zbytku jazyka. Jeho interakce s překladačem nebyla nikdy dobře popsána a X3J11 se pokusil tuto situaci napravit. Výsledek je o poznání lepší než vysvětlení v prvním vydání K&&R; kromě toho, že je obsáhlejší, poskytuje operace, jako je spojování tokenů, které byly dříve dostupné pouze náhodou při implementaci.
X3J11 správně věřil, že úplný a pečlivý popis standardní knihovny jazyka C je stejně důležitý jako jeho práce na jazyce samotném. Jazyk C sám o sobě neposkytuje vstupní výstup ani žádnou jinou interakci s vnějším světem, a proto je závislý na sadě standardních postupů. V době vydání K&&R byl jazyk C považován hlavně za systémový programovací jazyk Unixu; ačkoli jsme poskytli příklady knihovních procedur, které měly být snadno přenositelné do jiných operačních systémů, základní podpora ze strany Unixu byla implicitně chápána. Proto výbor X3J11 strávil většinu svého času návrhem a dokumentací souboru knihovních rutin, které musí být k dispozici ve všech vyhovujících implementacích.
Podle pravidel normalizačního procesu se současná činnost výboru X3J11 omezuje na vydávání výkladů ke stávající normě. Neformální skupina, kterou původně svolal Rex Jaeschke jako NCEG (Numerical C Extensions Group), však byla oficiálně přijata jako podskupina X3J11.1 a nadále zvažuje rozšíření jazyka C. Jak název napovídá, mnoho z těchto možných rozšíření má za cíl učinit jazyk vhodnějším pro numerické použití: například vícerozměrná pole, jejichž hranice jsou dynamicky určovány, začlenění prostředků pro práci s aritmetikou IEEE a zefektivnění jazyka na strojích s vektorovými nebo jinými pokročilými architektonickými vlastnostmi. Ne všechna možná rozšíření jsou specificky numerická; zahrnují notaci pro strukturní literály.
Nástupci
C a dokonce i B mají několik přímých potomků, ačkoli v generování potomků nesoupeří s Pascalem. Jedna vedlejší větev se vyvinula brzy. Když Steve Johnson navštívil v roce 1972 na studijním pobytu univerzitu ve Waterloo, přivezl s sebou i B. Stal se populárním na tamních strojích Honeywell a později dal vzniknout Fh a Zed (kanadské odpovědi na otázku "co následuje po B?"). Když se Johnson v roce 1973 vrátil do Bell Labs, s nelibostí zjistil, že jazyk, jehož zárodky přivezl do Kanady, se vyvinul doma; dokonce i jeho vlastní program yacc přepsal Alan Snyder do jazyka C.
Mezi novější potomky jazyka C patří Concurrent C [Gehani 89], Objective C [Cox86], C* [Thinking 90] a zejména C++ [Stroustrup 86]. Jazyk je také široce používán jako zprostředkující reprezentace (v podstatě jako přenosný jazyk assembleru) pro širokou škálu kompilátorů, a to jak pro přímé potomky, jako je C++, tak pro nezávislé jazyky, jako je Modula 3 [Nelson 91} a Eiffel [Meyer 88}.
Kritika
Mezi jazyky této třídy jsou pro jazyk C nejcharakterističtější dvě myšlenky: vztah mezi poli a ukazateli a způsob, jakým syntaxe deklarace napodobuje syntaxi výrazů.
Patří také mezi jeho nejčastěji kritizované vlastnosti a často slouží jako kámen úrazu pro začátečníky. V obou případech jejich obtížnost prohloubily historické náhody nebo omyly, z nichž nejvýznamnější byla tolerance kompilátorů jazyka C k typovým chybám. Jak by mělo být z výše uvedené historie zřejmé, jazyk C se vyvinul z beztypových jazyků. Neobjevil se najednou před svými uživateli a vývojáři jako zcela nový jazyk s vlastními pravidly; místo toho jsme museli průběžně přizpůsobovat existující programy, jak se jazyk vyvíjel, a zohledňovat existující soubor kódu. (Později se se stejným problémem potýkal výbor ANSI X3J11, který standardizoval jazyk C.
Překladače v roce 1977, a dokonce ani dlouho poté, si nestěžovaly na takové způsoby použití, jako je přiřazování mezi celými čísly a ukazateli nebo používání objektů nesprávného typu pro odkazování na členy struktury. Ačkoli definice jazyka uvedená v prvním vydání K&&R byla ve svém pojednání o pravidlech typů poměrně (i když ne zcela) konzistentní, tato kniha připouštěla, že stávající překladače je neprosazují. Navíc některá pravidla, která měla usnadnit rané přechody, přispěla k pozdějšímu zmatení. Například prázdné hranaté závorky v deklaraci funkce
int f(a) int a[]; { ... }
jsou živou fosilií, pozůstatkem NB způsobu deklarace ukazatele; a je pouze v tomto speciálním případě interpretováno v C jako ukazatel. Tento zápis přežil částečně z důvodu kompatibility, částečně na základě zdůvodnění, že umožní programátorům sdělit svým čtenářům záměr předat f ukazatel vygenerovaný z pole, a nikoli odkaz na jedno celé číslo. Bohužel slouží stejně tak k matení žáka jako k upozornění čtenáře.
V K&R C bylo dodání argumentů správného typu pro volání funkce odpovědností programátora a existující překladače nekontrolovaly typovou shodu. To, že původní jazyk nezahrnoval typy argumentů do typové signatury funkce, byla významná slabina, která si skutečně vyžádala nejodvážnější a nejbolestivější inovaci výboru X3J11, aby ji napravil. Raný návrh je vysvětlován (ne-li ospravedlňován) mým vyhýbáním se technologickým problémům, zejména křížové kontrole mezi odděleně kompilovanými zdrojovými soubory, a mým neúplným pochopením důsledků přechodu z netypovaného jazyka na typovaný. Výše zmíněný program lint se snažil tento problém zmírnit: kromě jiných funkcí kontroluje lint konzistenci a koherenci celého programu tím, že prohledává sadu zdrojových souborů a porovnává typy argumentů funkcí použitých ve volání s typy v jejich definicích.
K vnímání složitosti jazyka přispěla i nehoda syntaxe. Operátor indirekce, v C psaný , je syntakticky unární prefixový operátor, stejně jako v BCPL a B. To funguje dobře v jednoduchých výrazech, ale ve složitějších případech jsou nutné závorky pro usměrnění parsování. Například pro rozlišení indirekce přes hodnotu vrácenou funkcí odvolání funkce označené ukazatelem se píše fp(), respektive (*pf) (). Styl používaný ve výrazech se přenáší i do deklarací, takže jména mohou být deklarována
int *fp();int (*pf)();
Ve zdobnějších, ale stále reálných případech je to horší:
int *(*pfp) ();
je ukazatel na funkci vracející ukazatel na celé číslo. Dochází ke dvěma efektům. Nejdůležitější je, že jazyk C má poměrně bohatou sadu způsobů popisu typů (ve srovnání, řekněme, s Pascalem). Deklarace v jazycích tak expresivních, jako je například C-Algol 68 , popisují objekty stejně obtížně pochopitelné, jednoduše proto, že objekty samy jsou složité. Za druhý efekt vděčíme detailům syntaxe. Deklarace v jazyce C se musí číst stylem 'inside-out' (zevnitř-ven), který mnozí považují za obtížně pochopitelný [Anderson 80]. Sethi [Sethi 81] poznamenal, že mnoho vnořených deklarací a výrazů by se zjednodušilo, kdyby se operátor indirekce bral jako postfixový operátor namísto prefixového, ale v té době už bylo na změnu pozdě.
Další charakteristický rys jazyka C, jeho zacházení s poli, je z praktického hlediska podezřelejší, i když má také skutečné přednosti. Ačkoli je vztah mezi ukazateli a poli neobvyklý, lze se jej naučit. Jazyk navíc vykazuje značnou schopnost popsat důležité koncepty, například vektory, jejichž délka se za běhu mění, pouze s několika základními pravidly. Zejména se znakovými řetězci se zachází stejnými mechanismy jako s jakýmikoliv jinými poli a konvencí, navíc s konvencí, že nulový znak ukončuje řetězec. Je zajímavé porovnat přístup jazyka C s přístupem dvou téměř současných jazyků, Algolu 68 a Pascalu [Jensen 74]. Pole v jazyce Algol 68 mají buď pevně dané hranice, nebo jsou "ohebná". K přizpůsobení ohebných polí je zapotřebí značný mechanismus jak v definici jazyka, tak v překladačích (a ne všechny překladače je plně implementují.) Původní Pascal měl pouze pole a řetězce s pevně danou velikostí, později to bylo částečně opraveno, ačkoli výsledný jazyk se ukázal jako omezující [Kernighan 81]. Není dosud univerzálně dostupný.
C považuje řetězce za pole znaků konvenčně ukončené značkou. Kromě jednoho speciálního pravidla o inicializaci řetězcovými literály je sémantika řetězců plně podřízena obecnějším pravidlům, jimiž se řídí všechna pole, a v důsledku toho je jazyk jednodušší na popis a překlad než jazyk zahrnující řetězec jako jedinečný datový typ. Z tohoto přístupu vyplývají určité náklady: některé operace s řetězci jsou dražší než v jiných provedeních, protože aplikační kód nebo knihovní rutina musí občas hledat konec řetězce, protože je k dispozici málo vestavěných operací a protože břemeno správy úložišť pro řetězce nese více uživatel. Přesto přístup jazyka C k řetězcům funguje dobře.
Na druhou stranu má zacházení s poli v jazyce C obecně (nejen s řetězci) nešťastné důsledky jak pro optimalizaci, tak pro budoucí rozšíření. Převaha ukazatelů v programech v jazyce C, ať už explicitně deklarovaných nebo vyplývajících z polí, znamená, že optimalizátoři musí být opatrní a musí používat pečlivé techniky toku dat, aby dosáhli dobrých výsledků. Sofistikované překladače dokáží pochopit, co většina ukazatelů může změnit, ale některá důležitá použití zůstávají obtížně analyzovatelná. Například funkce s argumenty ukazatele odvozené z polí je těžké zkompilovat do efektivního kódu na vektorových strojích, protože je zřídka možné určit, že ukazatel na jeden argument nepřekrývá data, na která se odkazuje i jiný argument, nebo jsou přístupná externě. Ještě podstatnější je, že definice jazyka C popisuje sémantiku polí tak specificky, že změny nebo rozšíření, která považují pole za primitivnější objekty a umožňují operace s nimi jako s celky, se do stávajícího jazyka těžko vměstnávají, Dokonce i rozšíření umožňující deklaraci a použití vícerozměrných polí, jejichž velikost je určena dynamicky, nejsou zcela jednoduchá [MacDonald 89] [Ritchie 90], ačkoli by výrazně usnadnila psaní numerických knihoven v jazyce C. Jazyk C tedy pokrývá nejdůležitější použití řetězců a polí v praxi jednotným a jednoduchým mechanismem, ale ponechává problémy pro vysoce efektivní implementace a pro rozšíření.
V jazyce a jeho popisu samozřejmě existuje mnoho menších nedostatků kromě těch, o kterých jsme hovořili výše. Je třeba vznést i obecné výtky, které přesahují detailní body. Hlavní z nich je, že jazyk a jeho obecně očekávané prostředí poskytují malou pomoc při psaní velmi rozsáhlých systémů. Struktura pojmenování poskytuje pouze dvě hlavní úrovně, 'externí' (viditelnou všude) a interní (v rámci jedné procedury). Střední úroveň viditelnosti (v rámci jednoho souboru dat a procedur) je slabě vázána na definici jazyka. Proto je přímá podpora modularizace malá a návrháři projektů jsou nuceni vytvářet vlastní konvence
Podobně i samotný jazyk C poskytuje dvě délky ukládání: Automatické objekty, které existují, dokud se řízení nachází v proceduře nebo pod ní, a statické, existující po celou dobu provádění programu. Dynamicky alokované úložiště mimo zásobník je poskytováno pouze knihovní rutinou a břemeno jeho správy je kladeno na programátora:
Co stojí za úspěchem C
Jazyk C se stal úspěšným v míře, která daleko předčila veškerá dřívější očekávání. Jaké vlastnosti přispěly k jeho širokému rozšíření?
Nejdůležitějším faktorem byl nepochybně úspěch samotného Unixu, který zpřístupnil jazyk statisícům lidí. Na druhou stranu bylo samozřejmě pro úspěch systému důležité použití jazyka C a jeho následná přenositelnost na nejrůznější stroje. Ale invaze jazyka do jiných prostředí naznačuje zásadnější zásluhy.
Navzdory některým aspektům, které jsou pro začátečníky a občas i pro pokročilé záhadné, je C jednoduchý a malý jazyk, který lze překládat pomocí jednoduchých a malých překladačů. Jeho typy a operace jsou dobře zakotveny v těch, které poskytují skutečné stroje, a pro lidi zvyklé na to, jak počítače pracují, není obtížné naučit se idiomy pro generování časově a prostorově efektivních programů. Zároveň je jazyk dostatečně abstrahován od strojových detailů, takže lze dosáhnout přenositelnosti programů.
Stejně důležité je, že jazyk C a jeho centrální knihovní podpora vždy zůstávaly v kontaktu s reálným prostředím. Nebyl navržen izolovaně, aby dokázal něco nebo sloužil jako příklad, ale jako nástroj pro psaní programů, které dělají užitečné věci; vždy měl spolupracovat s větším operačním systémem a byl považován za nástroj pro vytváření větších nástrojů. úsporný, pragmatický přístup ovlivnil věci, které se dostaly do jazyka C: pokrývá základní potřeby mnoha programátorů, ale nesnaží se poskytovat příliš mnoho.
A konečně, navzdory změnám, kterými prošel od svého prvního publikovaného popisu, který byl sice neformální a neúplný, zůstal skutečný jazyk C, jak jej vidí miliony uživatelů používajících mnoho různých překladačů, pozoruhodně stabilní a jednotný ve srovnání s jazyky podobně rozšířenými, například Pascalem a Fortranem. Existují různé dialekty jazyka C. Nejvýrazněji se liší dialekty popsané starším K&&R a novějším Standard C, ale celkově zůstal jazyk C svobodnější od proprietárních rozšíření než jiné jazyky. Snad nejvýznamnějším rozšířením jsou kvalifikace ukazatelů 'far' a 'near' určené k řešení zvláštností některých procesorů Intel. Ačkoli jazyk C nebyl původně navržen s ohledem na přenositelnost jako hlavní cíl, podařilo se mu vyjádřit programy, dokonce včetně operačních systémů, na strojích od nejmenších osobních počítačů až po nejsilnější superpočítače.
C je svérázný, nedokonalý a nesmírně úspěšný. Ačkoli tomu jistě napomohly historické náhody, zjevně uspokojil potřebu jazyka pro implementaci systémů, který by byl dostatečně efektivní, aby vytlačil jazyk pro sestavování, a zároveň dostatečně abstraktní a plynulý, aby mohl popisovat algoritmy a interakce v široké škále prostředí.
Poděkování
Stojí za to shrnout v kostce role přímých přispěvatelů k dnešnímu jazyku C. Ken Thompson vytvořil jazyk B v letech 1969-70; byl odvozen přímo z jazyka BCPL Martina Richardse. Dennis Ritchie přetvořil jazyk B na jazyk C v letech 1971-73, přičemž zachoval většinu syntaxe jazyka B, přidal typy a mnoho dalších změn a napsal první překladač. Ritchie, Alan Snyder,Steven C. Johnson, Michael Lesk a Thompson přispěli v letech 1972-1977 nápady na jazyk, během tohoto období se díky těmto lidem a mnoha dalším v Bellových laboratořích značně rozrostla sbírka a Johnsonův přenosný překladač. Knihovních rutin. V roce 1978 Brian Kernighan a Ritchie napsali knihu, která se na několik let stala definicí jazyka. Od roku 1983 výbor ANSI X3J11 jazyk standardizoval. Zvláště významnou měrou se na udržení jejího úsilí podíleli její funkcionáři Jim Brodie, Tom Plum a P. J. Plauger a postupní redaktoři návrhů Larry Rosler a Dave Prosser.
Děkuji Brianu Kernighanovi, Dougu Mcllroyovi, Daveu Prosserovi, Peteru Nelsonovi, Robu Pikeovi, Kenu Thompsonovi a recenzentům HOPLu za rady při přípravě tohoto článku.
Referencie
[ANSI 89] | American Nationat Standards Institute, American National Standard for Information Systems—Programming Language C, X3.159-1989. | |
[Anderson 80] | B. Andesson, ‘Type syntax in the language C: an object lesson in syntactic innovation,’ SIGPLAN Notices 15 (3), March, 1980, pp. 24-27. | |
[Bell 72] | J.R. Bell, “Threaded Code,” C. ACM 16 (6), pp. 370-372. | |
[Canaday 69] | R. H. Canaday and D. M. Ritchie, ‘Bell laboratories BCPL,’ AT&T Bell Laboratories internal memorandum, May, 1969. | |
[Corbató 62] | F. J. Corbató, M. Merwin-Dagget, R. C. Daley, ‘An Experimental Time-sharing System,’ AFIPS Conf. Proc. SICC, 1962, pp. 335-344. | |
[Cox 86] | B. J. Cox and A. J. Novobilski, Object-Oriented Programming: An Evolutionary Approach, Addison-Wesley: Reading, Mass., 1986. Second edition, 1991. | |
[Gehani 89] | N. H. Gehani and W. D. Roome, Concurrent C, Silicon Press: Summit, NJ, 1989. | |
[Jensen 74] | K. Jensen and N. Wirth, Pascal User Manual and Report, Springer-Verlag: New York, Heidelberg, Berlin. Second Edition, 1974. | |
[Johnson 73] | S. C. Johnson and B. W. Kernighan, ‘The Programming | anguage B,’ Comp. Sci. Tech. Report #8, AT&T Bell Laboratories (January 1973). | |
[Johnson 78a] | S. C. Johnson and D. M. Ritchie, ‘Portability of C Programs and the UNIX System,’ Bell Sys. Tech. J. 57 (6) (part 2), July-Aug, 1978. | |
[Johnson 78b] | S. C, Johnson, ‘A Portable Compiler: Theory and Practice,’ Proc. Sth ACM. POPL Symposium (January 1978). | |
[Johnson 79a] | S. C. Johnson, ‘Yet another compiler-compiler,” in Unix Programmer's Manual, Seventh Edition, Vol. 2A, M. D. Mcllroy and B. W. Kemighan, eds. AT&T Bell Laboratories: Murray Hill, NJ, 1979, | |
[Johnson 79b] | S. C. Johnson, ‘Lint, a Program Checker,’ in Unix Programmer's Manual, Seventh Edition, Vol. 2B, M. D. Mcliroy and B. W. Kemighan, eds. AT&T Bell Laboratories: Murray Hilt, NJ, 1979. | |
[Kernighan 78] | B. W. Kernighan and D. M. Ritchie, The C Programming Language, Prentice-Hall: Englewood Cliffs, NJ, 1978. Second edition, 1988. | |
[Kernighan 81] | B. W. Kemighan, ‘Why Pascal is not my favorite programming language,’ Comp. Sci. Tech. Rep. #100, AT&T Bell Laboratories, 1981. | |
[Lesk 73] | M. E. Lesk, ‘A Portable /O Package,’ AT&T Bell Laboratories internal memorandum ca. 1973. | |
[MacDonald 89] | T. MacDonald, ‘Arrays of variable tength,’ J. C Lang. Trans 1 (3), Dec. 1989, pp. 215-233, | |
[McClure 65] | R. M. McClure, ‘TMG—A Syntax Directed Compiler,’ Proc. 20th ACM National Conf. (1965), pp. 262-274. | |
[Mcllroy 60] | M. D. Mcliroy, ‘Macro Instruction Extensions of Compiler Languages,’ C. ACM 3 (4), pp. 214-220. | |
[Mcllroy 79] | M.D. Mcliroy and B. W. Kernighan, eds, Unix Programmer's Manual, Seventh Fdition, Vol. I, AT&T Bell Laboratories: Murray Hill, NJ, 1979. | |
[Meyer 88] | B. Meyer, Object-oriented Software Construction, Prentice-Hall: Englewood Cliffs, NJ, 1988. | |
[Nelson 91] | G. Nelson, Systems Programming with Medula-3, Prentice-Hall: Englewood Cliffs, NJ, 1991. | |
[Organick 75] | E, I. Organick, The Multics System: An Examination of its Structure, MIT Press: Cambridge, Mass., 1975. | |
[Richards 67] | M. Richards, ‘The BCPI. Reference Manual,’ MIT Project MAC Memorandum M352, July 1967. | |
[Richards 79] | M. Richards and C. Whitbey-Strevens, BCPL: The Language and its Compiler, Cambridge Univ. Press: Cambridge, 1979. | |
[Ritchie 78] | D. M. Ritchie, ‘UNIX: A Retrospective,’ Bell Sys. Tech. J. 57 (6) (past 2), July-Aug, 1978. | |
[Ritchie 84] | D.M. Ritchie, ‘The Evolution of the UNIX Time-sharing System' AT&T Bell Labs. Tech. J. 63 (8) (part 2), Oct. 1984. | |
[Ritchie 90] | D. M. Ritchie, ‘Variable-size arrays in C,’ J. C Lang. Trans. 2 (2), Sept. 1990, pp. 81-86 | |
[Sethi 81] | R. Sethi, ‘Uniform syntax for type expressions and declarators,’ Softw. Prac. and Exp. 11 (6), June 1981, pp. 623-628. | |
[Snyder 74] | A. Snyder, A Portable Compiler for the Language C, MIT: Cambridge, Mass., 1974. | |
[Stoy 72] | J.E. Stoy and C. Strachey, ‘OS6—An experimental operating system for a small computer. Part 1: General principles and structure.' Comp J. 15, (Aug. 1972), pp. 117-124 | |
[Stroustrup 86] | B, Stroustrup, The C++ Programming Language, Addison-Wesley: Reading, Mass. 1986, Second edition, 1991. | |
[Thacker 79] | C. P. Thacker, E. M. McCreight, B. W. Lampson, R. F. Sproull, D. R. Boggs, ‘Alto: A Personal Computer,' in Computer Structures: Principles and Examples, D. Sieworek, C. G. Bell, A. Newell, McGraw-Hill: New York, 1982. | |
Thinking 90 | C* Programming Guide, Thinking Machines Corp.: Cambridge Mass., 1990. | |
[Thompson 69] | K. Thompson, ‘Bon—an Interactive Language,’ undated AT&T Bell Laboratories internal memorandum (ca. 1969). | |
[Wijngarden 69] | A. van Wijngaarden, B. J. Mailloux, J. E. Peck, C. H. Koster, M. Sintzoff, C. Lindsey, L. G. Meertens, R. G. Fisker, ‘Revised report on the algorithmic language Algol 68,” Acta Informatica 5, pp. 1-236. |
Artikler | a66ey |
#c #Cprogramminglanguage #programming #DennisRitchie #KenThompson #unix #history #computers #paper #translation #preklad #historie #jazykC
Din e-mailadresse vil ikke blive offentliggjort. Kommentarer bliver modereret.