C (язык программирования) - C (programming language)

C
Текст голубыми заглавными буквами с засечками на белом фоне и очень большой голубой буквой без засечек C.
Язык программирования C (часто называемый K&R ), основополагающая книга по C
Парадигма Мультипарадигма : императивная ( процедурная ), структурированная
Разработано Деннис Ричи
Разработчик Деннис Ричи и Bell Labs (создатели); ANSI X3J11 ( ANSI C ); ISO / IEC JTC1 / SC22 / WG14 (ISO C)
Впервые появился 1972 ; 49 лет назад ( 1972 )
Стабильный выпуск
C17 / июнь 2018 г . ; 3 года назад ( 2018-06 )
Предварительный выпуск
C2x ( N2596 ) / 11 декабря 2020 г . ; 10 месяцев назад ( 2020-12-11 )
Печатная дисциплина Статический , слабый , явный , номинальный
Операционные системы Кроссплатформенность
Расширения имени файла .c, .h
Веб-сайт www .iso .org / standard / 74528 .html
www .open-std .org / jtc1 / sc22 / wg14 /
Основные реализации
K&R C , GCC , Clang , Intel C , C ++ Builder , Microsoft Visual C ++ , Watcom C
Диалекты
Циклон , унифицированный параллельный C , Split-C , Cilk , C *
Под влиянием
B ( BCPL , CPL ), АЛГОЛ 68 , Сборка , PL / I , FORTRAN
Под влиянием
Многочисленные : AMPL , AWK , csh , C ++ , C-- , C # , Objective-C , D , Go , Java , JavaScript , Julia , Limbo , LPC , Perl , PHP , Pike , Processing , Python , Rust , Seed7 , Vala , Verilog (HDL), Ним , Зиг

С ( / ы я / , как и в письме с ) представляет собой универсальный , процедурный компьютерный язык программирования , поддерживающее структурированное программирование , объем лексического переменный , и рекурсию , с системой статического типа . По замыслу C предоставляет конструкции, которые эффективно отображаются на типичные машинные инструкции . Он нашел прочное применение в приложениях, ранее написанных на языке ассемблера . К таким приложениям относятся операционные системы и различное прикладное программное обеспечение для компьютерных архитектур, от суперкомпьютеров до ПЛК и встроенных систем .

Преемник языка программирования B , C первоначально была разработана в Bell Labs по Деннис Ритчи в период с 1972 по 1973 год построить утилиты , работающие на Unix . Он был применен для повторной реализации ядра операционной системы Unix. В течение 1980-х C постепенно завоевал популярность. Он стал одним из наиболее широко используемых языков программирования , с компиляторами C от различных поставщиков, доступными для большинства существующих компьютерных архитектур и операционных систем. C стандартизирован ANSI с 1989 года ( ANSI C ) и Международной организацией по стандартизации (ISO).

C - императивный процедурный язык. Он был разработан для компиляции, чтобы обеспечить низкоуровневый доступ к памяти и языковым конструкциям, которые эффективно отображаются на машинные инструкции , и все это с минимальной поддержкой времени выполнения . Несмотря на свои низкоуровневые возможности, язык был разработан для поощрения кроссплатформенного программирования. Соответствующая стандартам программа на языке C, написанная с учетом переносимости, может быть скомпилирована для широкого спектра компьютерных платформ и операционных систем с небольшими изменениями в ее исходном коде.

С 2000 года C неизменно входит в число двух лучших языков в индексе TIOBE , показывающем популярность языков программирования.

Обзор

Деннис Ричи (справа), изобретатель языка программирования C, с Кеном Томпсоном

Как и большинство процедурных языков в традиции Алгола , C имеет средства для структурированного программирования и допускает область видимости лексических переменных и рекурсию. Его система статического типа предотвращает непреднамеренные операции. В C весь исполняемый код содержится в подпрограммах (также называемых «функциями», хотя и не строго в смысле функционального программирования ). Параметры функции всегда передаются по значению (кроме массивов ). Передача по ссылке моделируется в C путем явной передачи значений указателя . Исходный текст программы на C имеет свободный формат , с точкой с запятой в качестве признака конца оператора и фигурными скобками для группировки блоков операторов .

