Защо езикът за програмиране C все още управлява

Никоя технология не се задържа в продължение на 50 години, освен ако не свърши работата си по-добре от повечето други неща - особено компютърната технология. Езикът за програмиране C е жив и рита от 1972 г. и все още царува като един от основните градивни елементи на нашия софтуерно дефиниран свят.

Но понякога една технология се задържа, защото хората просто не са стигнали до замяната ѝ. През последните няколко десетилетия се появиха десетки други езици - някои изрично предназначени да оспорят господството на C, а някои отстраняват C отстрани като страничен продукт от тяхната популярност.

Не е трудно да се твърди, че C се нуждае от подмяна. Изследванията на езика за програмиране и практиките за разработване на софтуер намекват как има много по-добри начини да се правят нещата от начина на C. Но C продължава все така, зад което стоят десетилетия изследвания и разработки. Малко други езици могат да го победят за производителност, за съвместимост без метали или за повсеместност. И все пак си струва да се види как C се подрежда срещу езиковото състезание с големи имена през 2018 г.

С срещу С ++

Естествено, C се сравнява най-често със C ++, езикът, който - както показва самото име - е създаден като разширение на C. Разликите между C ++ и C могат да бъдат характеризирани като обширни или  прекомерни , в зависимост от това, когото питате.

Въпреки че все още е C-подобен в синтаксиса и подхода си, C ++ предлага много истински полезни функции, които не са достъпни в C: пространства от имена, шаблони, изключения, автоматично управление на паметта и т.н. Проекти, които изискват производителност от първо ниво - бази данни, системи за машинно обучение - често се пишат на C ++, използвайки тези функции, за да изтласкат всяка капка производителност от системата.

Освен това C ++ продължава да се разширява далеч по-агресивно от C. Предстоящият C ++ 20 внася още повече в таблицата, включително модули, съпрограми, библиотека за синхронизация и концепции, които правят шаблоните по-лесни за използване. Последната редакция на стандарта C добавя малко и се фокусира върху запазването на обратната съвместимост.

Нещата са, че всички плюсове в C ++ също могат да работят като минуси. Големи. Колкото повече C ++ функции използвате, толкова повече сложност въвеждате и по-трудно става опитомяването на резултатите. Разработчиците, които се ограничават до подмножество на C ++, могат да избегнат много от най-лошите клопки и излишъци. Но някои магазини искат да се предпазят от сложността на C ++ заедно. Придържането към C принуждава разработчиците да се ограничат до това подмножество. Например екипът за разработка на ядрото на Linux избягва C ++.

Избирането на C върху C ++ е начин за вас - и за всички разработчици, които поддържат кода след вас - да избегнете да се заплитате с излишъци на C ++, като възприемете принудителен минимализъм. Разбира се, C ++ има богат набор от функции на високо ниво за добра причина. Но ако минимализмът е по-подходящ за настоящи и бъдещи проекти - и проектни екипи - тогава C има повече смисъл.

C срещу Java

След десетилетия Java остава основен елемент за разработване на корпоративен софтуер - и основно за развитие. Много от най-значимите корпоративни софтуерни проекти са написани на Java - включително по-голямата част от проектите на Apache Software Foundation - и Java остава жизнеспособен език за разработване на нови проекти с изисквания за корпоративен клас.

Синтаксисът на Java заимства много от C и C ++. За разлика от C обаче Java по подразбиране не се компилира в родния код. Вместо това средата за изпълнение на Java, JVM, JIT (точно навреме) компилира Java код, който да се изпълнява в целевата среда. При подходящи обстоятелства JITted Java кодът може да се доближи или дори да надвиши производителността на C.

Философията „пиши веднъж, стартирай навсякъде“, която стои зад Java, също така позволява на Java програми да работят с относително малко настройка за целева архитектура. За разлика от това, въпреки че C е пренесен в много архитектури, всяка програма на C все още може да се нуждае от персонализация, за да работи правилно, да речем, Windows срещу Linux.

Тази комбинация от преносимост и висока производителност, заедно с масивна екосистема от софтуерни библиотеки и рамки, правят Java преходен език и време за изпълнение за изграждане на корпоративни приложения.

