Си (от лат. буквы C англ. языка) — компилируемый статически типизированный язык программирования общего назначения, разработанный в 1969—1973 годах сотрудником Bell Labs Деннисом Ритчи как развитие языка Би. Первоначально был разработан для реализации операционной системы UNIX, но впоследствии был перенесён на множество других платформ. Согласно дизайну языка, его конструкции близко сопоставляются типичным машинным инструкциям, благодаря чему он нашёл применение в проектах, для которых был свойственен язык ассемблера, в том числе как в операционных системах, так и в различном прикладном программном обеспечении для множества устройств — от суперкомпьютеров до встраиваемых систем. Язык программирования Си оказал существенное влияние на развитие индустрии программного обеспечения, а его синтаксис стал основой для таких языков программирования, как C++, C#, Java и Objective-C.

История

Язык программирования Си разрабатывался в период с 1969 по 1973 годы в лабораториях Bell Labs, и к 1973 году на этот язык была переписана большая часть ядра UNIX, первоначально написанного на ассемблере PDP-11/20. Название языка стало логическим продолжением старого языка «Би», многие особенности которого были положены в основу.

По мере развития язык сначала стандартизировали как ANSI C, а затем этот стандарт был принят комитетом по международной стандартизации ISO как ISO C, ставший также известным под названием C90. В стандарте С99 язык получил новые возможности, такие как массивы переменной длины и встраиваемые функции. А в стандарте C11 в язык добавили реализацию потоков и поддержку атомарных типов; поддержка массивов переменной длины стала необязательной. 31 октября 2024 года официально вышел стандарт C23.

Общие сведения

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

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

Основные особенности Си:

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

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

Часть отсутствующих возможностей может имитироваться встроенными средствами (например, сопрограммы можно имитировать с помощью функций setjmp и longjmp), часть добавляется с помощью сторонних библиотек (например, для поддержки многозадачности и для сетевых функций можно использовать библиотеки pthreads, sockets и тому подобные; существуют библиотеки для поддержки автоматической сборки мусораA garbage collector for C and C++ ), часть реализуется в некоторых компиляторах в виде расширений языка (например, вложенные функции в GCC). Существует несколько громоздкая, но вполне работоспособная методика, позволяющая реализовывать на Си механизмы ООПObject-Oriented Programming With ANSI-C , базирующаяся на фактической полиморфности указателей в Си и поддержке в этом языке указателей на функции. Механизмы ООП, основанные на данной модели, реализованы в библиотеке GLib и активно используются во фреймворке GTK. GLib предоставляет базовый класс GObject, возможности наследования от одного класса и реализации множества интерфейсов.

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

Синтаксис и семантика

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

Типы данных

Примитивные типы

Целые числа Размер целочисленных типов данных варьируется от не менее 8 до не менее 32 бит. Стандарт C99 увеличивает максимальный размер целого числа — не менее 64 бит. Целочисленные типы данных используются для хранения целых чисел (тип char также используется для хранения ASCII-символов). Все размеры диапазонов представленных ниже типов данных минимальны и на отдельно взятой платформе могут быть больше.

Как следствие минимальных размеров типов стандарт требует, чтобы для размеров целочисленных типов выполнялось условие:

1 = sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) ≤ sizeof(long long).

Таким образом, размеры некоторых типов по количеству байт могут совпадать, если будет удовлетворяться условие по минимальному количеству бит. Даже char и long могут иметь одинаковый размер, если один байт будет занимать 32 бита или более, но такие платформы будут очень редки или не будут существовать. Стандарт гарантирует, что тип char всегда равен 1 байту. Размер байта в битах определяется константой CHAR_BIT из заголовочного файла limits.h, у POSIX-совместимых систем равен 8 битам.

