C ( произносится / ˈ s iː / – как буква c ) [6] — язык компьютерного программирования общего назначения . Он был создан в 1970-х годах Деннисом Ритчи и остается очень широко используемым и влиятельным. По замыслу функции C четко отражают возможности целевых процессоров. Он нашел устойчивое применение в операционных системах , драйверах устройств и стеках протоколов , но его использование в прикладном программном обеспечении сокращается. [7] C обычно используется в компьютерных архитектурах, от крупнейших суперкомпьютеров до самых маленьких микроконтроллеров и встроенных систем .
Преемник языка программирования B , C был первоначально разработан в Bell Labs Ритчи между 1972 и 1973 годами для создания утилит, работающих на Unix . Он был применен для повторной реализации ядра операционной системы Unix. [8] В 1980-х годах язык C постепенно набирал популярность. Он стал одним из наиболее широко используемых языков программирования, [9] [10] с компиляторами C , доступными практически для всех современных компьютерных архитектур и операционных систем. Книга «Язык программирования C» , соавтором которой является первоначальный разработчик языка, в течение многих лет служила фактическим стандартом для языка. [11] [1] C стандартизируется с 1989 года Американским национальным институтом стандартов (ANSI) и Международной организацией по стандартизации (ISO).
C — императивный процедурный язык, поддерживающий структурное программирование , область видимости лексических переменных и рекурсию , со статической системой типов . Он был разработан для компиляции с целью обеспечения низкоуровневого доступа к памяти и языковым конструкциям, которые эффективно сопоставляются с машинными инструкциями , и все это с минимальной поддержкой во время выполнения . Несмотря на свои низкоуровневые возможности, язык был разработан для поощрения кроссплатформенного программирования. Соответствующая стандартам программа на языке C, написанная с учетом переносимости , может быть скомпилирована для широкого спектра компьютерных платформ и операционных систем с небольшими изменениями в ее исходном коде. [12]
С 2000 года C неизменно входит в число двух лучших языков в индексе TIOBE , показателе популярности языков программирования. [13]
C — императивный процедурный язык в традиции АЛГОЛА . Он имеет статическую систему типов . В языке C весь исполняемый код содержится в подпрограммах (также называемых «функциями», но не в смысле функционального программирования ). Параметры функции передаются по значению, хотя массивы передаются как указатели , то есть адрес первого элемента массива. Передача по ссылке моделируется в C путем явной передачи указателей на объект, на который имеется ссылка.
Исходный текст программы на языке C представляет собой код в свободной форме . Точка с запятой завершает операторы , а фигурные скобки используются для группировки операторов в блоки .
Язык C также обладает следующими характеристиками:
if/else
: , for
, do/while
, while
и switch
. Пользовательские имена не отличаются от ключевых слов никакими символами .+
, +=
, ++
, &
, ||
и т.д.struct
) позволяют получать доступ к связанным элементам данных и назначать их как единое целое. Содержимое целых структур нельзя сравнивать с помощью одного встроенного оператора (элементы необходимо сравнивать индивидуально).month[11]
.enum
ключевого слова. Они свободно конвертируются в целые числа.void
.static
атрибуты extern
.Хотя C не включает в себя определенные функции, имеющиеся в других языках (такие как объектная ориентация и сборка мусора ), их можно реализовать или эмулировать, часто с помощью внешних библиотек (например, GLib Object System или сборщика мусора Boehm ).
Многие более поздние языки заимствовали прямо или косвенно из C, включая C++ , C# , оболочку C Unix , D , Go , Java , JavaScript (включая транспиляторы ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , Rust , Swift , Verilog и SystemVerilog (языки описания оборудования). [5] Эти языки заимствовали многие из своих управляющих структур и других основных функций из C. Большинство из них (Python является резким исключением) также имеют синтаксис, очень похожий на C, и они имеют тенденцию сочетать узнаваемый синтаксис выражений и операторов C. с базовыми системами типов, моделями данных и семантикой, которые могут радикально отличаться.
Происхождение языка C тесно связано с разработкой операционной системы Unix , первоначально реализованной на языке ассемблера на PDP-7 Деннисом Ритчи и Кеном Томпсоном , включившей в себя несколько идей коллег. В конце концов, они решили портировать операционную систему на PDP-11 . Исходная версия Unix PDP-11 также была разработана на языке ассемблера. [8]
Томпсону нужен был язык программирования для разработки утилит для новой платформы. Сначала он пытался написать компилятор Фортрана , но вскоре отказался от этой идеи. Вместо этого он создал урезанную версию недавно разработанного языка системного программирования под названием BCPL . Официальное описание BCPL в то время не было доступно [14] , и Томпсон изменил синтаксис, сделав его менее многословным и похожим на упрощенный АЛГОЛ , известный как SMALGOL. [15] Томпсон назвал результат B. [8] Он описал B как «семантику 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. [8]
Компилятор C и некоторые созданные с его помощью утилиты были включены в версию 2 Unix , также известную как Research Unix . [17]
В версии 4 Unix , выпущенной в ноябре 1973 года, ядро Unix было широко переработано на языке C. [8] К этому времени язык C приобрел некоторые мощные функции, такие как типы.struct
Препроцессор был представлен примерно в 1973 году по настоянию Алана Снайдера, а также в знак признания полезности механизмов включения файлов, доступных в BCPL и PL /I . Его первоначальная версия обеспечивала только включаемые файлы и простые замены строк, #include
а также #define
макросы без параметров. Вскоре после этого он был расширен, в основном Майком Леском , а затем Джоном Райзером, для включения макросов с аргументами и условной компиляции . [8]
Unix был одним из первых ядер операционной системы, реализованных на языке, отличном от ассемблера . Более ранние примеры включают систему Multics (написанную на PL/I ) и программу главного управления (MCP) для Burroughs B5000 (написанную на ALGOL ) в 1961 году. Примерно в 1977 году Ричи и Стивен К. Джонсон внесли дальнейшие изменения в язык, облегчающий переносимость операционной системы Unix. Портативный компилятор C Джонсона послужил основой для нескольких реализаций C на новых платформах. [16]
В 1978 году Брайан Керниган и Деннис Ритчи опубликовали первое издание « Языка программирования C ». [18] Книга , известная как K&R по инициалам авторов, долгие годы служила неформальным описанием языка. Описанную в нем версию C обычно называют « 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
.
Например:
длинная some_function (); /* Это объявление функции, поэтому компилятор может знать имя и тип возвращаемого значения этой функции. */ /* int */ other_function (); /* Еще одно объявление функции. Поскольку это ранняя версия C, здесь неявно используется тип int. Комментарий показывает, где в более поздних версиях потребуется явный спецификатор типа int. */ /* int */ call_function () /* Это определение функции, включая тело кода, заключенное в { фигурные скобки }. Поскольку тип возвращаемого значения не указан, в этой ранней версии C функция неявно возвращает значение 'int'. */ { long test1 ; зарегистрируйте /* int */ test2 ; /* Еще раз обратите внимание, что 'int' здесь не требуется. Спецификатор типа 'int' */ /* в комментарии потребуется в более поздних версиях C. */ /* Ключевое слово 'register' указывает компилятору, что эта переменная должна */ /* в идеале храниться в регистре как в отличие от внутри кадра стека. */ test1 = некоторая_функция (); если ( тест1 > 1 ) тест2 = 0 ; еще test2 = другая_функция (); вернуть тест2 ; }
Спецификаторы int
типа, которые закомментированы, могут быть опущены в K&R C, но они потребуются в более поздних стандартах.
Поскольку объявления функций K&R не включали никакой информации об аргументах функции, проверки типов параметров функции не выполнялись, хотя некоторые компиляторы выдавали предупреждающее сообщение, если локальная функция вызывалась с неправильным количеством аргументов или если несколько вызовов внешней функции использовали разные числа или типы аргументов. Были разработаны отдельные инструменты, такие как утилита Unix lint , которая (помимо прочего) могла проверять согласованность использования функций в нескольких исходных файлах.
За годы, прошедшие после публикации K&R C, в язык было добавлено несколько функций, поддерживаемых компиляторами AT&T (в частности PCC [21] ) и некоторыми другими поставщиками. В их число вошли:
void
функции (т. е. функции без возвращаемого значения)struct
или union
типы (ранее можно было возвращать только один указатель, целое число или число с плавающей запятой)struct
данных#define GREEN 3
)Большое количество расширений и отсутствие соглашения о стандартной библиотеке , а также популярность языка и тот факт, что даже компиляторы Unix не реализовали точно спецификацию K&R, привели к необходимости стандартизации. [ нужна цитата ]
В конце 1970-х и 1980-х годах версии C были реализованы для широкого спектра мейнфреймов , мини-компьютеров и микрокомпьютеров , включая IBM PC , поскольку его популярность начала значительно возрастать.
В 1983 году Американский национальный институт стандартов (ANSI) сформировал комитет X3J11 для установления стандартной спецификации C. X3J11 основал стандарт C на реализации Unix; однако непереносимая часть библиотеки Unix C была передана рабочей группе 1003 IEEE , чтобы стать основой для стандарта POSIX 1988 года . В 1989 году стандарт C был ратифицирован как ANSI X3.159-1989 «Язык программирования C». Эту версию языка часто называют ANSI C , Standard 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 в пределах ее ресурсов. Без таких мер предосторожности программы могут компилироваться только на определенной платформе или с помощью определенного компилятора, например, из-за использования нестандартных библиотек, таких как библиотеки графического интерфейса , или из-за зависимости от атрибутов компилятора или платформы, таких как как точный размер типов данных и порядок байтов .
В тех случаях, когда код должен быть скомпилирован либо компиляторами, соответствующими стандарту, либо компиляторами на основе K&R C, макрос __STDC__
можно использовать для разделения кода на разделы Standard и K&R, чтобы предотвратить использование в компиляторе K&R C функций, доступных только в Standard. С.
После процесса стандартизации ANSI/ISO спецификация языка C оставалась относительно неизменной в течение нескольких лет. В 1995 году была опубликована Нормативная поправка 1 к стандарту C 1990 года (ISO/IEC 9899/AMD1:1995, неофициально известная как C95), в которой были исправлены некоторые детали и добавлена более обширная поддержка международных наборов символов. [22]
Стандарт C был дополнительно пересмотрен в конце 1990-х годов, что привело к публикации ISO/IEC 9899:1999 в 1999 году, который обычно называют « C99 ». С тех пор в него трижды вносились технические исправления. [23]
В C99 появилось несколько новых функций, в том числе встроенные функции , несколько новых типов данных (в том числе long long int
и complex
тип для представления комплексных чисел ), массивы переменной длины и гибкие члены массива , улучшенная поддержка плавающей запятой IEEE 754 , поддержка макросов с переменным числом переменных (макросы переменных arity ) и поддержку однострочных комментариев, начинающихся с //
, как в BCPL или C++. Многие из них уже были реализованы как расширения в нескольких компиляторах C.
C99 по большей части обратно совместим с C90, но в некоторых отношениях более строг; в частности, объявление, в котором отсутствует спецификатор типа, больше не int
подразумевается неявно. Стандартный макрос __STDC_VERSION__
имеет значение, 199901L
указывающее, что доступна поддержка C99. GCC , Solaris Studio и другие компиляторы C сейчас [ когда? ] поддерживают многие или все новые функции C99. Однако компилятор C в Microsoft Visual C++ реализует стандарт C89 и те части C99, которые необходимы для совместимости с C++11 . [24] [ нужно обновить ]
Кроме того, стандарт C99 требует поддержки идентификаторов , использующих Unicode, в виде экранированных символов (например, \u0040
или \U0001f431
) и предлагает поддержку необработанных имен в Unicode.
В 2007 году началась работа над еще одной версией стандарта C, неофициально названной «C1X», до официальной публикации ISO / IEC 9899: 2011 08 декабря 2011 г. Комитет по стандартам C принял рекомендации по ограничению внедрения новых функций, которые не были протестированы существующими реализациями.
Стандарт C11 добавляет множество новых функций в C и библиотеку, включая общие макросы типов, анонимные структуры, улучшенную поддержку Unicode, атомарные операции, многопоточность и функции с проверкой границ. Он также делает некоторые части существующей библиотеки C99 необязательными и улучшает совместимость с C++. Стандартный макрос __STDC_VERSION__
определен как 201112L
индикатор доступности поддержки C11.
C17, опубликованный в июне 2018 года под названием ISO/IEC 9899:2018, является текущим стандартом языка программирования C. Он не вводит никаких новых функций языка, а только технические исправления и разъяснения дефектов C11. Стандартный макрос __STDC_VERSION__
определяется как 201710L
.
C23 — неофициальное название следующей (после C17) основной версии стандарта языка C. Ожидается, что он будет опубликован в 2024 году. [25]
Исторически сложилось так, что встроенное программирование на C требует нестандартных расширений языка C для поддержки экзотических функций, таких как арифметика с фиксированной запятой , несколько отдельных банков памяти и базовые операции ввода-вывода.
В 2008 году Комитет по стандартам C опубликовал технический отчет , расширяющий язык C [26] для решения этих проблем путем предоставления общего стандарта для всех реализаций, которого необходимо придерживаться. Он включает в себя ряд функций, недоступных в обычном C, таких как арифметика с фиксированной запятой , именованные адресные пространства и базовая аппаратная адресация ввода-вывода.
C имеет формальную грамматику , определенную стандартом C. [27] Окончание строк в C обычно не имеет значения; однако границы строк имеют значение на этапе предварительной обработки. Комментарии могут появляться либо между разделителями /*
и */
, либо (начиная с C99) //
до конца строки. Комментарии разделены /*
и */
не вложены, и эти последовательности символов не интерпретируются как разделители комментариев, если они появляются внутри строковых или символьных литералов. [28]
Исходные файлы 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 большей осторожности для получения надежных результатов, чем это необходимо для других языков программирования.||
?:
Керниган и Ритчи говорят во введении к языку программирования C : «C, как и любой другой язык, имеет свои недостатки. Некоторые операторы имеют неправильный приоритет; некоторые части синтаксиса могли бы быть лучше». [29] Стандарт C не пытался исправить многие из этих недостатков из-за влияния таких изменений на уже существующее программное обеспечение.
Базовый исходный набор символов C включает следующие символы:
a
– z
A
–Z
0
–9
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~
Новая строка указывает на конец текстовой строки; он не обязательно должен соответствовать отдельному символу, хотя для удобства C рассматривает его как один.
В строковых литералах можно использовать дополнительные многобайтовые закодированные символы, но они не полностью переносимы . Последний стандарт C ( C11 ) позволяет переносимо внедрять многонациональные символы Юникода в исходный текст C с помощью \uXXXX
или \UXXXXXXXX
кодирования (где символ X
обозначает шестнадцатеричный символ), хотя эта функция еще не получила широкого распространения.
Базовый набор символов выполнения C содержит те же символы, а также представления для alert , backspace и возврата каретки . Поддержка расширенных наборов символов во время выполнения увеличивалась с каждой версией стандарта C.
C89 имеет 32 зарезервированных слова, также известных как ключевые слова, которые нельзя использовать ни для каких целей, кроме тех, для которых они предопределены:
C99 зарезервировал еще пять слов:
C11 зарезервировал еще семь слов: [30]
_Alignas
_Alignof
_Atomic
_Generic
_Noreturn
_Static_assert
_Thread_local
C23 зарезервирует еще 14 слов:
alignas
alignof
bool
constexpr
false
nullptr
static_assert
thread_local
true
typeof
typeof_unqual
_Decimal128
_Decimal32
_Decimal64
Большинство недавно зарезервированных слов начинаются с подчеркивания, за которым следует заглавная буква, поскольку идентификаторы этой формы ранее были зарезервированы стандартом C для использования только в реализациях. Поскольку существующий исходный код программы не должен был использовать эти идентификаторы, это не повлияет на то, когда реализации C начнут поддерживать эти расширения языка программирования. Некоторые стандартные заголовки определяют более удобные синонимы для подчеркнутых идентификаторов. Некоторые из этих слов были добавлены как ключевые слова с их обычным написанием в C23, а соответствующие макросы были удалены.
До C89 entry
было зарезервировано как ключевое слово. Во втором издании своей книги « Язык программирования C» , в которой описывается то, что стало известно как C89, Керниган и Ритчи написали: «… [ключевое слово] entry
, ранее зарезервированное, но никогда не использовавшееся, больше не зарезервировано». и «Ключевое слово «мертворожденное entry
» удалено». [31]
C поддерживает богатый набор операторов — символов, используемых в выражении для указания манипуляций, которые необходимо выполнить при вычислении этого выражения. В C есть операторы для:
=
+=
, , , , , , , , , , -=
*=
/=
%=
&=
|=
^=
<<=
>>=
~
, &
, |
,^
<<
,>>
!
, &&
,||
( )
++
,--
.
,->
sizeof
typeof
, typeof_unqual
начиная с C23<
, <=
, >
,>=
&
, *
,[ ]
( )
(typename)
C использует оператор =
(используемый в математике для выражения равенства) для обозначения присваивания, следуя прецеденту Fortran и PL/I , но в отличие от ALGOL и его производных. C использует оператор ==
для проверки равенства. Сходство между этими двумя операторами (присваивание и равенство) может привести к случайному использованию одного вместо другого, и во многих случаях ошибка не приводит к появлению сообщения об ошибке (хотя некоторые компиляторы выдают предупреждения). Например, условное выражение if (a == b + 1)
может быть ошибочно записано как if (a = b + 1)
, которое будет оценено как true, если a
после присваивания не будет равно нулю. [32]
Приоритет операторов C не всегда интуитивно понятен. Например, оператор связывается более жестко, чем операторы (побитовое И) и (побитовое ИЛИ) ==
(выполняется раньше ) в таких выражениях, как , которые должны быть написаны так, как будто это и есть намерение программиста. [33]&
|
x & 1 == 0
(x & 1) == 0
Пример « hello, world », появившийся в первом издании K&R , стал образцом вводной программы в большинстве учебников по программированию. Программа выводит «привет, мир» на стандартный вывод , который обычно представляет собой терминал или экран.
Первоначальная версия была: [34]
main () { printf ( «привет, мир \n » ); }
Соответствующая стандарту программа «Привет, мир» — это: [a]
#include <stdio.h> int main ( void ) { printf ( «привет, мир \n » ); }
Первая строка программы содержит директиву предварительной обработки , обозначенную #include
. Это приводит к тому, что компилятор заменяет эту строку всем текстом стандартного stdio.h
заголовка, который содержит объявления для стандартных функций ввода и вывода, таких как printf
и scanf
. Угловые скобки вокруг stdio.h
указывают, что его stdio.h
можно найти с помощью стратегии поиска, которая предпочитает заголовки, предоставленные компилятором, другим заголовкам с тем же именем, в отличие от двойных кавычек, которые обычно включают локальные или специфичные для проекта файлы заголовков.
main
Следующая строка указывает, что определяется функция с указанным именем . Эта main
функция служит специальной цели в программах на языке C; среда выполнения вызывает main
функцию, чтобы начать выполнение программы. Спецификатор типа int
указывает, что значение, возвращаемое вызывающей стороне (в данном случае среде выполнения) в результате вычисления функции main
, является целым числом. Ключевое слово void
в качестве списка параметров указывает на то, что эта функция не принимает аргументов. [б]
Открывающая фигурная скобка указывает на начало определения функции main
.
Следующая строка вызывает (перенаправляет выполнение) функцию с именем printf
, которая в данном случае предоставляется из системной библиотеки . В этом вызове printf
функции передается (предоставляется) единственный аргумент — адрес первого символа строкового литерала "hello, world\n"
. Строковый литерал представляет собой безымянный массив с элементами типа char
, автоматически устанавливаемый компилятором с последним символом NULL (значение ASCII 0), обозначающим конец массива (чтобы printf
узнать длину строки). Символ NULL может также может быть записано как escape-последовательность , записанная как \0
. Это escape \n
- последовательность , которую C преобразует в символ новой строки , который на выходе означает конец текущей строки. Возвращаемое значение функции printf
имеет тип int
, но оно автоматически отбрасывается, поскольку не используется. (Более осторожная программа могла бы проверить возвращаемое значение, чтобы определить, завершилась ли функция printf
успешно.) Точка с запятой ;
завершает оператор.
Закрывающая фигурная скобка указывает на конец кода функции main
. Согласно спецификации C99 и более поздним версиям, main
функция, в отличие от любой другой функции, неявно возвращает значение 0
при достижении }
которой завершает функцию. (Раньше требовалось явное return 0;
выражение.) Это интерпретируется системой времени выполнения как код выхода, указывающий на успешное выполнение. [35]
Система типов в C статична и слабо типизирована , что делает ее похожей на систему типов потомков АЛГОЛА , таких как Паскаль . [36] Существуют встроенные типы для целых чисел различных размеров, как знаковых, так и беззнаковых, чисел с плавающей запятой и перечислимых типов ( enum
). Целочисленный тип char
часто используется для однобайтовых символов. В C99 добавлен логический тип данных . Существуют также производные типы, включая массивы , указатели , записи ( struct
) и объединения ( union
).
C часто используется в системном программировании низкого уровня, где может потребоваться выход из системы типов. Компилятор пытается обеспечить правильность типов большинства выражений, но программист может переопределить проверки различными способами: либо с помощью приведения типа для явного преобразования значения из одного типа в другой, либо с помощью указателей или объединений для повторной интерпретации базовых битов. объекта данных каким-либо другим способом.
Некоторые считают синтаксис объявления C неинтуитивным, особенно для указателей на функции . (Идея Ричи заключалась в том, чтобы объявлять идентификаторы в контекстах, напоминающих их использование: « объявление отражает использование ».) [37]
Обычные арифметические преобразования C позволяют генерировать эффективный код, но иногда могут давать неожиданные результаты. Например, сравнение целых чисел со знаком и без знака одинаковой ширины требует преобразования значения со знаком в беззнаковое. Это может привести к неожиданным результатам, если значение со знаком отрицательно.
C поддерживает использование указателей — типа ссылки , которая записывает адрес или расположение объекта или функции в памяти. Указатели можно разыменовать для доступа к данным, хранящимся по указанному адресу, или для вызова указанной функции. Указателями можно манипулировать с помощью присваивания или арифметики указателей . Представление значения указателя во время выполнения обычно представляет собой необработанный адрес памяти (возможно, дополненный полем смещения внутри слова), но поскольку тип указателя включает тип объекта, на который он указывает, выражения, включающие указатели, могут быть проверены по типу. во время компиляции. Арифметика указателей автоматически масштабируется в соответствии с размером типа данных, на который указывает.
Указатели используются в C для многих целей. Текстовые строки обычно обрабатываются с помощью указателей на массивы символов. Динамическое выделение памяти осуществляется с помощью указателей; результат a malloc
обычно приводится к типу данных, которые будут сохранены. Многие типы данных, такие как деревья , обычно реализуются как динамически выделяемые struct
объекты, связанные между собой с помощью указателей. Указатели на другие указатели часто используются в многомерных массивах и массивах struct
объектов. Указатели на функции ( указатели функций ) полезны для передачи функций в качестве аргументов функциям более высокого порядка (таким как qsort или bsearch ), в таблицах диспетчеризации или в качестве обратных вызовов обработчикам событий . [35]
Значение нулевого указателя явно указывает на отсутствие допустимого местоположения. Разыменование значения нулевого указателя не определено, что часто приводит к ошибке сегментации . Значения нулевого указателя полезны для обозначения особых случаев, таких как отсутствие указателя «следующий» в конечном узле связанного списка или в качестве индикации ошибки в функциях, возвращающих указатели. В соответствующих контекстах исходного кода, например, при присвоении переменной-указателю, константа нулевого указателя может быть записана как 0
, с явным приведением к типу указателя или без него, как NULL
макрос, определенный несколькими стандартными заголовками, или, начиная с C23, с константой nullptr
. В условных контекстах значения нулевого указателя оцениваются как ложные, а все остальные значения указателей оцениваются как истинные.
Пустые указатели ( void *
) указывают на объекты неопределенного типа и поэтому могут использоваться как «универсальные» указатели данных. Поскольку размер и тип объекта, на который указывает указатель, неизвестны, указатели void не могут быть разыменованы, а арифметика указателей на них не допускается, хотя их можно легко (и во многих контекстах неявно так и делают) преобразовать в указатель любого другого объекта и из него. тип. [35]
Неосторожное использование указателей потенциально опасно. Поскольку они обычно не отмечены флажком, переменную-указатель можно указать на любое произвольное место, что может вызвать нежелательные эффекты. Хотя правильно используемые указатели указывают на безопасные места, их можно заставить указывать на небезопасные места, используя недопустимую арифметику указателей ; объекты, на которые они указывают, могут продолжать использоваться после освобождения ( висячие указатели ); их можно использовать без инициализации ( дикие указатели ); или им может быть напрямую присвоено небезопасное значение с помощью приведения, объединения или другого поврежденного указателя. В общем, C позволяет манипулировать типами указателей и преобразовывать их между собой, хотя компиляторы обычно предоставляют опции для различных уровней проверки. Некоторые другие языки программирования решают эти проблемы, используя более ограничительные ссылочные типы.
Типы массивов в C традиционно имеют фиксированный статический размер, указанный во время компиляции. Более поздний стандарт C99 также допускает использование массивов переменной длины. Однако также возможно выделить блок памяти (произвольного размера) во время выполнения, используя malloc
функцию стандартной библиотеки, и рассматривать его как массив.
Поскольку доступ к массивам всегда осуществляется (фактически) через указатели, доступ к массиву обычно не проверяется по размеру базового массива, хотя некоторые компиляторы могут предоставлять проверку границ в качестве опции. [38] [39] Поэтому возможны нарушения границ массива, которые могут привести к различным последствиям, включая незаконный доступ к памяти, повреждение данных, переполнение буфера и исключения во время выполнения.
В C нет специального средства для объявления многомерных массивов , а скорее полагается на рекурсию внутри системы типов для объявления массивов массивов, что фактически выполняет то же самое. Значения индекса результирующего «многомерного массива» можно рассматривать как увеличивающиеся в порядке возрастания по строкам . Многомерные массивы обычно используются в числовых алгоритмах (в основном из прикладной линейной алгебры ) для хранения матриц. Структура массива C хорошо подходит для этой конкретной задачи. Однако в ранних версиях C границы массива должны быть известны фиксированными значениями или явно передаваться в любую подпрограмму, которая их требует, а к массивам массивов динамического размера нельзя получить доступ с помощью двойной индексации. (Обходным решением этой проблемы было выделение массива с дополнительным «вектором-строкой» указателей на столбцы.) C99 представил «массивы переменной длины», которые решают эту проблему.
В следующем примере с использованием современного C (C99 или более поздней версии) показано размещение двумерного массива в куче и использование индексации многомерного массива для доступа (которая может использовать проверку границ во многих компиляторах C):
int func ( int N , int M ) { float ( * p ) [ N ] [ M ] = malloc ( sizeof * p ); если ( p == 0 ) вернуть -1 ; for ( int i знак равно 0 ; я < N ; я ++ ) for ( int j знак равно 0 ; j < M ; j ++ ) ( * p )[ i ][ j ] = i + j ; print_array ( N , M , p ); бесплатно ( п ); вернуть 1 ; }
А вот аналогичная реализация с использованием функции Auto VLA C99 : [c]
int func ( int N , int M ) { // Внимание: необходимо выполнить проверку, чтобы убедиться, что N*M*sizeof(float) НЕ превышает ограничения для автоматических VLA и находится в пределах доступного размера стека. плавать p [ N ][ M ]; // auto VLA хранится в стеке и его размер изменяется при вызове функции for ( int i = 0 ; i < N ; i ++ ) for ( int j = 0 ; j < M ; j ++ ) p [ i ][ j ] знак равно я + j ; print_array ( N , M , p ); // нет необходимости в free(p), так как он исчезнет при выходе из функции вместе с остальной частью кадра стека return 1 ; }
Обозначение нижнего индекса x[i]
(где x
обозначает указатель) является синтаксическим сахаром для *(x+i)
. [40] Используя знание компилятором типа указателя, адрес, на который x + i
указывает, не является базовым адресом (на который указывает x
), увеличенным на i
байты, а скорее определяется как базовый адрес, увеличенный на i
умноженный на размер элемент, который x
указывает на. Таким образом, x[i]
обозначает i+1
й элемент массива.
Более того, в большинстве контекстов выражений (заметным исключением является операнд sizeof
) выражение типа массива автоматически преобразуется в указатель на первый элемент массива. Это означает, что массив никогда не копируется целиком, если он назван в качестве аргумента функции, а передается только адрес его первого элемента. Таким образом, хотя вызовы функций в C используют семантику передачи по значению , массивы фактически передаются по ссылке .
Общий размер массива x
можно определить, применив его sizeof
к выражению типа массива. Размер элемента можно определить, применив оператор sizeof
к любому разыменованному элементу массива A
, как в n = sizeof A[0]
. Таким образом, количество элементов в объявленном массиве A
можно определить как sizeof A / sizeof A[0]
. Обратите внимание: если доступен только указатель на первый элемент, как это часто бывает в коде C из-за описанного выше автоматического преобразования, информация о полном типе массива и его длине теряется.
Одной из наиболее важных функций языка программирования является предоставление средств управления памятью и объектами, хранящимися в памяти. C предоставляет три основных способа распределения памяти для объектов: [35]
malloc
из области памяти, называемой кучей ; эти блоки сохраняются до тех пор, пока впоследствии не будут освобождены для повторного использования путем вызова библиотечной функции realloc
илиfree
Эти три подхода подходят в разных ситуациях и имеют различные компромиссы. Например, статическое выделение памяти имеет небольшие накладные расходы, автоматическое выделение может потребовать немного больше накладных расходов, а динамическое выделение памяти потенциально может иметь большие накладные расходы как на выделение, так и на освобождение. Постоянный характер статических объектов полезен для сохранения информации о состоянии во время вызовов функций, автоматическое выделение легко использовать, но пространство стека обычно гораздо более ограничено и преходяще, чем статическая память или пространство кучи, а динамическое распределение памяти позволяет удобно выделять объекты, размер известен только во время выполнения. Большинство программ на языке C широко используют все три.
Там, где это возможно, автоматическое или статическое выделение обычно является самым простым, поскольку хранилищем управляет компилятор, что освобождает программиста от потенциально подверженной ошибкам рутинной работы по выделению и освобождению памяти вручную. Однако размер многих структур данных может изменяться во время выполнения, а поскольку статические выделения (и автоматические выделения до C99) должны иметь фиксированный размер во время компиляции, существует множество ситуаций, в которых динамическое выделение необходимо. [35] До появления стандарта C99 частым примером были массивы переменного размера. (Пример динамически выделяемых массивов см. в статье malloc
.) В отличие от автоматического выделения, которое может привести к сбою во время выполнения с неконтролируемыми последствиями, функции динамического выделения возвращают индикацию (в виде значения нулевого указателя), когда требуемое хранилище не может быть выделены. (Слишком большое статическое выделение обычно обнаруживается компоновщиком или загрузчиком еще до того, как программа сможет начать выполнение.)
Если не указано иное, статические объекты содержат нулевые или нулевые значения указателя при запуске программы. Автоматически и динамически выделяемые объекты инициализируются только в том случае, если начальное значение указано явно; в противном случае они изначально имеют неопределенные значения (обычно это любой битовый шаблон, присутствующий в хранилище , который может даже не представлять допустимое значение для этого типа). Если программа попытается получить доступ к неинициализированному значению, результаты будут неопределенными. Многие современные компиляторы пытаются обнаружить и предупредить об этой проблеме, но могут возникать как ложноположительные, так и ложноотрицательные результаты .
Выделение кучи памяти должно быть синхронизировано с ее фактическим использованием в любой программе, чтобы ее можно было использовать повторно. Например, если единственный указатель на выделение памяти в куче выходит за пределы области действия или его значение перезаписывается до того, как оно будет явно освобождено, то эта память не может быть восстановлена для последующего повторного использования и по существу теряется для программы — явление, известное как память . утечка . И наоборот, память может быть освобождена, но к ней будут обращаться впоследствии, что приведет к непредсказуемым результатам. Обычно симптомы сбоя появляются в части программы, не связанной с кодом, вызывающим ошибку, что затрудняет диагностику сбоя. Такие проблемы решаются в языках с автоматической сборкой мусора .
Язык программирования C использует библиотеки в качестве основного метода расширения. В языке C библиотека — это набор функций, содержащихся в одном «архивном» файле. Каждая библиотека обычно имеет заголовочный файл , который содержит прототипы функций, содержащихся в библиотеке, которые могут использоваться программой, а также объявления специальных типов данных и макросимволов, используемых с этими функциями. Чтобы программа могла использовать библиотеку, она должна включать заголовочный файл библиотеки, а библиотека должна быть связана с программой, что во многих случаях требует флагов компилятора (например -lm
, сокращение от «связать математическую библиотеку»). [35]
Наиболее распространенной библиотекой C является стандартная библиотека C , которая определяется стандартами C ISO и ANSI и поставляется с каждой реализацией C (реализации, предназначенные для ограниченных сред, таких как встроенные системы , могут предоставлять только подмножество стандартной библиотеки). Эта библиотека поддерживает потоковый ввод и вывод, распределение памяти, математические операции, строки символов и значения времени. Несколько отдельных стандартных заголовков (например, stdio.h
) определяют интерфейсы для этих и других средств стандартной библиотеки.
Другой распространенный набор функций библиотеки C — это те, которые используются приложениями, специально предназначенными для Unix и Unix-подобных систем, особенно функции, которые обеспечивают интерфейс с ядром . Эти функции подробно описаны в различных стандартах, таких как POSIX и Единая спецификация UNIX .
Поскольку многие программы написаны на языке C, существует множество других доступных библиотек. Библиотеки часто пишутся на C, поскольку компиляторы C генерируют эффективный объектный код ; затем программисты создают интерфейсы для библиотеки, чтобы подпрограммы можно было использовать из языков более высокого уровня, таких как Java , Perl и Python . [35]
Ввод и вывод файлов (I/O) не являются частью самого языка C, а обрабатываются библиотеками (такими как стандартная библиотека C) и связанными с ними заголовочными файлами (например, stdio.h
). Обработка файлов обычно реализуется посредством ввода-вывода высокого уровня, который работает через потоки . С этой точки зрения поток — это поток данных, независимый от устройств, тогда как файл — это конкретное устройство. Ввод-вывод высокого уровня осуществляется посредством ассоциации потока с файлом. В стандартной библиотеке C буфер (область памяти или очередь) временно используется для хранения данных перед их отправкой в конечный пункт назначения. Это сокращает время ожидания более медленных устройств, например жесткого или твердотельного накопителя . Функции ввода-вывода низкого уровня не являются частью стандартной библиотеки C [ необходимы пояснения ] , но, как правило, являются частью программирования на «голом железе» (программирование, которое не зависит от какой-либо операционной системы , например, большинство встроенных программ ). За некоторыми исключениями, реализации включают низкоуровневый ввод-вывод.
Был разработан ряд инструментов, помогающих программистам на C находить и исправлять операторы с неопределенным поведением или, возможно, ошибочными выражениями, с большей строгостью, чем та, которую обеспечивает компилятор. Инструмент lint был первым подобным инструментом, за которым последовало множество других.
Автоматизированная проверка и аудит исходного кода полезны на любом языке, и для C существует множество таких инструментов, например Lint . Обычной практикой является использование Lint для обнаружения сомнительного кода при первом написании программы. Как только программа проходит Lint, она компилируется с помощью компилятора C. Кроме того, многие компиляторы могут дополнительно предупреждать о синтаксически допустимых конструкциях, которые на самом деле могут оказаться ошибками. MISRA C — это собственный набор правил, позволяющий избегать использования такого сомнительного кода, разработанный для встроенных систем. [41]
Существуют также компиляторы, библиотеки и механизмы уровня операционной системы для выполнения действий, не являющихся стандартной частью C, таких как проверка границ массивов, обнаружение переполнения буфера , сериализация , динамическое отслеживание памяти и автоматическая сборка мусора .
Такие инструменты, как Purify или Valgrind , а также связывание с библиотеками, содержащими специальные версии функций распределения памяти , могут помочь обнаружить ошибки во время выполнения, связанные с использованием памяти. [42] [43]
C широко используется для системного программирования при реализации операционных систем и встроенных системных приложений. [44] Это происходит по нескольким причинам:
malloc
и free
; более сложный механизм с аренами ; или версию для ядра ОС , которая может подходить для DMA , использоваться в обработчиках прерываний или интегрироваться с системой виртуальной памяти .Исторически C иногда использовался для веб-разработки с использованием интерфейса Common Gateway Interface (CGI) в качестве «шлюза» для передачи информации между веб-приложением, сервером и браузером. [45] Си, возможно, был выбран вместо интерпретируемых языков из- за его скорости, стабильности и почти универсальной доступности. [46] Веб-разработка на C уже не является общепринятой практикой, [47] существует множество других инструментов веб-разработки .
Следствием широкой доступности и эффективности C является то, что компиляторы , библиотеки и интерпретаторы других языков программирования часто реализуются на C. [ 48] Например, эталонные реализации Python , [49] Perl , [50] Ruby , [51] и PHP [52] написаны на C.
C позволяет программистам создавать эффективные реализации алгоритмов и структур данных, поскольку уровень абстракции от аппаратного обеспечения тонкий, а его накладные расходы невелики, что является важным критерием для программ с интенсивными вычислениями. Например, Библиотека арифметики множественной точности GNU , Научная библиотека GNU , Mathematica и MATLAB полностью или частично написаны на C. Многие языки поддерживают вызов библиотечных функций на C, например, основанная на Python платформа NumPy использует C для высокой точности. -Аспекты производительности и взаимодействия с оборудованием.
C иногда используется в качестве промежуточного языка в реализациях других языков. Этот подход можно использовать для портативности или удобства; используя C в качестве промежуточного языка, дополнительные генераторы кода, специфичные для машины, не нужны. В C есть некоторые функции, такие как директивы препроцессора с номерами строк и необязательные лишние запятые в конце списков инициализаторов, которые поддерживают компиляцию сгенерированного кода. Однако некоторые недостатки C побудили разработку других языков на основе C , специально предназначенных для использования в качестве промежуточных языков, таких как C-- . Кроме того, современные основные компиляторы GCC и LLVM имеют промежуточное представление, отличное от C, и эти компиляторы поддерживают интерфейсы для многих языков, включая C.
Язык C также широко используется для реализации приложений для конечных пользователей . [53] Однако такие приложения также можно писать на более новых языках более высокого уровня.
мощь языка ассемблера и удобство... языка ассемблера
- Деннис Ричи [54]
Хотя C был популярен, влиятельн и чрезвычайно успешен, у него есть недостатки, в том числе:
malloc
и free
подвержена ошибкам. К ошибкам относятся: Утечки памяти, когда память выделяется, но не освобождается; и доступ к ранее освобожденной памяти.scanf
или strncat
, могут привести к переполнению буфера .#pragma
, а некоторые — с помощью дополнительных ключевых слов, например, использовать соглашение о вызовах. Но директива и опции не поддерживаются последовательно. [57]__cdecl
setjmp
иlongjmp
использовались [59] для реализации механизма try-catch с помощью макросов.Для некоторых целей были приняты ограниченные стили C, например MISRA C или CERT C , в попытке уменьшить вероятность ошибок. Базы данных, такие как CWE, пытаются подсчитать количество уязвимостей C и т. д., а также дать рекомендации по их устранению.
Существуют инструменты, которые могут смягчить некоторые недостатки. Современные компиляторы C включают проверки, которые могут генерировать предупреждения, помогающие выявить множество потенциальных ошибок.
Некоторые из этих недостатков побудили создание других языков. [ нужна цитата ]
C прямо и косвенно повлиял на многие более поздние языки, такие как C++ и Java . [61] Наиболее распространенное влияние было синтаксическим; все упомянутые языки сочетают в себе синтаксис операторов и (более или менее узнаваемых) выражений C с системами типов, моделями данных или крупномасштабными программными структурами, которые отличаются от C, иногда радикально.
Существует несколько интерпретаторов C или близких к C, включая Ch и CINT , которые также можно использовать для написания сценариев.
Когда объектно-ориентированные языки программирования стали популярными, C++ и Objective-C были двумя разными расширениями C, предоставлявшими объектно-ориентированные возможности. Оба языка изначально были реализованы как компиляторы исходного кода ; исходный код был переведен на C, а затем скомпилирован компилятором C. [62]
Язык программирования C++ (первоначально называвшийся «C с классами ») был разработан Бьярном Страуструпом как подход к обеспечению объектно-ориентированной функциональности с синтаксисом, подобным C. [63] 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
поддержки обеих форм, что является особым подходом, не предоставляемым какой-либо другой функции.print_array
(не показан) немного отличается, [ почему? ] слишком.{{cite web}}
: CS1 maint: numeric names: authors list (link)1980-е: впервые представлен Verilog; Verilog, вдохновленный языком программирования C