Където Java не достига C е област, в която Java никога не е била предназначена да се конкурира: работи близо до метала или работи директно с хардуер. C кодът се компилира в машинен код, който се изпълнява директно от процеса. Java се компилира в байт код, който е междинен код, който интерпретаторът на JVM след това преобразува в машинен код. Освен това, въпреки че автоматичното управление на паметта на Java е благословия в повечето случаи, C е по-подходящ за програми, които трябва да използват оптимално ограничените ресурси на паметта.

Въпреки това има някои области, в които Java може да се доближи до C по отношение на скоростта. JIT двигателят на JVM оптимизира рутините по време на изпълнение въз основа на програмното поведение, позволявайки много класове оптимизация, които не са възможни с предварително компилирана C. И докато Java runtime автоматизира управлението на паметта, някои по-нови приложения работят около това. Например Apache Spark оптимизира частично обработката в паметта, като използва персонализиран код за управление на паметта, който заобикаля JVM.

C срещу C # и .Net

Почти две десетилетия след въвеждането им, C # и .Net Framework остават основни части от корпоративния софтуерен свят. Казано е, че C # и .Net са отговорът на Microsoft на Java - управлявана система за компилация на кодове и универсално време за изпълнение - и толкова много сравнения между C и Java също поддържат C и C # /. Net.

Подобно на Java (и до известна степен Python) .Net предлага преносимост на различни платформи и обширна екосистема от интегриран софтуер. Това не са малки предимства, като се има предвид колко ориентирано към предприятията развитие се осъществява в .Net света. Когато разработвате програма на C # или който и да е друг .Net език, можете да черпите вселена от инструменти и библиотеки, написани за изпълнението на .Net. 

Друго подобно на Java .NET предимство е JIT оптимизацията. Програмите C # и .Net могат да бъдат компилирани преди време според C, но те са предимно точно навреме, компилирани от времето на изпълнение .Net и оптимизирани с информация за времето на изпълнение. JIT компилацията позволява всякакви оптимизации на място за работеща .Net програма, която не може да бъде изпълнена в C.

Подобно на C, C # и .Net предоставят различни механизми за директен достъп до паметта. Купчината, стекът и неуправляемата системна памет са достъпни чрез .Net API и обекти. А разработчиците могат да използват unsafeрежима в .Net, за да постигнат още по-голяма производителност.

Нищо от това обаче не се предлага безплатно. Управляваните обекти и unsafeобекти не могат да бъдат обменяни произволно и марширането между тях се дължи на производителност. Следователно, максимизирането на производителността на .Net приложения означава свеждане до минимум на движението между управлявани и неуправлявани обекти.

Когато не можете да си позволите да платите наказанието за управлявана срещу неуправляема памет или когато времето за изпълнение .Net е лош избор за целевата среда (напр. Пространство на ядрото) или изобщо не е налице, тогава C е това, което трябва. И за разлика от C # и .Net, C отключва директен достъп до паметта по подразбиране. 

C срещу Go

Go синтаксисът дължи много на C - фигурни скоби като разделители, изрази, завършени с точка и запетая и т.н. Разработчиците, владеещи C, обикновено могат да преминат направо в Go без особени затруднения, дори като вземат предвид новите Go функции, като пространства от имена и управление на пакети.

Четеният код беше една от водещите цели на Go за проектиране: Улеснете разработчиците да ускорят работата си с всеки проект на Go и да се запознаят с кодовата база в кратки срокове. Кодните бази на C могат да бъдат трудни за претрупване, тъй като са склонни да се превръщат в гнездо на плъхове от макроси и #ifdefспецифични за проекта и даден екип. Синтаксисът на Go и неговите вградени инструменти за форматиране на код и управление на проекти имат за цел да задържат този вид институционални проблеми.

Go също предлага екстри като goroutines и канали, инструменти на езиково ниво за обработка на едновременност и предаване на съобщения между компонентите. C ще изисква такива неща да бъдат ръчно валцувани или доставяни от външна библиотека, но Go ги предоставя веднага, като улеснява конструирането на софтуер, който се нуждае от тях.

Там, където Go се различава най-много от C под капака, е управлението на паметта. Обектите Go автоматично се управляват и събират боклук по подразбиране. За повечето задачи по програмиране това е изключително удобно. Но това също така означава, че всяка програма, която изисква детерминирано боравене с паметта, ще бъде по-трудна за писане.

Go включва unsafeпакета за заобикаляне на някои от защитите за обработка на типа на Go, като четене и запис на произволна памет с Pointerтип. Но unsafeидва с предупреждение, че програми, написани с него, „може да не са преносими и не са защитени от насоките за съвместимост Go 1“.