Язык C также обладает следующими характеристиками:

  • Язык имеет небольшое, фиксированное количество ключевых слов, в то числе полного набора потоков управления примитивами: if/else, for, do/while, while, и switch. Пользовательские имена не отличаются от ключевых слов никакими символами .
  • Он имеет большое количество арифметических, побитовые, и логические операторы: +, +=, ++, &, ||и т.д.
  • В одном операторе может быть выполнено более одного присваивания .
  • Функции:
    • Если в этом нет необходимости, возвращаемые функции можно игнорировать.
    • Указатели на функции и данные допускают произвольный полиморфизм во время выполнения .
    • Функции не могут быть определены в лексической области других функций.
  • Типизация данных статична , но не соблюдается ; все данные имеют тип, но возможны неявные преобразования .
  • Декларации не синтаксис контекст использования подражает. C не имеет ключевого слова "define"; вместо этого заявление, начинающееся с имени типа, считается объявлением. Ключевого слова "функция" нет; вместо этого функция обозначается наличием списка аргументов в скобках.
  • Возможны определяемые пользователем ( typedef ) и составные типы.
    • Гетерогенные агрегированные типы данных ( struct) позволяют получить доступ к связанным элементам данных и назначить их как единое целое.
    • Союз - это структура с перекрывающимися членами; действителен только последний сохраненный член.
    • Индексация массивов - это вторичная нотация, определенная в терминах арифметики указателей. В отличие от структур, массивы не являются объектами первого класса: их нельзя назначать или сравнивать с помощью отдельных встроенных операторов. Ключевое слово "массив" не используется или не определяется; вместо этого квадратные скобки обозначают массивы, например, синтаксически month[11].
    • Перечислимые типы возможны с enumключевым словом. Они свободно конвертируются с целыми числами.
    • Строки не являются отдельным типом данных, но обычно реализуются как массивы символов с завершающим нулем .
  • Низкоуровневый доступ к памяти компьютера возможен путем преобразования машинных адресов в типизированные указатели .
  • Процедуры (подпрограммы, не возвращающие значения) - это особый случай функции с нетипизированным возвращаемым типом void.
  • Препроцессор выполняет макрос определения, исходный код включение файла и условную компиляцию .
  • Существует базовая форма модульности : файлы могут быть скомпилированы отдельно и связаны друг с другом, с контролем над которым функцией и данные объекты являются видимыми для других файлов с помощью staticи externатрибутами.
  • Сложные функции, такие как ввод-вывод , манипуляции со строками и математические функции, последовательно делегируются библиотечным подпрограммам .

Хотя C не включает некоторые функции, имеющиеся в других языках (например, ориентацию объектов и сборку мусора ), они могут быть реализованы или эмулированы, часто с использованием внешних библиотек (например, объектной системы GLib или сборщика мусора Boehm ).

Отношения к другим языкам

Многие более поздние языки прямо или косвенно заимствованы из C, включая C ++ , C # , оболочку C Unix , D , Go , Java , JavaScript (включая транспилеры ), Julia , Limbo , LPC , Objective-C , Perl , PHP , Python , Ruby , Rust , Swift , Verilog и SystemVerilog (языки описания оборудования). Эти языки позаимствовали многие из своих управляющих структур и другие базовые функции из C.Большинство из них (Python является драматическим исключением) также выражают синтаксис, очень похожий на C, и они, как правило, объединяют узнаваемые выражения и синтаксис операторов C с базовым типом. системы, модели данных и семантика, которые могут радикально отличаться.

История

Ранние разработки

Хронология развития языка
Год C Стандартный
1972 г. Рождение
1978 г. K&R C
1989/1990 ANSI C и ISO C
1999 г. C99
2011 г. C11
2017 г. C17
TBD C2x

Происхождение C тесно связано с развитием операционной системы Unix , первоначально реализованной на языке ассемблера на PDP-7 Деннисом Ритчи и Кеном Томпсоном с использованием нескольких идей коллег. В конце концов, они решили перенести операционную систему на PDP-11 . Исходная версия Unix для PDP-11 также была разработана на языке ассемблера.

Томпсон хотел использовать язык программирования для создания утилит для новой платформы. Сначала он пытался создать компилятор Фортрана , но вскоре отказался от этой идеи. Вместо этого он создал урезанную версию недавно разработанного языка системного программирования BCPL . Официальное описание BCPL не было доступно в то время, и Томпсон модифицировал синтаксис менее многословный, производя подобный , но несколько более простой B . Однако в конечном итоге на B было написано несколько утилит, потому что он был слишком медленным, и B не мог использовать преимущества PDP-11, такие как байтовая адресация.

В 1972 году Ричи начал улучшать B, особенно добавляя типизацию данных для переменных, что привело к созданию нового языка C. Компилятор C и некоторые утилиты, созданные с его помощью, были включены в версию 2 Unix .

В версии 4 Unix , выпущенной в ноябре 1973 года, ядро Unix было широко повторно реализовано на C. К этому времени язык C приобрел некоторые мощные функции, такие как типы. struct

Препроцессор был представлен примерно в 1973 году по настоянию Алана Снайдера, а также в знак признания полезности механизмов включения файлов, доступных в BCPL и PL / I. Его исходная версия содержала только включенные файлы и простую замену строк: #includeи #defineмакросов без параметров. Вскоре после этого он был расширен, в основном Майком Леском, а затем Джоном Рейзером, для включения макросов с аргументами и условной компиляции.

Unix была одним из первых ядер операционной системы, реализованных на языке, отличном от ассемблера . Более ранние экземпляры включают систему Multics (которая была написана на PL / I ) и Master Control Program (MCP) для Burroughs B5000 (которая была написана на ALGOL ) в 1961 году. Примерно в 1977 году Ричи и Стивен С. Джонсон внесли дальнейшие изменения в язык, облегчающий переносимость операционной системы Unix. Портативный компилятор Си Джонсона послужил основой для нескольких реализаций Си на новых платформах.

K&R C

Обложка книги Брайана Кернигана и Денниса Ричи The C Programming Language , первое издание

В 1978 году Брайан Керниган и Деннис Ритчи опубликовал первое издание Язык программирования C . Эта книга, известная программистам на C как K&R , в течение многих лет служила неформальной спецификацией языка. Версия C, которую он описывает, обычно называется « K&R C ». Поскольку он был выпущен в 1978 году, он также упоминается как C78 . Второе издание книги охватывает более поздний стандарт ANSI C , описанный ниже.