Минимальный диапазон значений целых типов по стандарту определяется с -(2N-1-1) по 2N-1-1 для знаковых типов и с 0 по 2N-1 — для беззнаковых, где N — разрядность типа. Реализация компиляторов может расширять этот диапазон по своему усмотрению. На практике для знаковых типов чаще используется диапазон с -2N-1 по 2N-1-1. Минимальное и максимальное значения каждого типа указывается в файле limits.h в виде макроопределений.

Отдельное внимание стоит уделить типу char. Формально это отдельный тип, но фактически char эквивалентен либо signed char, либо unsigned char, в зависимости от компилятора.

Для того, чтобы избежать путаницы между размерами типов стандарт C99 ввел новые типы данных, описанные в файле stdint.h. Среди них такие типы как: intN_t, int_leastN_t, int_fastN_t, где N = 8, 16, 32 или 64. Приставка least- обозначает минимальный тип, способный вместить N бит, приставка fast- обозначает тип размером не менее 16 бит, работа с которым наиболее быстрая на данной платформе. Типы без приставок обозначают типы с фиксированном размером, равным N бит.

Типы с приставками least- и fast- можно считать заменой типам int, short, long, с той лишь разницей, что первые дают программисту выбрать между скоростью и размером. +Основные типы данных для хранения целых чиселТип данныхРазмерМинимальный диапазон значенийСтандартsigned charминимум 8 битот −127 (= -(27−1)) до 127C90int_least8_tC99int_fast8_tunsigned charминимум 8 битот 0 до 255 (=28−1)C90uint_least8_tC99uint_fast8_tcharминимум 8 битот −127 до 127 или от 0 до 255 в зависимости от компилятораC90short intминимум 16 битот −32,767 (= -(215−1)) до 32,767C90intint_least16_tC99int_fast16_tunsigned short intминимум 16 битот 0 до 65,535 (= 216−1)C90unsigned intuint_least16_tC99uint_fast16_tlong intминимум 32 бита от −2,147,483,647 до 2,147,483,647C90int_least32_tC99int_fast32_tunsigned long intминимум 32 бита от 0 до 4,294,967,295 (= 232−1)C90uint_least32_tC99uint_fast32_tlong long intминимум 64 битаот −9,223,372,036,854,775,807 до 9,223,372,036,854,775,807C99int_least64_tint_fast64_tunsigned long long intминимум 64 битаот 0 до 18,446,744,073,709,551,615 (= 264−1)uint_least64_tuint_fast64_tint8_t8 битот −127 до 127uint8_t8 битот 0 до 255 (=28−1)int16_t16 битот −32,767 до 32,767uint16_t16 битот 0 до 65,535 (= 216−1)int32_t32 битаот −2,147,483,647 до 2,147,483,647uint32_t32 битаот 0 до 4,294,967,295 (= 232−1)int64_t64 битаот −9,223,372,036,854,775,807 до 9,223,372,036,854,775,807uint64_t64 битаот 0 до 18,446,744,073,709,551,615 (= 264−1)В таблице приведён минимальный диапазон значений согласно стандарту языка. Компиляторы языка Си могут расширять диапазон значений.

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

В Си существует несколько дополнительных целочисленных типов для безопасной работы с типом данных указателей: intptr_t, uintptr_t и ptrdiff_t. Типы intptr_t и uintptr_t из стандарта C99 предназначены для хранения соответственно знакового и беззнакового значений, которые по размеру могут уместить в себе указатель. Эти типы часто применяются для хранения произвольного целого числа в указателе, например, как способ избавиться от лишнего выделения памяти при регистрации функций обратной связи либо при использовании сторонних связных списков, ассоциативных массивов и прочих структур, в которых данные хранятся по указателю. Тип ptrdiff_t из заголовочного файла stddef.h предназначен для безопасного хранения разности двух указателей.

Для хранения размера предусмотрен беззнаковый тип size_t из заголовочного файла stddef.h. Данный тип способен уместить максимально возможное количество байт, доступное по указателю, и обычно используется для хранения размера в байтах. Значение именно этого типа возвращает оператор sizeof.

Приведение целочисленных типов