Go е подходящ за изграждане на програми като помощни програми от командния ред и мрежови услуги, тъй като те рядко се нуждаят от такива фини манипулации. Но драйверите на устройства на ниско ниво, компонентите на операционната система за пространство на ядрото и други задачи, които изискват строг контрол върху оформлението и управлението на паметта, са най-добре създадени в C.

С срещу ръжда

В някои отношения Rust е отговор на загадките за управление на паметта, създадени от C и C ++, както и на много други недостатъци на тези езици. Rust се компилира в собствен машинен код, така че се счита наравно с C, що се отнася до производителността. Безопасността на паметта по подразбиране обаче е основната точка за продажба на Rust.

Правилата за синтаксис и компилация на Rust помагат на разработчиците да избягват често срещани грешки при управлението на паметта. Ако една програма има проблем с управлението на паметта, който пресича синтаксиса на Rust, тя просто няма да се компилира. Новодошлите в езика, особено от език като C, който осигурява достатъчно място за такива грешки, прекарват първата фаза от обучението си по Rust, научавайки се как да успокоят компилатора. Но привържениците на Rust твърдят, че тази краткосрочна болка има дългосрочна печалба: по-безопасен код, който не жертва скоростта.

Ръждата също подобрява C със своите инструменти. Управлението на проекти и компоненти са част от инструментариума, доставен с Rust по подразбиране, както и с Go. Има препоръчителен начин по подразбиране за управление на пакети, организиране на папки на проекти и обработка на много други неща, които в C са ad-hoc в най-добрия случай, като всеки проект и екип се справят по различен начин.

И все пак това, което се рекламира като предимство в Rust, може да не изглежда като такова за разработчик на C. Функциите за безопасност по време на компилация на Rust не могат да бъдат деактивирани, така че дори и най-тривиалната програма Rust трябва да отговаря на изискванията за безопасност на паметта на Rust. C може да е по-малко безопасно по подразбиране, но е много по-гъвкаво и прощава, когато е необходимо.

Друг възможен недостатък е размерът на езика Rust. C има относително малко функции, дори когато се вземе предвид стандартната библиотека. Наборът от функции Rust се разпростира и продължава да расте. Както при C ++, по-големият набор от функции на Rust означава повече мощност, но и по-голяма сложност. C е по-малък език, но е много по-лесен за мислене, така че може би е по-подходящ за проекти, при които Rust би бил прекален.

C срещу Python

В наши дни, когато се говори за разработка на софтуер, Python винаги изглежда влиза в разговора. В края на краищата Python е „вторият най-добър език за всичко“ и несъмнено един от най-универсалните, с налични хиляди библиотеки на трети страни.

Това, което Python набляга и където се различава най-много от C, благоприятства скоростта на развитие пред скоростта на изпълнение. Програма, която може да отнеме час, за да се събере на друг език - като C - може да бъде сглобена в Python за минути. От друга страна, тази програма може да отнеме секунди за изпълнение в C, но минута за изпълнение в Python. (Добро правило: Програмите на Python обикновено работят с порядък по-бавно от своите аналози на C.) Но за много работни места в съвременния хардуер Python е достатъчно бърз и това е ключово за неговото усвояване.

Друга основна разлика е управлението на паметта. Програмите на Python се управляват изцяло от паметта по време на изпълнение на Python, така че разработчиците не трябва да се притесняват за ниската степен на разпределение и освобождаване на паметта. Но и тук лекотата за разработчици идва за сметка на производителността по време на изпълнение. Писането на програми на C изисква внимателно внимание към управлението на паметта, но получените програми често са златният стандарт за чиста скорост на машината.

Под кожата обаче Python и C споделят дълбока връзка: референтното време за изпълнение на Python е написано на C. Това позволява на програмите на Python да обгръщат библиотеки, написани на C и C ++. Значителни парчета от Python екосистемата на библиотеки на трети страни, като например за машинно обучение, имат в основата си C код.

Ако скоростта на разработка е по-важна от скоростта на изпълнение и ако повечето от изпълняващите се части на програмата могат да бъдат изолирани в самостоятелни компоненти (за разлика от разпространението в целия код), или чист Python, или комбинация от библиотеки Python и C по-добър избор от C сам. В противен случай C все още управлява.