K&R представила несколько языковых функций:

  • Стандартная библиотека ввода-вывода
  • long int тип данных
  • unsigned int тип данных
  • Операторы составного присваивания формы (например, ) были изменены на форму (то есть ), чтобы устранить семантическую неоднозначность, созданную такими конструкциями, как , которые интерпретировались как (уменьшение на 10) вместо предполагаемого (пусть будет - 10).=op=-op=-=i=-10i =- 10ii = -10i

Даже после публикации стандарта ANSI 1989 года, в течение многих лет K&R C все еще считался " наименьшим общим знаменателем ", которым программисты на C ограничивались, когда была желательна максимальная переносимость, поскольку многие старые компиляторы все еще использовались, и потому что тщательно написанные K&R Код C также может быть законным стандартом C.

В ранних версиях C только функции, возвращающие типы, отличные от того, intдолжны быть объявлены, если они используются до определения функции; Предполагалось, что функции, используемые без предварительного объявления, возвращают тип int.

Например:

long some_function();
/* int */ other_function();

/* int */ calling_function()
{
    long test1;
    register /* int */ test2;

    test1 = some_function();
    if (test1 > 0)
          test2 = 0;
    else
          test2 = other_function();
    return test2;
}

В intспецификаторах типа , которые закомментированы могут быть опущены в K & R C, но необходимы в более поздних стандартах.

Поскольку объявления функций K&R не включали никакой информации об аргументах функции, проверки типа параметра функции не выполнялись, хотя некоторые компиляторы выдают предупреждающее сообщение, если локальная функция вызывается с неправильным количеством аргументов или если несколько вызовов внешней функции использовали разное количество или типы аргументов. Были разработаны отдельные инструменты, такие как утилита lint для Unix, которые (помимо прочего) могли проверять согласованность использования функций в нескольких исходных файлах.

За годы, прошедшие после публикации K&R C, в язык были добавлены несколько функций, поддерживаемых компиляторами от AT&T (в частности, PCC ) и некоторых других поставщиков. К ним относятся:

Большое количество расширений и отсутствие согласия по стандартной библиотеке вместе с популярностью языка и тем фактом, что даже компиляторы Unix не реализовали точно спецификацию K&R, привели к необходимости стандартизации.

ANSI C и ISO C

В конце 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), чтобы исправить некоторые детали и добавить более широкую поддержку международных наборов символов.

C99

1999 ISO C.pdf

Стандарт C был дополнительно пересмотрен в конце 1990-х, что привело к публикации ISO / IEC 9899: 1999 в 1999 году, который обычно называют « C99 ». С тех пор в него трижды вносились технические поправки.

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 .

Кроме того, теперь требуется поддержка идентификаторов Unicode (имен переменных / функций) в форме экранированных символов (например \U0001f431). Поддержка необработанных имен Unicode необязательна.

C11

В 2007 году началась работа над еще одной версией стандарта C, неофициально называемой «C1X», до его официальной публикации 8 декабря 2011 года. Комитет по стандартам C принял руководящие принципы, ограничивающие принятие новых функций, которые не были протестированы существующими реализациями.

Стандарт C11 добавляет множество новых функций в C и библиотеку, включая универсальные макросы типов, анонимные структуры, улучшенную поддержку Unicode, атомарные операции, многопоточность и функции с проверкой границ. Это также делает некоторые части существующей библиотеки C99 необязательными и улучшает совместимость с C ++. Стандартный макрос __STDC_VERSION__определен как 201112Lуказывающий на то, что доступна поддержка C11.

C17

Опубликованный в июне 2018 года, C17 является текущим стандартом для языка программирования C. Он не вводит никаких новых языковых функций, только технические исправления и разъяснения дефектов в C11. Стандартный макрос __STDC_VERSION__определяется как 201710L.

C2x

C2x - неофициальное название следующей (после C17) основной версии стандарта языка C. Ожидается, что голосование состоится в 2023 году, и поэтому он будет называться C23.

Встроенный C

Исторически сложилось так, что встроенное программирование на C требует нестандартных расширений языка C для поддержки экзотических функций, таких как арифметика с фиксированной точкой, несколько отдельных банков памяти и базовые операции ввода-вывода.

В 2008 году Комитет по стандартам C опубликовал технический отчет, расширяющий возможности языка C для решения этих проблем, предоставляя общий стандарт, которого должны придерживаться все реализации. Он включает в себя ряд функций, недоступных в обычном C, таких как арифметика с фиксированной точкой , именованные адресные пространства и базовая аппаратная адресация ввода-вывода.

Синтаксис

C имеет формальную грамматику, определенную стандартом C. Окончания строк обычно не имеют значения в C; однако границы строк действительно имеют значение на этапе предварительной обработки. Комментарии могут появляться либо между разделителями /*и */, либо (начиная с C99) //до конца строки. Комментарии разграничены /*и */не гнездится, и эти последовательности символов не интерпретируются как комментарий разделители , если они появляются внутри строку или символьные литералы.

Исходные файлы C содержат объявления и определения функций. Определения функций, в свою очередь, содержат объявления и операторы . Объявления либо определяют новые типы с помощью таких ключевых слов, как struct, unionи enum, либо присваивают типы и, возможно, резервируют хранилище для новых переменных, обычно путем записи типа, за которым следует имя переменной. Ключевые слова, такие как charи intопределяют встроенные типы. Разделы кода заключаются в фигурные скобки ( {и }иногда их называют «фигурными скобками»), чтобы ограничить объем объявлений и действовать как единый оператор для управляющих структур.

