C ( произносится как / ˈs iː / – как буква c ) [6] – язык программирования общего назначения . Он был создан в 1970-х годах Деннисом Ритчи и по-прежнему широко используется и оказывает влияние. По своей конструкции возможности C четко отражают возможности целевых процессоров. Он нашел прочное применение в коде операционных систем (особенно в ядрах [7] ), драйверах устройств и стеках протоколов , но его использование в прикладном программном обеспечении сокращается. [8] C обычно используется в компьютерных архитектурах, которые варьируются от крупнейших суперкомпьютеров до самых маленьких микроконтроллеров и встраиваемых систем .
Преемник языка программирования B , C был первоначально разработан в Bell Labs Ричи между 1972 и 1973 годами для создания утилит, работающих на Unix . Он был применен для повторной реализации ядра операционной системы Unix. [9] В течение 1980-х годов C постепенно набирал популярность. Он стал одним из наиболее широко используемых языков программирования, [10] [11] с компиляторами C, доступными практически для всех современных компьютерных архитектур и операционных систем. Книга «Язык программирования C» , написанная в соавторстве с разработчиком оригинального языка, в течение многих лет служила фактическим стандартом для языка. [12] [1] C был стандартизирован с 1989 года Американским национальным институтом стандартов (ANSI), а затем совместно Международной организацией по стандартизации (ISO) и Международной электротехнической комиссией (IEC).
C — императивный процедурный язык, поддерживающий структурное программирование , лексическую область видимости переменных и рекурсию со статической системой типов . Он был разработан для компиляции с целью предоставления низкоуровневого доступа к памяти и языковым конструкциям, которые эффективно отображаются в машинные инструкции , и все это с минимальной поддержкой времени выполнения . Несмотря на свои низкоуровневые возможности, язык был разработан для поощрения кроссплатформенного программирования. Соответствующая стандартам программа на C, написанная с учетом переносимости , может быть скомпилирована для широкого спектра компьютерных платформ и операционных систем с небольшими изменениями в исходном коде.
С 2000 года C неизменно входит в четверку лучших языков в индексе TIOBE , который является показателем популярности языков программирования. [13]
C — императивный , процедурный язык в традиции ALGOL . Он имеет статическую систему типов . В C весь исполняемый код содержится в подпрограммах (также называемых «функциями», хотя и не в смысле функционального программирования ). Параметры функций передаются по значению, хотя массивы передаются как указатели , т. е. адрес первого элемента в массиве. Передача по ссылке имитируется в C путем явной передачи указателей на то, на что делается ссылка.
Исходный текст программы на языке C представляет собой код свободной формы . Точки с запятой завершают операторы , а фигурные скобки используются для группировки операторов в блоки .
Язык Си также обладает следующими характеристиками:
if/else
, for
, do/while
, while
, и switch
. Пользовательские имена не отличаются от ключевых слов никаким символом .+
, +=
, ++
, &
, ||
и т. д.struct
) позволяют получать доступ к связанным элементам данных и назначать их как единое целое. Содержимое целых структур нельзя сравнивать с помощью одного встроенного оператора (элементы должны сравниваться по отдельности).month[11]
.enum
ключевым словом. Они свободно взаимопреобразуются с целыми числами.void
.static
и extern
.Хотя в языке C отсутствуют некоторые функции, имеющиеся в других языках (например, объектная ориентация и сборка мусора ), их можно реализовать или эмулировать, часто с помощью внешних библиотек (например, объектной системы GLib или сборщика мусора Boehm ).
Многие более поздние языки заимствовали напрямую или косвенно из C, включая C++ , C# , C shell Unix , D , Go , Java , JavaScript (включая транспиляторы ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , Rust , Swift , Verilog и SystemVerilog (языки описания оборудования). [5] Эти языки заимствовали многие из своих управляющих структур и других основных функций из C. Большинство из них также выражают очень похожий синтаксис на C, и они, как правило, объединяют узнаваемый синтаксис выражений и операторов C с базовыми системами типов, моделями данных и семантикой, которые могут радикально отличаться.
Происхождение C тесно связано с разработкой операционной системы Unix , изначально реализованной на языке ассемблера на PDP-7 Деннисом Ритчи и Кеном Томпсоном , включившими несколько идей коллег. В конце концов, они решили перенести операционную систему на PDP-11 . Первоначальная версия Unix для PDP-11 также была разработана на языке ассемблера. [9]
Томпсон хотел язык программирования для разработки утилит для новой платформы. Сначала он попытался написать компилятор Fortran , но вскоре отказался от этой идеи и вместо этого создал урезанную версию недавно разработанного языка системного программирования под названием BCPL . Официальное описание BCPL в то время не было доступно, [14] и Томпсон изменил синтаксис, сделав его менее «многословным» и похожим на упрощенный ALGOL, известный как SMALGOL. [15] Он назвал результат B , [9] описав его как «семантику BCPL с большим количеством синтаксиса SMALGOL». [15] Как и BCPL, B имел самозагружающийся компилятор для облегчения переноса на новые машины. [15] В конечном итоге на B было написано мало утилит, поскольку он был слишком медленным и не мог использовать преимущества функций PDP-11, таких как байтовая адресуемость.
В 1971 году Ритчи начал улучшать B, чтобы использовать возможности более мощного PDP-11. Значительным дополнением стал символьный тип данных. Он назвал его New B (NB). [15] Томпсон начал использовать NB для написания ядра Unix , и его требования сформировали направление развития языка. [15] [16] До 1972 года в язык NB были добавлены более богатые типы: в NB были массивы int
и char
. Также были добавлены указатели, возможность генерировать указатели на другие типы, массивы всех типов и типы, возвращаемые из функций. Массивы внутри выражений стали указателями. Был написан новый компилятор, и язык был переименован в C. [9]
Компилятор C и некоторые утилиты, созданные с его помощью, были включены в версию 2 Unix , которая также известна как Research Unix . [17]
В версии 4 Unix , выпущенной в ноябре 1973 года, ядро Unix было существенно переработано на языке C. [9] К этому времени язык C приобрел некоторые мощные функции, такие как типы.struct
Препроцессор был представлен около 1973 года по настоянию Алана Снайдера, а также в знак признания полезности механизмов включения файлов, доступных в BCPL и PL/I . Его первоначальная версия обеспечивала только включенные файлы и простые замены строк: и макросы без параметров. Вскоре после этого он был расширен, в основном Майком Леском , а затем Джоном Райзером, для включения макросов с аргументами и условной компиляции . [9]#include
#define
Unix был одним из первых ядер операционной системы, реализованных на языке, отличном от ассемблера . Более ранние примеры включают систему Multics (которая была написана на PL/I ) и Master Control Program (MCP) для Burroughs B5000 (которая была написана на ALGOL ) в 1961 году. Около 1977 года Ритчи и Стивен С. Джонсон внесли дополнительные изменения в язык, чтобы облегчить переносимость операционной системы Unix. Компилятор Johnson's Portable C послужил основой для нескольких реализаций C на новых платформах. [16]
В 1978 году Брайан Керниган и Деннис Ритчи опубликовали первое издание книги «Язык программирования Си» [18] . Известная как K&R по инициалам ее авторов, книга долгие годы служила неформальной спецификацией языка . Версия языка Си, которую она описывает, обычно называется « K&R C ». Поскольку она была выпущена в 1978 году, теперь ее также называют C78 [19] . Второе издание книги [20] охватывает более поздний стандарт ANSI C , описанный ниже.
K&R представил несколько языковых функций:
long int
тип данныхunsigned int
тип данных=op
=-
op=
-=
i=-10
i =- 10
i
i = -10
i
Даже после публикации стандарта ANSI 1989 года, в течение многих лет K&R C по-прежнему считался « наименьшим общим знаменателем », которым ограничивали себя программисты на C, когда требовалась максимальная переносимость, поскольку многие старые компиляторы все еще использовались, а тщательно написанный код K&R C также мог быть допустимым стандартным C.
В ранних версиях языка C только функции, возвращающие типы, отличные от , int
должны были быть объявлены, если они использовались до определения функции; функции, используемые без предварительного объявления, как предполагалось, возвращали тип int
.
Например:
long some_function (); /* Это объявление функции, поэтому компилятор может знать имя и возвращаемый тип этой функции. */ /* int */ other_function (); /* Другое объявление функции. Поскольку это ранняя версия C, здесь присутствует неявный тип 'int'. Комментарий показывает, где в более поздних версиях потребуется явный спецификатор типа 'int'. */ /* int */ called_function () /* Это определение функции, включая тело кода, следующего за { фигурными скобками }. Поскольку тип возвращаемого значения не указан, функция неявно возвращает 'int' в этой ранней версии C. */ { long test1 ; register /* int */ test2 ; /* Опять же, обратите внимание, что 'int' здесь не требуется. Спецификатор типа 'int' */ /* в комментарии будет необходим в более поздних версиях C. */ /* Ключевое слово 'register' указывает компилятору, что эта переменная должна */ /* в идеале храниться в регистре, а не в стековом фрейме. */ test1 = some_function (); if ( test1 > 1 ) test2 = 0 ; else test2 = other_function (); return test2 ; }
Спецификаторы типов int
, которые закомментированы, могут быть опущены в K&R C, но они требуются в более поздних стандартах.
Поскольку объявления функций K&R не включали никакой информации об аргументах функций, проверки типов параметров функций не выполнялись, хотя некоторые компиляторы выдавали предупреждающее сообщение, если локальная функция вызывалась с неправильным числом аргументов или если разные вызовы внешней функции использовали разные числа или типы аргументов. Были разработаны отдельные инструменты, такие как утилита lint в Unix , которые (помимо прочего) могли проверять согласованность использования функций в нескольких исходных файлах.
В последующие годы после публикации K&R C в язык было добавлено несколько функций, поддерживаемых компиляторами от AT&T (в частности PCC [21] ) и некоторых других поставщиков. Они включали:
void
функции (т.е. функции без возвращаемого значения)struct
или union
(ранее можно было вернуть только один указатель, целое число или число с плавающей точкой)struct
типов данных#define GREEN 3
)Большое количество расширений и отсутствие соглашения по стандартной библиотеке , а также популярность языка и тот факт, что даже компиляторы Unix не реализовали спецификацию K&R в точности, привели к необходимости стандартизации. [22]
В конце 1970-х и 1980-х годах версии языка C были реализованы для самых разных мэйнфреймов , мини-компьютеров и микрокомпьютеров , включая IBM PC , поскольку его популярность начала значительно расти.
В 1983 году Американский национальный институт стандартов (ANSI) сформировал комитет X3J11 для установления стандартной спецификации языка C. X3J11 основал стандарт языка C на реализации Unix; однако непереносимая часть библиотеки языка C для Unix была передана рабочей группе IEEE 1003, чтобы стать основой для стандарта POSIX 1988 года . В 1989 году стандарт языка C был ратифицирован как ANSI X3.159-1989 «Язык программирования C». Эту версию языка часто называют ANSI C , стандартом C или иногда C89.
В 1990 году стандарт ANSI C (с изменениями форматирования) был принят Международной организацией по стандартизации (ISO) как ISO/IEC 9899:1990, который иногда называют C90. Поэтому термины «C89» и «C90» относятся к одному и тому же языку программирования.
ANSI, как и другие национальные органы по стандартизации, больше не разрабатывает стандарт C самостоятельно, а полагается на международный стандарт C, поддерживаемый рабочей группой ISO/IEC JTC1/SC22 /WG14. Национальное принятие обновления международного стандарта обычно происходит в течение года после публикации ISO.
Одной из целей процесса стандартизации C было создание надмножества K&R C, включающего многие из впоследствии введенных неофициальных функций. Комитет по стандартам также включил несколько дополнительных функций, таких как прототипы функций (заимствованные из C++), void
указатели, поддержка международных наборов символов и локалей , а также улучшения препроцессора. Хотя синтаксис для объявлений параметров был расширен, чтобы включить стиль, используемый в C++, интерфейс K&R по-прежнему был разрешен для совместимости с существующим исходным кодом.
C89 поддерживается текущими компиляторами C, и большая часть современного кода C основана на нем. Любая программа, написанная только на стандартном C и без каких-либо аппаратно-зависимых предположений, будет корректно работать на любой платформе с соответствующей реализацией C в пределах ее ресурсов. Без таких мер предосторожности программы могут компилироваться только на определенной платформе или с определенным компилятором, например, из-за использования нестандартных библиотек, таких как библиотеки GUI , или из-за зависимости от атрибутов компилятора или платформы, таких как точный размер типов данных и порядок байтов .
В случаях, когда код должен компилироваться либо стандартными компиляторами, либо компиляторами K&R на основе C, макрос __STDC__
можно использовать для разделения кода на стандартные и K&R-разделы, чтобы предотвратить использование в компиляторе K&R на основе C функций, доступных только в стандартном C.
После процесса стандартизации ANSI/ISO спецификация языка C оставалась относительно статичной в течение нескольких лет. В 1995 году была опубликована Нормативная поправка 1 к стандарту C 1990 года (ISO/IEC 9899/AMD1:1995, неформально известная как C95), чтобы исправить некоторые детали и добавить более обширную поддержку международных наборов символов. [23]
Стандарт C был дополнительно пересмотрен в конце 1990-х годов, что привело к публикации ISO/IEC 9899:1999 в 1999 году, который обычно называют « C99 ». С тех пор он был изменен трижды Техническими исправлениями. [24]
C99 представил несколько новых функций, включая встроенные функции , несколько новых типов данных (включая long long int
и complex
тип для представления комплексных чисел ), массивы переменной длины и гибкие элементы массива , улучшенную поддержку плавающей точки IEEE 754 , поддержку вариативных макросов (макросов переменной арности ) и поддержку однострочных комментариев, начинающихся с //
, как в BCPL или C++. Многие из них уже были реализованы как расширения в нескольких компиляторах C.
C99 по большей части обратно совместим с C90, но в некоторых отношениях строже; в частности, объявление, в котором отсутствует спецификатор типа, больше не int
подразумевается. Стандартный макрос __STDC_VERSION__
определяется со значением 199901L
, указывающим на доступность поддержки C99. GCC , Solaris Studio и другие компиляторы C теперь [ когда? ] поддерживают многие или все новые возможности C99. Однако компилятор C в Microsoft Visual C++ реализует стандарт C89 и те части C99, которые требуются для совместимости с C++11 . [25] [ нужно обновить ]
Кроме того, стандарт C99 требует поддержки идентификаторов, использующих Unicode в виде экранированных символов (например, \u0040
или \U0001f431
), и предлагает поддержку необработанных имен Unicode.
Работа над очередной редакцией стандарта C началась в 2007 году, неофициально именовавшейся «C1X» до ее официальной публикации ISO/IEC 9899:2011 8 декабря 2011 года. Комитет по стандартам C принял руководящие принципы, ограничивающие принятие новых функций, которые не были протестированы существующими реализациями.
Стандарт C11 добавляет многочисленные новые функции в C и библиотеку, включая макросы типов generic, анонимные структуры, улучшенную поддержку Unicode, атомарные операции, многопоточность и функции проверки границ. Он также делает некоторые части существующей библиотеки C99 необязательными и улучшает совместимость с C++. Стандартный макрос __STDC_VERSION__
определен как 201112L
указывающий на доступность поддержки C11.
Опубликованный в июне 2018 года как ISO/IEC 9899:2018, C17 является текущим стандартом для языка программирования C. Он не вводит новых языковых возможностей, а только технические исправления и пояснения к дефектам в C11. Стандартный макрос __STDC_VERSION__
определен как 201710L
указывающий на доступность поддержки C17.
C23 — неофициальное название следующей (после C17) крупной редакции стандарта языка C. На протяжении большей части разработки он был неофициально известен как «C2X». Ожидается, что C23 будет опубликован в начале 2024 года как ISO/IEC 9899:2024. [26] Стандартный макрос __STDC_VERSION__
определен как 202311L
указывающий на доступность поддержки C23.
C2Y — это временное неофициальное название для следующей крупной ревизии стандарта языка C после C23 (C2X), которая, как ожидается, будет выпущена позднее в десятилетии 2020-х годов, отсюда и «2» в «C2Y». Ранний рабочий проект C2Y был выпущен в феврале 2024 года как N3220 рабочей группой ISO/IEC JTC1/SC22 /WG14. [27]
Исторически сложилось так, что для встраиваемого программирования на языке C требуются нестандартные расширения языка C для поддержки экзотических функций, таких как арифметика с фиксированной точкой , несколько отдельных банков памяти и базовые операции ввода-вывода.
В 2008 году Комитет по стандартам C опубликовал технический отчет , расширяющий язык C [28] для решения этих проблем путем предоставления общего стандарта для всех реализаций. Он включает ряд функций, недоступных в обычном C, таких как арифметика с фиксированной точкой, именованные адресные пространства и базовая аппаратная адресация ввода-вывода.
C имеет формальную грамматику, определенную стандартом C. [29] Окончания строк, как правило, не имеют значения в C; однако границы строк имеют значение во время фазы предварительной обработки. Комментарии могут появляться либо между разделителями /*
и */
, либо (начиная с C99) после //
до конца строки. Комментарии, разделенные /*
и , */
не вкладывают друг в друга, и эти последовательности символов не интерпретируются как разделители комментариев, если они появляются внутри строковых или символьных литералов. [30]
Исходные файлы C содержат объявления и определения функций. Определения функций, в свою очередь, содержат объявления и операторы . Объявления либо определяют новые типы с помощью таких ключевых слов, как struct
, union
, и enum
, либо назначают типы и, возможно, резервируют память для новых переменных, обычно записывая тип с последующим именем переменной. Ключевые слова, как char
и , int
указывают встроенные типы. Разделы кода заключаются в фигурные скобки ( {
и }
, иногда называемые «фигурными скобками»), чтобы ограничить область объявлений и действовать как один оператор для управляющих структур.
Как императивный язык, C использует операторы для указания действий. Наиболее распространенным оператором является оператор выражения , состоящий из выражения для оценки, за которым следует точка с запятой; в качестве побочного эффекта оценки могут вызываться функции и присваиваться переменным новые значения. Для изменения обычного последовательного выполнения операторов C предоставляет несколько операторов потока управления, идентифицируемых зарезервированными ключевыми словами. Структурированное программирование поддерживается if
... [ else
] условным выполнением и do
... while
, while
, и for
итеративным выполнением (циклированием). for
Оператор имеет отдельные выражения инициализации, тестирования и повторной инициализации, любое или все из которых могут быть опущены. break
и continue
может использоваться внутри цикла. Break используется для выхода из самого внутреннего охватывающего оператора цикла, а continue используется для перехода к его повторной инициализации. Существует также неструктурированный goto
оператор, который напрямую переходит к назначенной метке внутри функции. switch
выбирает case
для выполнения на основе значения целочисленного выражения. В отличие от многих других языков, поток управления перейдет к следующему, case
если не будет завершен с помощью break
.
Выражения могут использовать множество встроенных операторов и могут содержать вызовы функций. Порядок, в котором оцениваются аргументы функций и операнды большинства операторов, не определен. Оценки могут даже чередоваться. Однако все побочные эффекты (включая сохранение в переменных) будут иметь место до следующей « точки последовательности »; точки последовательности включают конец каждого оператора выражения, а также вход в каждый вызов функции и возврат из него. Точки последовательности также возникают во время оценки выражений, содержащих определенные операторы ( &&
, ||
, ?:
и оператор запятая ). Это обеспечивает высокую степень оптимизации объектного кода компилятором, но требует от программистов на языке C большей осторожности для получения надежных результатов, чем это необходимо для других языков программирования.
Керниган и Ритчи говорят в «Введении в язык программирования Си» : «С, как и любой другой язык, имеет свои недостатки. Некоторые операторы имеют неправильный приоритет; некоторые части синтаксиса могли бы быть лучше». [31] Стандарт Си не пытался исправить многие из этих недостатков из-за влияния таких изменений на уже существующее программное обеспечение.
Базовый набор исходных символов языка C включает следующие символы:
a
– z
, A
–Z
0
–9
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
Символ новой строки обозначает конец текстовой строки; он не обязательно должен соответствовать отдельному символу, хотя для удобства язык C рассматривает его именно так.
Дополнительные многобайтовые кодированные символы могут использоваться в строковых литералах , но они не полностью переносимы . Последний стандарт C ( C11 ) позволяет переносимо встраивать многонациональные символы Unicode в исходный текст C с помощью \uXXXX
или \UXXXXXXXX
кодировки (где X
обозначает шестнадцатеричный символ), хотя эта функция пока не получила широкого распространения. [ требуется обновление ]
Базовый набор символов выполнения C содержит те же символы, а также представления для alert , backspace и cart return . Поддержка времени выполнения для расширенных наборов символов увеличивалась с каждой редакцией стандарта C.
Следующие зарезервированные слова чувствительны к регистру .
C89 имеет 32 зарезервированных слова, также известных как «ключевые слова», которые нельзя использовать ни для каких целей, кроме тех, для которых они предопределены:
C99 добавил еще пять зарезервированных слов: (‡ указывает на альтернативный вариант написания ключевого слова C23)
C11 добавил еще семь зарезервированных слов: [32] (‡ указывает на альтернативный вариант написания ключевого слова C23)
_Alignas
‡_Alignof
‡_Atomic
_Generic
_Noreturn
_Static_assert
‡_Thread_local
‡C23 зарезервировал еще пятнадцать слов:
alignas
alignof
bool
constexpr
false
nullptr
static_assert
thread_local
true
typeof
typeof_unqual
_BitInt
_Decimal32
_Decimal64
_Decimal128
Большинство недавно зарезервированных слов начинаются с подчеркивания, за которым следует заглавная буква, поскольку идентификаторы такой формы ранее были зарезервированы стандартом C для использования только реализациями. Поскольку существующий исходный код программы не должен был использовать эти идентификаторы, он не будет затронут, когда реализации C начнут поддерживать эти расширения языка программирования. Некоторые стандартные заголовки определяют более удобные синонимы для подчеркнутых идентификаторов. Некоторые из этих слов были добавлены как ключевые слова с их обычным написанием в C23, а соответствующие макросы были удалены.
До C89 entry
было зарезервировано как ключевое слово. Во втором издании своей книги «Язык программирования C» , в которой описывается то, что стало известно как C89, Керниган и Ритчи написали: «… [ключевое слово] entry
, ранее зарезервированное, но никогда не использовавшееся, больше не зарезервировано». и « entry
Ключевое слово «мертворожденный» отозвано». [33]
C поддерживает богатый набор операторов , которые являются символами, используемыми в выражении для указания манипуляций, которые необходимо выполнить при оценке этого выражения. В C есть операторы для:
=
+=
, , , , , , , , , -=
*=
/=
%=
&=
|=
^=
<<=
>>=
~
, &
, |
,^
<<
,>>
!
, &&
,||
( )
++
,--
.
,->
sizeof
typeof
, typeof_unqual
начиная с C23<
, <=
, >
,>=
&
, *
,[ ]
( )
(typename)
C использует оператор =
(используемый в математике для выражения равенства) для указания присваивания, следуя прецеденту Fortran и PL/I , но в отличие от ALGOL и его производных. C использует оператор ==
для проверки на равенство. Сходство между операторами присваивания и равенства может привести к случайному использованию одного вместо другого, и во многих случаях ошибка не приводит к появлению сообщения об ошибке (хотя некоторые компиляторы выдают предупреждения). Например, условное выражение if (a == b + 1)
может быть ошибочно записано как if (a = b + 1)
, что будет оценено как, true
если только значение a
не находится 0
после присваивания. [34]
Приоритет оператора C не всегда интуитивно понятен. Например, оператор ==
связывает более тесно (выполняется до) операторов &
(побитовое И) и |
(побитовое ИЛИ) в выражениях, таких как x & 1 == 0
, которые должны быть написаны так, как (x & 1) == 0
будто это намерение кодера. [35]
Пример "hello, world", появившийся в первом издании K&R , стал моделью для вводной программы в большинстве учебников по программированию. Программа выводит "hello, world" на стандартный вывод , который обычно является терминалом или экраном.
Первоначальная версия была: [36]
main () { printf ( "привет, мир \n " ); }
Соответствующая стандарту программа «Привет, мир» выглядит следующим образом: [a]
#include <stdio.h> int main ( void ) { printf ( "привет, мир \n " ); }
Первая строка программы содержит директиву предварительной обработки , обозначенную как #include
. Это заставляет компилятор заменить эту строку кода всем текстом stdio.h
файла заголовка, который содержит объявления для стандартных функций ввода и вывода, таких как printf
и scanf
. Угловые скобки, окружающие файл заголовка, stdio.h
указывают, что файл заголовка можно найти с помощью стратегии поиска, которая отдает предпочтение заголовкам, предоставленным с компилятором, другим заголовкам с тем же именем (в отличие от двойных кавычек, которые обычно включают локальные или специфичные для проекта файлы заголовков).
main
Вторая строка указывает, что определяется функция с именем . main
Функция выполняет особую функцию в программах на языке C; среда выполнения вызывает main
функцию, чтобы начать выполнение программы. Спецификатор типа int
указывает, что возвращаемое вызывающему (в данном случае среде выполнения) значение в результате вычисления main
функции является целым числом. Ключевое слово void
as a список параметров указывает, что main
функция не принимает аргументов. [b]
Открывающая фигурная скобка указывает на начало кода, определяющего main
функцию.
Следующая строка программы — это оператор, который вызывает (т. е. перенаправляет выполнение) функцию с именем printf
, которая в данном случае предоставляется из системной библиотеки . В этом вызове printf
функции передается (т. е. предоставляется) один аргумент, который является адресом первого символа в строковом литерале "hello, world\n"
. Строковый литерал — это безымянный массив , автоматически созданный компилятором, с элементами типа char
и конечным символом NULL (значение ASCII 0), отмечающим конец массива (чтобы можно было printf
определить длину строки). Символ NULL также может быть записан как escape-последовательность \0
. Это \n
стандартная escape-последовательность, которую C транслирует в символ новой строки , который на выходе означает конец текущей строки. Возвращаемое значение функции printf
имеет тип int
, но оно молча отбрасывается, поскольку не используется. (Более осторожная программа может проверить возвращаемое значение, чтобы убедиться, что функция printf
выполнена успешно.) Точка с запятой ;
завершает оператор.
Закрывающая фигурная скобка указывает на конец кода функции main
. Согласно спецификации C99 и более поздним версиям, main
функция (в отличие от любой другой функции) неявно вернет значение 0
при достижении , }
которое завершает функцию. [c] Возвращаемое значение 0
интерпретируется системой выполнения как код выхода, указывающий на успешное выполнение функции. [37]
Система типов в C является статической и слабо типизированной , что делает ее похожей на систему типов потомков ALGOL, таких как Pascal . [38] Существуют встроенные типы для целых чисел различных размеров, как со знаком, так и без знака, чисел с плавающей точкой и перечислимых типов ( enum
). Целочисленный тип char
часто используется для однобайтовых символов. В C99 был добавлен логический тип данных . Существуют также производные типы, включая массивы , указатели , записи ( struct
) и объединения ( union
).
C часто используется в низкоуровневом системном программировании, где могут потребоваться выходы из системы типов. Компилятор пытается обеспечить корректность типов большинства выражений, но программист может переопределить проверки различными способами, либо используя приведение типов для явного преобразования значения из одного типа в другой, либо используя указатели или объединения для переинтерпретации базовых битов объекта данных каким-либо другим способом.
Некоторые считают синтаксис объявления языка C неинтуитивным, особенно для указателей функций . (Идея Ритчи состояла в том, чтобы объявлять идентификаторы в контекстах, напоминающих их использование: « объявление отражает использование ».) [39]
Обычные арифметические преобразования C позволяют генерировать эффективный код, но иногда могут давать неожиданные результаты. Например, сравнение знаковых и беззнаковых целых чисел одинаковой ширины требует преобразования знакового значения в беззнаковое. Это может генерировать неожиданные результаты, если знаковое значение отрицательно.
C поддерживает использование указателей , типа ссылки , которая записывает адрес или местоположение объекта или функции в памяти. Указатели могут быть разыменованы для доступа к данным, хранящимся по адресу, на который они указывают, или для вызова функции, на которую они указывают. Указатели могут быть обработаны с помощью присваивания или арифметики указателей . Представление значения указателя во время выполнения обычно представляет собой необработанный адрес памяти (возможно, дополненный полем смещения внутри слова), но поскольку тип указателя включает тип указываемой вещи, выражения, включающие указатели, могут быть проверены на соответствие типу во время компиляции. Арифметика указателей автоматически масштабируется по размеру типа данных, на который они указывают.
Указатели используются для многих целей в C. Текстовые строки обычно обрабатываются с помощью указателей в массивах символов. Динамическое выделение памяти выполняется с помощью указателей; результат a malloc
обычно приводится к типу данных данных, которые должны быть сохранены. Многие типы данных, такие как деревья , обычно реализуются как динамически выделяемые struct
объекты, связанные вместе с помощью указателей. Указатели на другие указатели часто используются в многомерных массивах и массивах struct
объектов. Указатели на функции ( указатели функций ) полезны для передачи функций в качестве аргументов функциям более высокого порядка (таким как qsort или bsearch ), в таблицах диспетчеризации или в качестве обратных вызовов обработчикам событий . [37]
Значение указателя null явно не указывает на допустимое местоположение. Разыменование значения указателя null не определено, что часто приводит к ошибке сегментации . Значения указателя null полезны для указания особых случаев, таких как отсутствие указателя «next» в конечном узле связанного списка , или как указание на ошибку от функций, возвращающих указатели. В соответствующих контекстах в исходном коде, таких как назначение переменной указателя, константа указателя null может быть записана как 0
, с явным приведением к типу указателя или без него, как NULL
макрос, определенный несколькими стандартными заголовками или, начиная с C23, с константой nullptr
. В условных контекстах значения указателя null оцениваются как false
, в то время как все другие значения указателя оцениваются как true
.
Указатели void ( void *
) указывают на объекты неопределенного типа и, следовательно, могут использоваться как «общие» указатели данных. Поскольку размер и тип указываемого объекта неизвестны, указатели void не могут быть разыменованы, и арифметика указателей на них не допускается, хотя они могут быть легко (и во многих контекстах неявно так и происходит) преобразованы в любой другой тип указателя объекта и из него. [37]
Небрежное использование указателей потенциально опасно. Поскольку они обычно не проверяются, переменную-указатель можно заставить указывать на любое произвольное место, что может вызвать нежелательные эффекты. Хотя правильно используемые указатели указывают на безопасные места, их можно заставить указывать на небезопасные места, используя недопустимую арифметику указателей ; объекты, на которые они указывают, могут продолжать использоваться после освобождения ( висячие указатели ); их можно использовать без инициализации ( дикие указатели ); или им может быть напрямую присвоено небезопасное значение с помощью приведения, объединения или через другой поврежденный указатель. В целом, C допускает манипуляцию и преобразование между типами указателей, хотя компиляторы обычно предоставляют возможности для различных уровней проверки. Некоторые другие языки программирования решают эти проблемы, используя более ограничивающие ссылочные типы.
Типы массивов в C традиционно имеют фиксированный, статический размер, указанный во время компиляции. Более поздний стандарт C99 также допускает форму массивов переменной длины. Однако также возможно выделить блок памяти (произвольного размера) во время выполнения, используя malloc
функцию стандартной библиотеки, и рассматривать его как массив.
Поскольку доступ к массивам всегда осуществляется (фактически) через указатели, доступ к массиву обычно не проверяется на соответствие базовому размеру массива, хотя некоторые компиляторы могут предоставлять проверку границ в качестве опции. [40] [41] Поэтому возможны нарушения границ массива, которые могут привести к различным последствиям, включая незаконный доступ к памяти, повреждение данных, переполнение буфера и исключения во время выполнения.
В языке C нет специального положения для объявления многомерных массивов , но вместо этого он полагается на рекурсию в системе типов для объявления массивов массивов, что фактически выполняет то же самое. Значения индекса результирующего «многомерного массива» можно рассматривать как увеличивающиеся в порядке возрастания строк . Многомерные массивы обычно используются в численных алгоритмах (в основном из прикладной линейной алгебры ) для хранения матриц. Структура массива C хорошо подходит для этой конкретной задачи. Однако в ранних версиях C границы массива должны быть известными фиксированными значениями или явно передаваться любой подпрограмме, которая их требует, а к динамическим массивам массивов нельзя получить доступ с помощью двойной индексации. (Обходным путем для этого было выделение массиву дополнительного «вектора-строки» указателей на столбцы.) В C99 были введены «массивы переменной длины», которые решают эту проблему.
Следующий пример с использованием современного языка C (C99 или более поздней версии) демонстрирует размещение двумерного массива в куче и использование индексации многомерного массива для доступа (который может использовать проверку границ во многих компиляторах C):
int func ( int N , int M ) { float ( * p )[ N ] [ M ] = malloc ( sizeof * p ); if ( p == 0 ) return -1 ; for ( int i = 0 ; i < N ; i ++ ) for ( int j = 0 ; j < M ; j ++ ) ( * p )[ i ] [ j ] = i + j ; print_array ( N , M , p ); free ( p ); return 1 ; }
А вот похожая реализация с использованием функции Auto VLA C99 : [d]
int func ( int N , int M ) { // Внимание: необходимо выполнить проверки, чтобы убедиться, что N*M*sizeof(float) НЕ превышает ограничений для автоматических VLA и находится в пределах доступного размера стека. float p [ N ] [ M ]; // автоматический VLA хранится в стеке и его размер изменяется при вызове функции for ( int i = 0 ; i < N ; i ++ ) for ( int j = 0 ; j < M ; j ++ ) p [ i ] [ j ] = i + j ; print_array ( N , M , p ); // нет необходимости в free(p), так как он исчезнет при выходе из функции вместе с остальной частью стекового фрейма return 1 ; }
Индексная нотация x[i]
(где x
обозначает указатель) является синтаксическим сахаром для *(x+i)
. [42] Используя знание компилятором типа указателя, адрес, который x + i
указывает на , не является базовым адресом (на который указывает x
), увеличенным на i
байты, а скорее определяется как базовый адрес, увеличенный на , i
умноженный на размер элемента, x
на который указывает. Таким образом, x[i]
обозначает i+1
th-й элемент массива.
Более того, в большинстве контекстов выражений (заметным исключением является операнд sizeof
), выражение типа массива автоматически преобразуется в указатель на первый элемент массива. Это подразумевает, что массив никогда не копируется целиком, когда он назван как аргумент функции, а передается только адрес его первого элемента. Поэтому, хотя вызовы функций в C используют семантику передачи по значению , массивы фактически передаются по ссылке .
Общий размер массива x
можно определить, применив sizeof
к выражению типа массива. Размер элемента можно определить, применив оператор sizeof
к любому разыменованному элементу массива A
, как в n = sizeof A[0]
. Таким образом, количество элементов в объявленном массиве A
можно определить как sizeof A / sizeof A[0]
. Обратите внимание, что если доступен только указатель на первый элемент, как это часто бывает в коде C из-за описанного выше автоматического преобразования, информация о полном типе массива и его длине теряется.
Одной из важнейших функций языка программирования является предоставление возможностей для управления памятью и объектами, которые хранятся в памяти. C предоставляет три основных способа выделения памяти для объектов: [37]
malloc
из области памяти, называемой кучей ; эти блоки сохраняются до тех пор, пока не будут освобождены для повторного использования путем вызова библиотечной функции realloc
или free
.Эти три подхода подходят для разных ситуаций и имеют различные компромиссы. Например, статическое выделение памяти имеет небольшие накладные расходы, автоматическое выделение может включать немного больше накладных расходов, а динамическое выделение памяти может потенциально иметь большие накладные расходы как для выделения, так и для освобождения. Постоянная природа статических объектов полезна для сохранения информации о состоянии между вызовами функций, автоматическое выделение легко использовать, но пространство стека, как правило, гораздо более ограничено и временно, чем статическая память или пространство кучи, а динамическое выделение памяти позволяет удобно выделять объекты, размер которых известен только во время выполнения. Большинство программ на языке C широко используют все три.
Там, где это возможно, автоматическое или статическое выделение обычно является самым простым, поскольку хранилищем управляет компилятор, освобождая программиста от потенциально подверженной ошибкам рутинной работы по ручному выделению и освобождению хранилища. Однако многие структуры данных могут изменяться в размере во время выполнения, и поскольку статическое выделение (и автоматическое выделение до C99) должно иметь фиксированный размер во время компиляции, существует много ситуаций, в которых необходимо динамическое выделение. [37] До стандарта C99 распространенным примером этого были массивы переменного размера. (См. статью о динамическом выделении памяти C для примера динамически выделенных массивов.) В отличие от автоматического выделения, которое может дать сбой во время выполнения с неконтролируемыми последствиями, функции динамического выделения возвращают указание (в виде значения нулевого указателя), когда требуемое хранилище не может быть выделено. (Статическое выделение, которое слишком велико, обычно обнаруживается компоновщиком или загрузчиком , прежде чем программа сможет даже начать выполнение.)
Если не указано иное, статические объекты содержат нулевые или пустые значения указателя при запуске программы. Автоматически и динамически выделяемые объекты инициализируются только в том случае, если начальное значение указано явно; в противном случае они изначально имеют неопределенные значения (обычно это любая битовая комбинация , которая случайно присутствует в хранилище , которая может даже не представлять допустимое значение для этого типа). Если программа пытается получить доступ к неинициализированному значению, результаты не определены. Многие современные компиляторы пытаются обнаружить и предупредить об этой проблеме, но могут возникнуть как ложные срабатывания, так и ложные отрицания .
Выделение памяти кучи должно быть синхронизировано с ее фактическим использованием в любой программе, чтобы ее можно было повторно использовать как можно чаще. Например, если единственный указатель на выделение памяти кучи выходит из области действия или его значение перезаписывается до того, как он был явно освобожден, то эта память не может быть восстановлена для последующего повторного использования и по сути теряется для программы, явление, известное как утечка памяти . И наоборот, возможно освобождение памяти, но последующее обращение к ней приводит к непредсказуемым результатам. Обычно симптомы сбоя проявляются в части программы, не связанной с кодом, вызывающим ошибку, что затрудняет диагностику сбоя. Такие проблемы смягчаются в языках с автоматической сборкой мусора .
Язык программирования C использует библиотеки в качестве основного метода расширения. В C библиотека представляет собой набор функций, содержащихся в одном файле «архива». Каждая библиотека обычно имеет заголовочный файл , который содержит прототипы функций, содержащихся в библиотеке, которые могут использоваться программой, а также объявления специальных типов данных и макросимволов, используемых с этими функциями. Чтобы программа могла использовать библиотеку, она должна включать заголовочный файл библиотеки, а библиотека должна быть связана с программой, что во многих случаях требует флагов компилятора (например, -lm
, сокращение для «link the math library»). [37]
Наиболее распространенной библиотекой C является стандартная библиотека C , которая определена стандартами ISO и ANSI C и поставляется с каждой реализацией C (реализации, нацеленные на ограниченные среды, такие как встроенные системы, могут предоставлять только подмножество стандартной библиотеки). Эта библиотека поддерживает потоковый ввод и вывод, выделение памяти, математику, символьные строки и значения времени. Несколько отдельных стандартных заголовков (например, stdio.h
) определяют интерфейсы для этих и других стандартных библиотечных возможностей.
Другой распространенный набор функций библиотеки C — это те, которые используются приложениями, специально предназначенными для Unix и Unix-подобных систем, особенно функции, которые предоставляют интерфейс к ядру . Эти функции подробно описаны в различных стандартах, таких как POSIX и Single UNIX Specification .
Поскольку многие программы были написаны на языке C, существует широкий спектр других доступных библиотек. Библиотеки часто пишутся на языке C, поскольку компиляторы C генерируют эффективный объектный код ; затем программисты создают интерфейсы к библиотеке, чтобы процедуры можно было использовать из языков более высокого уровня, таких как Java , Perl и Python . [37]
Файловый ввод и вывод (I/O) не является частью самого языка C, а вместо этого обрабатывается библиотеками (например, стандартной библиотекой C) и связанными с ними заголовочными файлами (например stdio.h
). Обработка файлов обычно реализуется посредством высокоуровневого ввода-вывода, который работает через потоки . С этой точки зрения поток представляет собой поток данных, который не зависит от устройств, в то время как файл является конкретным устройством. Высокоуровневый ввод-вывод выполняется посредством ассоциации потока с файлом. В стандартной библиотеке C буфер ( область памяти или очередь) временно используется для хранения данных перед их отправкой в конечный пункт назначения. Это сокращает время ожидания более медленных устройств, например, жесткого диска или твердотельного накопителя . Функции низкоуровневого ввода-вывода не являются частью стандартной библиотеки C [ необходимо разъяснение ], но обычно являются частью программирования «на голом железе» (программирования, которое не зависит от какой-либо операционной системы , например, большинства встроенных программ ). За некоторыми исключениями, реализации включают низкоуровневый ввод-вывод.
Разработан ряд инструментов, помогающих программистам на языке C находить и исправлять операторы с неопределенным поведением или возможно ошибочными выражениями с большей строгостью, чем та, которую предоставляет компилятор. Инструмент lint был первым таким инструментом, который привел к появлению многих других.
Автоматизированная проверка и аудит исходного кода полезны для любого языка, и для C существует множество таких инструментов, таких как Lint . Распространенной практикой является использование Lint для обнаружения сомнительного кода при первом написании программы. После того, как программа проходит Lint, она затем компилируется с помощью компилятора C. Кроме того, многие компиляторы могут опционально предупреждать о синтаксически допустимых конструкциях, которые, скорее всего, на самом деле являются ошибками. MISRA C — это фирменный набор рекомендаций по избежанию такого сомнительного кода, разработанный для встраиваемых систем. [43]
Существуют также компиляторы, библиотеки и механизмы уровня операционной системы для выполнения действий, которые не являются стандартной частью языка C, таких как проверка границ массивов, обнаружение переполнения буфера , сериализация , отслеживание динамической памяти и автоматическая сборка мусора .
Такие инструменты, как Purify или Valgrind , а также связь с библиотеками, содержащими специальные версии функций выделения памяти, могут помочь обнаружить ошибки времени выполнения при использовании памяти. [44] [45]
C широко используется для системного программирования при реализации операционных систем и встроенных системных приложений. [46] Это происходит по нескольким причинам:
malloc
и free
более сложный механизм с аренами или версию для ядра ОС , которая может подходить для прямого доступа к памяти (DMA) , использоваться в обработчиках прерываний или интегрироваться с системой виртуальной памяти .C позволяет программистам создавать эффективные реализации алгоритмов и структур данных, поскольку уровень абстракции от оборудования тонок, а его накладные расходы низки, что является важным критерием для программ с интенсивными вычислениями. Например, GNU Multiple Precision Arithmetic Library , GNU Scientific Library , Mathematica и MATLAB полностью или частично написаны на C. Многие языки поддерживают вызов библиотечных функций на C, например, фреймворк NumPy на основе Python использует C для аспектов высокой производительности и взаимодействия с оборудованием.
C иногда используется как промежуточный язык реализациями других языков. Этот подход может использоваться для переносимости или удобства; при использовании C как промежуточного языка дополнительные машинно-специфичные генераторы кода не нужны. C имеет некоторые функции, такие как директивы препроцессора номеров строк и необязательные лишние запятые в конце списков инициализаторов, которые поддерживают компиляцию сгенерированного кода. Однако некоторые из недостатков C побудили разработать другие основанные на C языки, специально разработанные для использования в качестве промежуточных языков, такие как C-- . Кроме того, современные основные компиляторы GCC и LLVM оба имеют промежуточное представление , которое не является C, и эти компиляторы поддерживают внешние интерфейсы для многих языков, включая C.
Следствием широкой доступности и эффективности языка C является то, что компиляторы , библиотеки и интерпретаторы других языков программирования часто реализуются на языке C. [47] Например, эталонные реализации Python , [48] Perl , [49] Ruby , [50] и PHP [51] написаны на языке C.
Исторически C иногда использовался для веб-разработки с использованием Common Gateway Interface (CGI) в качестве «шлюза» для информации между веб-приложением, сервером и браузером. [52] C, возможно, был выбран среди интерпретируемых языков из-за его скорости, стабильности и почти универсальной доступности. [53] Веб-разработка больше не является общепринятой практикой, [54] и существует множество других инструментов веб-разработки .
Два самых популярных веб-сервера , Apache HTTP Server и Nginx , написаны на языке C. Эти веб-серверы взаимодействуют с операционной системой, прослушивают порты TCP на предмет HTTP-запросов, а затем обслуживают статический веб-контент или вызывают выполнение других языков обработки для «рендеринга» контента, таких как PHP , который сам по себе в основном написан на языке C. Подход C, близкий к «железу», позволяет создавать эти высокопроизводительные программные системы.
Язык C также широко использовался для реализации приложений конечного пользователя . [55] Однако такие приложения могут быть написаны и на более новых языках более высокого уровня.
мощь языка ассемблера и удобство ... языка ассемблера
— Деннис Ричи [56]
Несмотря на то, что язык C популярен, влиятелен и чрезвычайно успешен, у него есть недостатки, в том числе:
malloc
и free
подвержена ошибкам. Ошибки включают: утечки памяти, когда память выделяется, но не освобождается; и доступ к ранее освобожденной памяти.scanf
или strncat
, могут привести к переполнению буфера .#pragma
[ 57] [58] , а некоторые — с помощью дополнительных ключевых слов, например, use __cdecl
calling convention. Но директива и параметры не поддерживаются последовательно. [59]setjmp
иlongjmp
были использованы [61] для реализации механизма try-catch с помощью макросов.Для некоторых целей были приняты ограниченные стили C, например MISRA C или CERT C , в попытке уменьшить возможность ошибок. Базы данных, такие как CWE, пытаются подсчитать, как C и т. д. имеет уязвимости, вместе с рекомендациями по смягчению.
Существуют инструменты, которые могут смягчить некоторые недостатки. Современные компиляторы C включают проверки, которые могут генерировать предупреждения, помогающие выявить множество потенциальных ошибок.
Язык C оказал как прямое, так и косвенное влияние на многие более поздние языки, такие как C++ и Java . [63] Наиболее сильное влияние было синтаксическим; все упомянутые языки объединяют синтаксис операторов и (более или менее узнаваемый) выражений языка C с системами типов, моделями данных или крупномасштабными программными структурами, которые отличаются от таковых в языке C, иногда радикально.
Существует несколько интерпретаторов языка C или близких к нему языков, включая Ch и CINT , которые также можно использовать для написания скриптов.
Когда объектно-ориентированные языки программирования стали популярными, C++ и Objective-C были двумя различными расширениями C, которые предоставляли объектно-ориентированные возможности. Оба языка изначально были реализованы как компиляторы исходного кода ; исходный код был переведен в C, а затем скомпилирован с помощью компилятора C. [64]
Язык программирования C++ (первоначально названный «C with Classes ») был разработан Бьярне Страуструпом как подход к предоставлению объектно-ориентированной функциональности с синтаксисом, подобным C. [65] C++ добавляет большую силу типизации, область действия и другие инструменты, полезные в объектно-ориентированном программировании, и допускает обобщенное программирование с помощью шаблонов. Почти надмножество C, C++ теперь [ когда? ] поддерживает большую часть C, за некоторыми исключениями .
Objective-C изначально был очень «тонким» слоем поверх C и остается строгим надмножеством C, которое допускает объектно-ориентированное программирование с использованием гибридной динамической/статической парадигмы типизации. Objective-C унаследовал свой синтаксис как от C, так и от Smalltalk : синтаксис, включающий предварительную обработку, выражения, объявления функций и вызовы функций, унаследован от C, в то время как синтаксис для объектно-ориентированных функций изначально был взят из Smalltalk.
Помимо C++ и Objective-C , Ch , Cilk и Unified Parallel C являются почти надмножествами C.
main
на самом деле имеет два аргумента, int argc
и char *argv[]
, соответственно, которые могут использоваться для обработки аргументов командной строки . Стандарт ISO C (раздел 5.1.2.2.1) требует main
поддержки обеих форм , что является особым режимом, не предусмотренным ни для одной другой функции.return 0;
в конце функции требовалось явное указание main
.print_array
(не показан) немного отличается, [ почему? ] тоже.1980-е: Verilog впервые представлен; Verilog вдохновлен языком программирования C