В качестве императивного языка C использует операторы для определения действий. Наиболее распространенный оператор - это оператор выражения , состоящий из выражения, которое нужно вычислить, за которым следует точка с запятой; в качестве побочного эффекта оценки могут быть вызваны функции и переменным могут быть присвоены новые значения. Чтобы изменить нормальное последовательное выполнение операторов, C предоставляет несколько операторов потока управления, идентифицированных зарезервированными ключевыми словами. Структурное программирование поддерживается if(- else) условное исполнение и do- while, whileи forитеративного выполнения (обхвата). Этот forоператор имеет отдельные выражения инициализации, тестирования и повторной инициализации, любое или все из которых можно опустить. breakи continueможет использоваться для выхода из самого внутреннего оператора включающего цикла или перехода к его повторной инициализации. Существует также неструктурированный gotoоператор, который ведет непосредственно к назначенной метке внутри функции. switchвыбирает caseдля выполнения на основе значения целочисленного выражения.

Выражения могут использовать множество встроенных операторов и могут содержать вызовы функций. Порядок, в котором оцениваются аргументы функций и операнды для большинства операторов, не указан. Оценки могут даже чередоваться. Однако все побочные эффекты (включая сохранение переменных) будут происходить до следующей « точки последовательности »; точки последовательности включают конец каждого оператора выражения, а также вход и выход из каждого вызова функции. Точки последовательности также происходят во время вычисления выражений , содержащих определенные операторы ( &&, ||, ?:и оператор запятой ). Это позволяет компилятору в высокой степени оптимизировать объектный код, но требует, чтобы программисты на C уделяли больше внимания получению надежных результатов, чем это требуется для других языков программирования.

Керниган и Ричи говорят во введении к языку программирования C : «C, как и любой другой язык, имеет свои недостатки. У некоторых операторов неправильный приоритет; некоторые части синтаксиса могли бы быть лучше». Стандарт C не пытался исправить многие из этих недостатков из-за воздействия таких изменений на уже существующее программное обеспечение.

Набор символов

Базовый набор символов исходного кода C включает следующие символы:

Новая строка обозначает конец текстовой строки; он не обязательно должен соответствовать реальному одиночному символу, хотя для удобства C рассматривает его как один.

В строковых литералах можно использовать дополнительные многобайтовые символы, но они не являются полностью переносимыми . Последний стандарт C ( C11 ) позволяет переносимо встраивать многонациональные символы Unicode в исходный текст C с помощью \uXXXXили \UXXXXXXXXкодирования (где Xобозначает шестнадцатеричный символ), хотя эта функция еще не получила широкого распространения.

Базовый набор выполнения C символов содержат одни и те же символы, наряду с представлениями для предупреждения , забоя и возврата каретки . Запуск время поддержка расширенных наборов символов увеличились с каждым пересмотром стандарта C.

Зарезервированные слова

C89 имеет 32 зарезервированных слова, также известных как ключевые слова, которые не могут использоваться для каких-либо целей, кроме тех, для которых они предопределены:

C99 зарезервировал еще пять слов:

C11 зарезервировал еще семь слов:

  • _Alignas
  • _Alignof
  • _Atomic
  • _Generic
  • _Noreturn
  • _Static_assert
  • _Thread_local

Большинство недавно зарезервированных слов начинаются с подчеркивания, за которым следует заглавная буква, потому что идентификаторы этой формы ранее были зарезервированы стандартом C для использования только реализациями. Поскольку существующий исходный код программы не должен использовать эти идентификаторы, это не повлияет, когда реализации C начнут поддерживать эти расширения языка программирования. Некоторые стандартные заголовки действительно определяют более удобные синонимы для подчеркнутых идентификаторов. Раньше язык включал зарезервированное слово, называемое entry, но это редко реализовывалось, и теперь было удалено как зарезервированное слово.

Операторы

C поддерживает богатый набор операторов , которые представляют собой символы, используемые в выражении для указания операций , которые необходимо выполнить при вычислении этого выражения. В C есть операторы для:

C использует оператор =(используемый в математике для выражения равенства) для обозначения присваивания, следуя прецеденту Fortran и PL / I , но в отличие от ALGOL и его производных. C использует оператор ==для проверки равенства. Сходство между этими двумя операторами (присваивание и равенство) может привести к случайному использованию одного вместо другого, и во многих случаях ошибка не вызывает сообщения об ошибке (хотя некоторые компиляторы выдают предупреждения). Например, условное выражение if (a == b + 1)может быть ошибочно записано как if (a = b + 1), которое будет оценено как истинное, если aпосле присвоения не будет равно нулю.

Приоритет оператора C не всегда интуитивно понятен. Например, оператор ==связывает более жестко, чем (выполняется до) операторы &(побитовое И) и |(побитовое ИЛИ) в таких выражениях, как x & 1 == 0, которые должны быть записаны так, как (x & 1) == 0если бы это было намерением кодировщика.

Пример "Привет, мир"

"Привет, мир!" программа Брайана Кернигана (1978)

Пример " hello, world ", появившийся в первом издании K&R , стал моделью для вводной программы в большинстве учебников по программированию. Программа выводит «привет, мир» на стандартный вывод , который обычно представляет собой дисплей терминала или экран.

Исходная версия была:

main()
{
    printf("hello, world\n");
}

Соответствующая стандартам программа "Hello, world":

# include <stdio.h>

int main(void)
{
    printf("hello, world\n");
}

Первая строка программы содержит директиву предварительной обработки , обозначенную значком #include. Это заставляет компилятор заменять эту строку полным текстом stdio.hстандартного заголовка, который содержит объявления для стандартных функций ввода и вывода, таких как printfи scanf. Окружающие угловые скобки stdio.hуказывают на то, что stdio.hиспользуется стратегия поиска, которая предпочитает заголовки, предоставленные с компилятором, другим заголовкам с тем же именем, в отличие от двойных кавычек, которые обычно включают локальные или специфичные для проекта файлы заголовков.

Следующая строка указывает на то, что определена названная функция main. Эта mainфункция служит специальной цели в программах на языке C; среда выполнения вызывает mainфункцию, чтобы начать выполнение программы. Спецификатор типа intуказывает, что значение, возвращаемое вызывающей стороне (в данном случае среде выполнения) в результате оценки mainфункции, является целым числом. Ключевое слово voidкак список параметров указывает, что эта функция не принимает аргументов.

Открывающая фигурная скобка указывает на начало определения mainфункции.

Следующая строка вызывает (перенаправляет выполнение) названную функцию printf, которая в данном случае предоставляется из системной библиотеки . В этом вызове printfфункции передается (предоставляется) единственный аргумент - адрес первого символа строкового литерала "hello, world\n" . Строковый литерал - это безымянный массив с элементами типа char, автоматически устанавливаемый компилятором с заключительным 0-значным символом для обозначения конца массива ( printfнеобходимо знать это). \nПредставляет собой последовательность символов , что С переводит к новой строке символу, который на выходе означает конец текущей строки. Возвращаемое значение printfфункции относится к типу int, но оно игнорируется, поскольку не используется. (Более тщательная программа могла бы проверить возвращаемое значение, чтобы определить, printfуспешно ли выполнена функция.) Точка с запятой ;завершает оператор.

Закрывающая фигурная скобка указывает конец кода mainфункции. Согласно спецификации C99 и новее, mainфункция, в отличие от любой другой функции, будет неявно возвращать значение 0при достижении того, }что завершает функцию. (Раньше return 0;требовался явный оператор.) Это интерпретируется системой времени выполнения как код выхода, указывающий на успешное выполнение.

Типы данных

Система типов в C является статической и слабо типизированной , что делает ее похожей на систему типов потомков АЛГОЛА, таких как Паскаль . Существуют встроенные типы для целых чисел разного размера, как со знаком, так и без знака, чисел с плавающей запятой и перечислимых типов ( enum). Целочисленный тип charчасто используется для однобайтовых символов. C99 добавил логический тип данных . Существуют также производные типы, включая массивы , указатели , записи ( struct) и объединения ( union).

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

Некоторые находят синтаксис объявления C неинтуитивным, особенно для указателей на функции . (Идея Ричи заключалась в объявлении идентификаторов в контексте, напоминающем их использование: « объявление отражает использование ».)

Обычные арифметические преобразования C позволяют генерировать эффективный код, но иногда могут давать неожиданные результаты. Например, сравнение знаковых и беззнаковых целых чисел одинаковой ширины требует преобразования знакового значения в беззнаковое. Это может привести к неожиданным результатам, если значение со знаком отрицательное.

Указатели

C поддерживает использование указателей , типа ссылки, которая записывает адрес или расположение объекта или функции в памяти. Указатели могут быть разыменованы для доступа к данным, хранящимся по указанному адресу, или для вызова указанной функции. Указателями можно управлять с помощью присваивания или арифметики указателя . Представление значения указателя во время выполнения обычно представляет собой необработанный адрес памяти (возможно, дополненный полем смещения внутри слова), но поскольку тип указателя включает тип объекта, на который указывает, выражения, включающие указатели, могут быть проверены по типу во время компиляции. Арифметика указателя автоматически масштабируется по размеру указанного типа данных. Указатели используются для многих целей в C. Текстовые строки обычно обрабатываются с помощью указателей на массивы символов. Распределение динамической памяти выполняется с помощью указателей. Многие типы данных, такие как деревья , обычно реализуются как динамически выделяемые structобъекты, связанные вместе с помощью указателей. Указатели на функции полезны для передачи функций в качестве аргументов функциям более высокого порядка (таким как qsort или bsearch ) или как обратные вызовы, которые будут вызываться обработчиками событий.

Указатель NULL значение явно не указывает ни действительного местоположения. Разыменование значения нулевого указателя не определено, что часто приводит к ошибке сегментации . Значения нулевого указателя полезны для указания особых случаев, таких как отсутствие указателя «следующий» в последнем узле связанного списка , или как указание на ошибку от функций, возвращающих указатели. В соответствующих контекстах исходного кода, например для присвоения переменной- указателю, константа нулевого указателя может быть записана как 0с явным приведением к типу указателя или без него, или как NULLмакрос, определенный несколькими стандартными заголовками. В условных контекстах значения нулевого указателя оцениваются как ложные, в то время как все другие значения указателей оцениваются как истинные.

Пустые указатели ( void *) указывают на объекты неопределенного типа и поэтому могут использоваться как «общие» указатели на данные. Поскольку размер и тип указанного объекта неизвестны, указатели void не могут быть разыменованы, а арифметика указателей на них не разрешена, хотя они могут быть легко (и во многих контекстах неявно) преобразованы в любой другой указатель объекта и обратно. тип.

Небрежное использование указателей потенциально опасно. Поскольку они обычно не отмечены, переменная-указатель может указывать на любое произвольное место, что может вызвать нежелательные эффекты. Хотя правильно используемые указатели указывают на безопасные места, их можно сделать так, чтобы они указывали на небезопасные места, используя арифметику неверных указателей ; объекты, на которые они указывают, могут продолжать использоваться после освобождения ( висячие указатели ); их можно использовать без инициализации ( дикие указатели ); либо им может быть прямо присвоено небезопасное значение с помощью приведения, объединения или другого поврежденного указателя. В общем, C допускает манипулирование и преобразование между типами указателей, хотя компиляторы обычно предоставляют варианты для различных уровней проверки. Некоторые другие языки программирования решают эти проблемы, используя более ограничительные ссылочные типы.

Массивы

Типы массивов в C обычно имеют фиксированный статический размер, указанный во время компиляции. (Более поздний стандарт C99 также допускает форму массивов переменной длины.) Однако также можно выделить блок памяти (произвольного размера) во время выполнения, используя mallocфункцию стандартной библиотеки , и рассматривать его как множество. Унификация массивов и указателей в языке C означает, что объявленные массивы и эти динамически распределяемые моделируемые массивы практически взаимозаменяемы.

Поскольку доступ к массивам всегда (фактически) осуществляется через указатели, доступ к массивам обычно не проверяется по размеру базового массива, хотя некоторые компиляторы могут предоставлять проверку границ в качестве опции. Следовательно, нарушения границ массива возможны и довольно распространены в небрежно написанном коде и могут привести к различным последствиям, включая незаконный доступ к памяти, повреждение данных, переполнение буфера и исключения времени выполнения. Если требуется проверка границ, это необходимо сделать вручную.

C не имеет специального положения для объявления многомерных массивов , но полагается на рекурсию в системе типов для объявления массивов массивов, что фактически выполняет то же самое. Значения индекса результирующего «многомерного массива» можно рассматривать как возрастающие в порядке возрастания строк .

Многомерные массивы обычно используются в численных алгоритмах (в основном из прикладной линейной алгебры ) для хранения матриц. Структура массива C хорошо подходит для этой конкретной задачи. Однако, поскольку массивы передаются просто как указатели, границы массива должны быть известными фиксированными значениями или же явно передаваться любой подпрограмме, которая их требует, а к массивам массивов с динамическим размером нельзя получить доступ с помощью двойной индексации. (Обходной путь для этого - выделить массив с дополнительным "вектором-строкой" указателей на столбцы.)

C99 представил «массивы переменной длины», которые решают некоторые, но не все, проблемы с обычными массивами C.

Взаимозаменяемость массива и указателя

Обозначение индекса x[i](где xобозначает указатель) является синтаксическим сахаром для *(x+i). Воспользовавшись знаниями компилятора о типе указателя, адрес, на который x + iуказывает, не является базовым адресом (на который указывает x), увеличенным на iбайты, а скорее определяется как базовый адрес, увеличенный на, iумноженный на размер элемента, который xуказывает к. Таким образом, x[i]обозначает i+1th элемент массива.

Более того, в большинстве контекстов выражений (заметным исключением является операнд sizeof) имя массива автоматически преобразуется в указатель на первый элемент массива. Это означает, что массив никогда не копируется целиком, когда он назван в качестве аргумента функции, а передается только адрес его первого элемента. Следовательно, хотя вызовы функций в C используют семантику передачи по значению , массивы фактически передаются по ссылке .

Размер элемента можно определить, применив оператор sizeofк любому разыменованному элементу x, как в n = sizeof *xили n = sizeof x[0], а количество элементов в объявленном массиве Aможет быть определено как sizeof A / sizeof A[0]. Последнее относится только к именам массивов: переменным, объявленным с помощью индексов ( int A[20]). Из-за семантики C невозможно определить полный размер массивов с помощью указателей на массивы, таких как массивы, созданные с помощью динамического выделения ( malloc) или параметров функции массива; такой код, как sizeof arr / sizeof arr[0](где arrобозначает указатель) не будет работать, поскольку компилятор предполагает, что запрашивается размер самого указателя. Поскольку аргументы имени массива sizeofне преобразуются в указатели, они не проявляют такой двусмысленности. Однако к массивам, созданным с помощью динамического выделения, обращаются указатели, а не истинные переменные массива, поэтому они страдают от тех же sizeofпроблем, что и указатели на массивы.

Таким образом, несмотря на эту очевидную эквивалентность между переменными массива и указателя, все же необходимо проводить различие между ними. Несмотря на то, что имя массива в большинстве контекстов выражения преобразуется в указатель (на его первый элемент), этот указатель сам по себе не занимает никакой памяти; имя массива не является l-значением , а его адрес является константой, в отличие от переменной-указателя. Следовательно, то, на что «указывает» массив, не может быть изменено, и невозможно присвоить новый адрес имени массива. Однако содержимое массива можно скопировать с помощью memcpyфункции или доступа к отдельным элементам.

Управление памятью

Одна из наиболее важных функций языка программирования - предоставить средства для управления памятью и объектами, которые хранятся в памяти. C предоставляет три различных способа выделения памяти для объектов:

  • Распределение статической памяти : пространство для объекта предоставляется в двоичном коде во время компиляции; эти объекты имеют протяженность (или время жизни) до тех пор, пока двоичный файл, который их содержит, загружен в память.
  • Автоматическое выделение памяти : временные объекты могут храниться в стеке , и это пространство автоматически освобождается и может использоваться повторно после выхода из блока, в котором они объявлены.
  • Динамическое выделение памяти : блоки памяти произвольного размера могут запрашиваться во время выполнения с использованием библиотечных функций, например, mallocиз области памяти, называемой кучей ; эти блоки сохраняются до тех пор, пока впоследствии не будут освобождены для повторного использования путем вызова библиотечной функции reallocилиfree

Эти три подхода подходят в разных ситуациях и имеют разные компромиссы. Например, выделение статической памяти имеет небольшие накладные расходы, автоматическое выделение может включать немного больше накладных расходов, а выделение динамической памяти потенциально может иметь большие накладные расходы как для выделения, так и для освобождения. Постоянный характер статических объектов полезен для сохранения информации о состоянии между вызовами функций, автоматическое выделение легко использовать, но пространство стека, как правило, гораздо более ограничено и временное, чем статическая память или пространство кучи, а динамическое распределение памяти позволяет удобно выделять объекты, чьи размер известен только во время выполнения. Большинство программ на C широко используют все три.

Там, где это возможно, автоматическое или статическое распределение обычно является наиболее простым, поскольку хранилище управляется компилятором, освобождая программиста от потенциально подверженной ошибкам рутинной работы по выделению и освобождению хранилища вручную. Однако многие структуры данных могут изменяться по размеру во время выполнения, и поскольку статические выделения (и автоматические выделения до C99) должны иметь фиксированный размер во время компиляции, существует много ситуаций, в которых необходимо динамическое выделение. До стандарта C99 типичным примером этого были массивы переменного размера. (См. В статье mallocпример динамически выделяемых массивов.) В отличие от автоматического выделения, которое может дать сбой во время выполнения с неконтролируемыми последствиями, функции динамического выделения возвращают указание (в виде значения нулевого указателя), когда требуемое хранилище не может быть выделенным. (Слишком большое статическое выделение памяти обычно обнаруживается компоновщиком или загрузчиком еще до того, как программа сможет начать выполнение.)

Если не указано иное, статические объекты содержат нулевые или нулевые значения указателей при запуске программы. Автоматически и динамически выделяемые объекты инициализируются только в том случае, если явно указано начальное значение; в противном случае они изначально имеют неопределенные значения (как правило, любой битовый шаблон , присутствующий в хранилище , может даже не представлять допустимое значение для этого типа). Если программа пытается получить доступ к неинициализированному значению, результаты не определены. Многие современные компиляторы пытаются обнаружить и предупредить об этой проблеме, но могут возникать как ложные срабатывания, так и ложноотрицательные .

Выделение памяти в куче должно быть синхронизировано с ее фактическим использованием в любой программе, чтобы ее можно было повторно использовать в максимально возможной степени. Например, если единственный указатель на выделение памяти в куче выходит за пределы области видимости или его значение перезаписывается до того, как оно будет явно освобождено, то эта память не может быть восстановлена ​​для последующего повторного использования и по существу потеряна для программы, явление, известное как память утечка . И наоборот, память может быть освобождена, но на нее впоследствии будут ссылаться, что приведет к непредсказуемым результатам. Обычно симптомы сбоя появляются в части программы, не связанной с кодом, вызывающим ошибку, что затрудняет диагностику сбоя. Такие проблемы устраняются в языках с автоматической сборкой мусора .

Библиотеки

Язык программирования C использует библиотеки в качестве основного метода расширения. В C библиотека - это набор функций, содержащихся в одном «архивном» файле. Каждая библиотека обычно имеет файл заголовка , который содержит прототипы функций, содержащихся в библиотеке, которые могут использоваться программой, а также объявления специальных типов данных и макросимволов, используемых с этими функциями. Чтобы программа могла использовать библиотеку, она должна включать файл заголовка библиотеки, и библиотека должна быть связана с программой, что во многих случаях требует наличия флагов компилятора (например -lm, сокращение для «связать математическую библиотеку»).

Наиболее распространенной библиотекой C является стандартная библиотека C , которая определяется стандартами ISO и ANSI C и поставляется с каждой реализацией C (реализации, предназначенные для ограниченных сред, таких как встроенные системы, могут предоставлять только подмножество стандартной библиотеки). Эта библиотека поддерживает потоковый ввод и вывод, распределение памяти, математику, символьные строки и значения времени. Несколько отдельных стандартных заголовков (например, stdio.h) определяют интерфейсы для этих и других средств стандартной библиотеки.

Другой распространенный набор функций библиотеки C - это те, которые используются приложениями, специально предназначенными для Unix и Unix-подобных систем, особенно функции, которые обеспечивают интерфейс с ядром . Эти функции подробно описаны в различных стандартах, таких как POSIX и Single UNIX Specification .

Поскольку многие программы написаны на C, доступно множество других библиотек. Библиотеки часто пишутся на C, потому что компиляторы C генерируют эффективный объектный код ; Затем программисты создают интерфейсы к библиотеке, чтобы можно было использовать подпрограммы из языков более высокого уровня, таких как Java , Perl и Python .

Обработка файлов и потоки

Файловый ввод и вывод (I / O) не является частью самого языка C, но вместо этого обрабатывается библиотеками (такими как стандартная библиотека C) и связанными с ними файлами заголовков (например stdio.h). Обработка файлов обычно осуществляется посредством высокоуровневого ввода-вывода, который работает через потоки . С этой точки зрения поток - это поток данных, который не зависит от устройств, а файл - это конкретное устройство. Высокоуровневый ввод-вывод выполняется через ассоциацию потока с файлом. В стандартной библиотеке C буфер (область памяти или очередь) временно используется для хранения данных перед их отправкой в ​​конечный пункт назначения. Это сокращает время ожидания более медленных устройств, например жесткого диска или твердотельного накопителя . Функции низкоуровневого ввода-вывода не являются частью стандартной библиотеки C, но обычно являются частью "голого" программирования (программирования, которое не зависит от какой-либо операционной системы, например, для большинства встроенных программ ). За некоторыми исключениями, реализации включают в себя низкоуровневый ввод-вывод.

Языковые инструменты

Ряд инструментов был разработан, чтобы помочь программистам на C находить и исправлять операторы с неопределенным поведением или, возможно, ошибочными выражениями, с большей строгостью, чем та, которая предоставляется компилятором. Инструмент lint был первым из них, за ним последовало множество других.

Автоматическая проверка и аудит исходного кода полезны для любого языка, и для C существует множество таких инструментов, например Lint . Распространенной практикой является использование Lint для обнаружения сомнительного кода при первом написании программы. Как только программа проходит Lint, она компилируется с помощью компилятора C. Кроме того, многие компиляторы могут дополнительно предупреждать о синтаксически правильных конструкциях, которые могут быть ошибками. MISRA C - это проприетарный набор рекомендаций, позволяющий избежать такого сомнительного кода, разработанный для встраиваемых систем.

Существуют также компиляторы, библиотеки и механизмы уровня операционной системы для выполнения действий, которые не являются стандартной частью C, таких как проверка границ для массивов, обнаружение переполнения буфера , сериализация , отслеживание динамической памяти и автоматическая сборка мусора .

Такие инструменты, как Purify или Valgrind, и связывание с библиотеками, содержащими специальные версии функций распределения памяти, могут помочь выявить ошибки времени выполнения в использовании памяти.

Использует

Индекс TIOBE график, показывающий сравнение популярности различных языков программирования

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

C может использоваться для программирования веб-сайтов с использованием Common Gateway Interface (CGI) в качестве «шлюза» для обмена информацией между веб-приложением, сервером и браузером. C часто предпочитают интерпретируемым языкам из-за его скорости, стабильности и почти универсальной доступности.

Следствие широкой доступности и эффективности C является то , что компиляторы , библиотека и переводчики других языков программирования часто реализуются в С. Например, ссылка реализация на Python , Perl , Ruby , и PHP написана на C.

C позволяет программистам создавать эффективные реализации алгоритмов и структур данных, потому что уровень абстракции от оборудования тонкий, а накладные расходы низкие, что является важным критерием для программ с интенсивными вычислениями. Например, GNU Multiple Precision Арифметика библиотека , то GNU научная библиотека , Mathematica , и MATLAB полностью или частично написаны на C.

C иногда используется в качестве промежуточного языка при реализации других языков. Этот подход может использоваться для портативности или удобства; при использовании C в качестве промежуточного языка дополнительные машинно-зависимые генераторы кода не требуются. C имеет некоторые функции, такие как директивы препроцессора для номеров строк и необязательные лишние запятые в конце списков инициализаторов, которые поддерживают компиляцию сгенерированного кода. Однако некоторые недостатки C побудили к разработке других языков на основе C, специально предназначенных для использования в качестве промежуточных языков, таких как C-- .

C также широко используется для реализации приложений для конечных пользователей . Однако такие приложения также могут быть написаны на более новых языках высокого уровня.

Родственные языки

C прямо и косвенно повлиял на многие более поздние языки, такие как C # , D , Go , Java , JavaScript , Limbo , LPC , Perl , PHP , Python и оболочку C Unix . Наиболее распространенное влияние было синтаксическим; все упомянутые языки сочетают синтаксис операторов и (более или менее узнаваемых) выражений языка Си с системами типов, моделями данных и / или крупномасштабными программными структурами, которые отличаются от таковых в Си, иногда радикально.

Существует несколько интерпретаторов C или близких к C, включая Ch и CINT , которые также можно использовать для написания сценариев.

Когда стали популярными объектно-ориентированные языки, C ++ и Objective-C были двумя разными расширениями C, которые обеспечивали объектно-ориентированные возможности. Оба языка изначально были реализованы как компиляторы «исходный код» ; исходный код был переведен на C, а затем скомпилирован с помощью компилятора C.

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

Objective-C изначально был очень «тонким» слоем поверх C и остается строгим надмножеством C, позволяющим объектно-ориентированное программирование с использованием гибридной парадигмы динамической / статической типизации. Objective-C заимствует свой синтаксис как из C, так и из Smalltalk : синтаксис, который включает предварительную обработку, выражения, объявления функций и вызовы функций, унаследован от C, в то время как синтаксис для объектно-ориентированных функций был первоначально взят из Smalltalk.

В дополнение к C ++ и Objective-C , Ch , Cilk и Unified Parallel C являются почти надмножествами C.

Смотрите также

Примечания

использованная литература

Источники

дальнейшее чтение

внешние ссылки